from http://jlongster.com/Writing-Your-First-Sweet.js-Macrophp
你將學會html
全部的教程和可用的編譯 sweet.js 宏環境都躺在 repo https://github.com/jlongster/sweet.js-tutorials 中。下一個教程是遞歸的宏以及模式匹配 http://jlongster.com/Sweet.js-Tutorial--2--Recursive-Macros-and-Custom-Pattern-Classesnode
若是你徹底不瞭解 sweet.js 和 js 宏,我推薦先閱讀 http://jlongster.com/Stop-Writing-JavaScript-Compilers--Make-Macros-Instead。我儘早把這些表達出來,雖然要花些時間。你能夠訂閱 rss http://feeds.feedburner.com/jlongster 或者 follow 個人 twitter https://twitter.com/jlongstergit
整個教程都圍繞他進行,你能夠交互式的左邊寫代碼,右邊會自動 expand 展開你的代碼,而且展現結果 (ps:咱們這裏可能要上面看代碼 (//註釋分隔) 下面看結果了)es6
macro foo { rule { $x } => { $x } } foo "Hi there!"; foo "Another string"; // 'Hi there!'; 'Another string';
若是有報錯 error 會有個大紅條兒出如今底部,也能夠單步調試。這在後面的教程中尤爲有用哦~github
下面有些連接,點擊就會改變左邊框中的代碼,好比點擊這裏,看看變化,點擊 revert 恢復原始代碼。express
macro bar { rule { $x } => { $x } } bar "This text is different!"; bar "The macro name changed too"; // 'This text is different!'; 'The macro name changed too';
當你想要寫更大變化的代碼,直接用在線編輯器 http://sweetjs.org/browser/editor.html 會更好啦npm
注意:本教程用的 sweet.js 提交在 ba9b6771678cb26af58dfa6b5b99d5b7eac75e2c 3月9號, sweet.js 仍然是早期階段,因此你可能碰到 bugs.gulp
宏用關鍵詞 macro 建立。咱們提供一個名字,以及一個模式匹配列表數組
macro foo { rule { $x } => { $x + 'rule1' } } foo 5; foo bar; // 5 + 'rule1'; bar + 'rule1';
宏名字能夠包含任何關鍵字,標識符或者標點。一個 punctuator 加標點者 是一個符號,好比:+ - * & ^ $ # 等等。只有 {} 這幾個被看成分隔符的符號無效。
這裏宏的惟一規則就是綁定 bind 第一個元素到 $x。它執行宏名字後面的模式匹配語法。當你給一個標識符加前綴 $ ,宏就會捕獲匹配的元素,你能夠在模板中輸出匹配的值,不然,宏會照字面意思原樣匹配口令就像 { 或 } 等任何你扔到代碼裏面的東西。
沒有 $ ,宏會照字面意思原樣匹配標識符。來看看,咱們把上面宏裏的 $x 改成 x
macro foo { rule { x } => { $x + 'rule1' } } foo 5; foo bar; // SyntaxError: [macro] Macro `foo` could not be matched with `5 ; foo bar ;...` 5: foo 5; ^
咱們看到,這觸發了一個錯誤。若是你想重置代碼例子,只須要點擊 「恢復 revert」,而後它又活過來啦。
咱們堅持只用 x 他也會工做
macro foo { rule { x } => { 'rule1' } } foo x; // 'rule1';
可是若是咱們調用任何非 x 的數據,都會觸發宏的失敗。它如今只是徹底照字面意思原樣的匹配 x 除非咱們用 $x 來使得他成爲一個,模式變量
來玩玩幾個宏輕鬆下:
macro ^ { rule { { $x } } => { wrapped($x) } } ^{x}; foo(x, y, ^{z}) // wrapped(x); foo(x, y, wrapped(z));
let var = macro { rule { [$x, $y] = $arr } => { var $x = $arr[0]; var $y = $arr[1]; } } var [foo, bar] = arr; // var foo = arr[0]; var bar = arr[1];
下面,咱們來試試添加更多的規則和模式:
macro foo { rule { => $x } => { $x + 'rule1' } rule { [$x] } => { $x + 'rule2' } rule { $x } => { $x + 'rule3' } } foo => 5; foo 6; foo [bar]; // 5 + 'rule1'; 6 + 'rule3'; bar + 'rule2';
如今更好玩了。咱們用不一樣的代碼觸發不一樣的匹配模式。第一個規則匹配 => 口令,而後把這個口令後面跟的任何東西一塊兒綁定到 $x。 foo => 5 正確的匹配到 5 + 'rule1'。第二個規則剝去 [] 而後捕獲到剩下的東西
=> 被視做一個單獨的口令,由於es6 fat arrow functions http://wiki.ecmascript.org/doku.php?id=harmony:arrow_function_syntax 這麼用啦。只要整個口令集都匹配,任何口令集都能正常工做,因此 =*> 也是能夠滴~
macro foo { rule { =*> $x } => { $x + 'rule1' } rule { [$x] } => { $x + 'rule2' } rule { $x } => { $x + 'rule3' } } foo =*> baller; foo 6; foo [bar]; // baller + 'rule1'; 6 + 'rule3'; bar + 'rule2';
規則的順序是嚴格的。這是咱們要學習的模式匹配的一個基本的原則。他是從頂至底匹配的,因此更細的模式應在包容性更強的模式上面。例如,[$x] 是比 $x 更細粒度的,若是你切換他們的順序,foo [bar]將匹配到更包容的模式,也就是說有些就匹配不到了。好比:
macro foo { rule { => $x } => { $x + 'rule1' } rule { $x } => { $x + 'rule3' } rule { [$x] } => { $x + 'rule2' } } foo => 5; foo 6; foo [bar]; // 5 + 'rule1'; 6 + 'rule3'; [bar] + 'rule3';
當你用一個模式變量 $x,他匹配任何在其位置表明的東西,不管是字符串,數組表達式。。。。若是你要限定他匹配的類型咋辦呢?
能夠指定一個特殊的解析類 parse class。例如: $x:ident 只匹配標識符。有3個可用解析類:
你可能會想,什麼是 expr 呢?由於 expr 幾乎能夠是任何東西。默認狀況下,sweet.js 是不貪婪的。他會找到最小匹配的語法。若是你嘗試匹配 $x 爲 bar(),$x 將僅僅被綁定到標識符 bar。若是你強制他爲表達式,$x:expr 才能匹配到整個 bar() 表達式。
使用模式解析類,當出問題的時候,你會獲得很好的匹配錯誤警告。
macro foo { rule { $x:lit } => { $x + 'lit' } rule { $x:ident } => { $x + 'ident' } rule { $x:expr } => { $x + 'expr' } } foo 3; foo "string"; foo bar; foo [1, 2, 3]; foo baz(); // 3 + 'lit'; 'string' + 'lit'; bar + 'ident'; [ 1, 2, 3 ] + 'expr'; baz + 'ident'();
3 和 "string" 匹配 lit,bar 匹配 ident。一個標識符是任意的 js 變量名,一個文本是任何的數字或者字符串常數。
數組以表達式來匹配,這沒錯。可是,baz()呢?前面提到,sweet.js是不貪婪的,他只匹配 baz。特別在這個狀況下,因咱們有了一個 $x:ident 其優先級高於 $x:expr,因而匹配了標識符baz。就算咱們不用模式類,咱們也有這個問題:
macro foo { rule { $x } => { $x + 'any' } rule { $x:expr } => { $x + 'expr' } } foo baz(); // baz + 'any'();
若是咱們真的要匹配整個表達式呢,咱們須要用 $x:expr 而且要把他放到其餘規則之上。
macro foo { rule { $x:expr } => { $x + 'expr' } rule { $x } => { $x + 'any' } } foo baz(); // baz() + 'expr';
至少如今看不出來任何規則在他之下,由於 $x:expr 將匹配表達式和文本。
若是真的想匹配整個表達式,應一直在模式變量上用 expr 類
宏展開是遞歸的,意思是在你的宏運行後 sweet.js 將再次展開代碼。你會發現這個行爲在更高級的宏(用數個可展開的步驟來轉換代碼)中很是好用。當你匹配一個規則,而後釋放代碼來再次運行宏來匹配另外一個規則。在更進一步的教程中,你將看到它們。
macro foo { rule { { $expr:expr } } => { foo ($expr + 3) } rule { ($expr:expr) } => { "expression: " + $expr } } foo { 1 + 2 } // 'expression: ' + 1 + 2 + 3;
一個很好用的功能是重寫關鍵字。例如你能夠實現一個 var 宏,重寫內置的var,並加上 es6 非結構化 http://wiki.ecmascript.org/doku.php?id=harmony:destructuring。固然,若是非結構化沒有被使用,你只須要簡單的擴展到原生的 var 聲明上。
這形成一個問題:你須要釋放一個不遞歸展開宏的代碼。這時候可能用 let 定義一個你本身的宏: let foo = macro { ... }。如今 foo 在你的展開代碼中不會引用任何你的宏外面的 foo ,因此他不會被展開。若是你製做一個 var 宏,任何 var 你釋放的都成爲內置的 var。
來改變上面的例子到一個 let 宏:
let foo = macro { rule { { $expr:expr } } => { foo ($expr + 3) } rule { ($expr:expr) } => { "expression: " + $expr } } foo { 1 + 2 } // foo(1 + 2 + 3);
重複模式容許你一次捕獲多重的模式實例。只要匹配就會這樣。你能夠用 ... 匹配重複的模式。
一個基礎的例子: rule { ($name ...) } => { $name ... }。它匹配全部有括號的語法,而後在輸出中剝去括號
一個重複模式匹配 0 到屢次,因此他不能用來強制一個模式的存在,他會一直匹配,就算匹配的是空
macro basic { rule { { $x (,) ... } } => { wrapped($x (,) ...); } } basic {} // expands to wrapped() basic { x, y, z } // expands to wrapped(x, y, z)
爲了讓重複模式更有用, sweet.js 容許你定製一個分隔符,用來分開模式們。例如, rule { $name (,) ... } => { $name (,) ... } 說的是在匹配之間應有一個逗號,而且這個逗號同重復的 names 一同釋放。你能夠在調用代碼中丟掉這個逗號(只是 $name ...) 那麼他將只釋放 names 的集合,忽略逗號。在 js 中用逗號分隔元素很是廣泛 (想一想 參數 args, 數組元素,等等)
最後,爲了定製一個複雜的模式,你須要用到模式組。由於 ... 只是提早在一個簡單的匹配變量上起做用,當你須要 "組" 一個模式,到一個簡單的元素。你該這麼作: rule { $($name = $init) (,) ... } => { $name ... }。注意這個額外的 $() 他建立了一個模式組。這個宏將匹配像 x=5, y=6, z=7 並釋放 x y z 。你能可選的來用模式組和分隔符,當匹配和釋放代碼的時候,這容許你轉換重複模式到任何其餘想要的重複模式。
但願多玩玩 editor 能多感覺一下。這裏是一些給你瞎搞的宏~~:
macro basic { rule { { $x (,) ... } } => { wrapped($x (,) ...); } } basic {} basic { x, y, z } // wrapped(); wrapped(x, y, z);
let function = macro { rule { $name ($args (,) ...) { $body ... } } => { function $name($args (,) ...) { console.log("called"); $body ... } } } function bar() { var x = 2, y = 5; console.log('hello'); } // function bar() { console.log('called'); var x = 2, y = 5; console.log('hello'); }
let var = macro { rule { $([$name] = $expr:expr) (,) ... } => { $(var $name = $expr[0]) ... } } var [x] = arr, [y] = bar(); // var x = arr[0]; var y = bar()[0];
macro foo { rule { [$([$name ...] -> $init) (,) ...] } => { $($(var $name = $init;) ...) ... } } foo [[x y z] -> 3, [bar baz] -> 10] // var x = 3; var y = 3; var z = 3; var bar = 10; var baz = 10;
全部的這些例子都是故意簡化的來方便簡單的玩玩。var 宏,好比破壞了 var 由於你再也不能正常的用內建的 var 啦。將來的教程中咱們會深刻更多的複雜的宏,將會解釋這類事情。
重複模式是好用的,可是若是你想要更多的控制匹配的模式,通常用遞歸的宏來代替。它讓你作一些了匹配 1 或者更多,捕獲多個不一樣的模式,等等其餘。一樣的,更多的將會在之後的教程中!
==衛生==
這裏咱們不能談衛生,即時他一般不影響你如何寫宏。事實上,衛生的關鍵點是:它都是自動生效的,因此你不須要擔憂名字衝突。
全部在 sweet.js 中的宏都是衛生的,意思是標識符老是引用了正確的東西。若是一個宏建立了一個新的變量(好比用了 var ),他會只在宏的自己中有效。他不會和調用宏的代碼中的另外的其餘同名變量衝突。默認狀況下,你不能引入一個新變量到一個無心識的做用域中。你能作一些工做來故意作到這一點,咱們都把他放到將來的教程中
macro foo { rule { $id = $init } => { var $id = $init } rule { $init } => { var x = $init } } foo 5; var x = 6; foo y = 10; var y = 11; // var x = 5; var x$2 = 6; var y = 10; var y = 11;
上面的例子中,前2個例子建立了2個不一樣的變量,由於宏本身建立了一個 x ,和咱們用 var 產生的是不一樣的。在後面的2個例子中,都引用了一樣的 y ,由於我把他傳給了宏
衛生更名是你看到 foo 更名到了 'foo$1234' 的緣由。 sweet.js 確保每一個變量都引用了正確的東西。他必須重命名全部的變量,爲了這篇文章 http://disnetdev.com/blog/2013/09/27/hygiene-in-sweet.js/ 裏面描述的全部的緣由。不幸的是那意味着全部你的標識符,會變得醜一點,可是若是你傳入 -r 參數 或者 "readable names"選項給 sweet.js 編譯器,他會清理下,而且大部分狀況都會從新變好看點。因爲這僅僅在 es5 代碼中生效,因此還不是默認開啓的。
首篇教程僅需學會如何用在本身的項目上
npm install -g sweet.js 安裝。如今 sjs 命令生效了,運行它用這些參數:
若是你想編譯一個使用宏的 foo.js 而且用上 source map,你須要 sjs -c -o foo.built.js foo.js 你能用一個不一樣的擴展名 例如 .sjs
我推薦使用 https://github.com/evanw/node-source-map-support 這個node模塊,他自動處理錯誤信息,你只須要 require('source-map-support').install() 在文件頂部就能夠啦;
最後,你會想要一個 Grunt 任務,這更容易,甚至自動添加了 source-map 支持(需配置)。
npm install grunt-sweet.js --save-dev 而後寫 Gruntfile.js 模仿個人示例項目 https://gist.github.com/jlongster/8838950
https://github.com/sindresorhus/gulp-sweetjs gulp插件
第一部分教程結束
覆蓋了用 sweet.js 寫宏的基礎,接下來的教程我會更深刻,which I will release in a week or two. Follow my blog to see when that comes out. I am also speaking at MountainWestJS about macros March 17-18th so let me know if you'll be there!
(Thanks to Tim Disney, Nate Faubion, Dave Herman, and other who reviewed this)