Sweet.js:JavaScriptシンタックス拡張

JavaScript用の衛生マクロを実装するコンパイラであるSweet.jsを見てみましょう。



簡単に機能します-構文ツリーを検索するためのテンプレートのセットを定義します。 一致する場合、マクロは必要な木片を取得し、マクロの本体がこの木片の変換方法を決定します。 その後、結果がツリーに埋め込まれ、その場所から手順が続行されます。



Sweet.jsは、ほとんどトークンレベルで、最小限の構造を持つ独自の構文ツリー形式で動作します。 一方で、マクロのかなりエキゾチックな構文を定義することが可能になり、一方で、あたかも標準のAST JavaScriptで定義されているかのように、マクロの記述が少し複雑になります。







最も単純な例から始めましょうが、最初にSweet.jsをインストールする必要があります。



npm install --global sweet.js
      
      







その後、 sjs



ユーティリティが利用可能になります。 2つの変数の値を交換するマクロを作成して、 swap.sjs



ファイルに次のコードをswap.sjs



ます。



 macro swap { rule { $x , $y } => { var tmp = $x; $x = $y; $y = tmp } } var x = 11; var y = 12; swap x, y; swap y, x;
      
      







ここで、ES5互換のJavaScriptコードを取得するには、これをsjs -r ./swap.sjs



に「フィード」するだけです。



 var x = 11; var y = 12; var tmp = x; x = y; y = tmp; var tmp$2 = y; y = x; x = tmp$2;
      
      







注意が必要な点は、Sweet.jsがマクロを展開するときに変数名を生成し、名前の競合の可能性を排除することです。 これは、Sweet.jsが衛生マクロを実装することを意味します。



便利なものを書きましょう。 BDDスタイルでテストを作成するための一連のマクロはどうですか。 最も単純なものから始めましょう。



 let describe = macro { rule { $name:lit { $body ... } } => { describe($name, function () { $body ... }); } } let it = macro { rule { $name:lit { $body ... } } => { it($name, function () { $body ... }); } } describe "My functionality" { it "works!" { } }
      
      







macro name



形式とは異なり、 let name = macro



を使用let name = macro



-これは、無限再帰を除外するために行われます。 describe



およびit



マクロは、マクロ自体の名前と一致する名前を持つトークンのセットを返すため、Sweet.jsは、スタックがなくなるまで対応するマクロを何度も適用しようとします。 let



フォームは、マクロによって返される構文内のマクロ名のバインダーを作成しないため、これを回避するのに役立ちます。



私たちが得たものを見てみましょう

 describe('My functionality', function () { it('works!', function () { }); });
      
      







これは、最初に書いたswap



マクロよりも既に便利です。これにより、コードの記述を節約し、主題領域により近い構文構造を使用できます。



テストを書くためにマクロを使って他に何ができるか見てみましょう。 アサーションを記述するための一連のマクロはどうですか? マクロはコード自体の構造にアクセスできるため、これを使用して、ステートメントの失敗に関する情報メッセージを含むステートメントを作成できます。 同時に、Sweet.jsを使用して中置マクロを作成する方法を見てみましょう。



どのように見えるのでしょうか? 次の構文をお勧めします。



 2 + 2 should == 4 "aabbcc" should contain "bb" [1, 2] should be truthy xy() should throw
      
      







同時に、失敗したステートメントで、 undefined has no method x



スタイルで現在の変数の値を表示するだけでundefined has no method x



、これにつながったコードを出力する有益なエラーメッセージを見たいと思います。 たとえば、 2 + 2 should == 5



場合、エラーメッセージ2 + 2 should be equal to 5



ます。



まず、JavaScript式を受け取り、この式のコード行を生成するマクロを作成します。「構文解析のように、まったく反対です。」 これは、有益なエラーメッセージを生成するために必要になります。



 macro fmt { case { _ ( $val:expr ) } => { function fmt(v) { return v.map(function(x){ return x.token.inner ? x.token.value[0] + fmt(x.token.inner) + x.token.value[1] : x.token.value; }).join(''); } return [makeValue('`' + fmt(#{$val}) + '`', #{here})]; } }
      
      







前の例とは異なり、このマクロはケースマクロです。 前に使用したルールマクロとは異なり、ケースマクロを使用すると、JavaScriptのすべての機能を使用して構文変換を定義できます。



このマクロの機能については詳しく説明しません。 しかし、スキームはこれです-構文ツリーをバイパスし、そこからコードの行を生成する関数fmt



を定義します。 次に、1つの行ノードで構成される別の構文ツリーを構築し、マクロの結果として返します。



 fmt(1 + 1) // "1+1" fmt(xy(1, 2)) // "xy(1,2)"
      
      







ご覧のとおり、文字列がスペースなしで取得されることを除いて、すべてがバングで機能します。 fmt



マクロの最適なバージョンの作成は、読者への課題として残ります。



次に、ステートメントの構文の直接定義に戻ります。 ステートメント自体に標準Node.jsライブラリのassert



モジュールを使用し、このモジュールから関数呼び出しにコンパイルされるマクロを単純に定義します。



 var assert = require('assert'); macro should { rule infix { $lhs:expr | == $rhs:expr } => { assert.deepEqual( $lhs, $rhs, fmt($lhs) + " should be equal to " + fmt($rhs)); } rule infix { $lhs:expr | be truthy } => { assert.ok( $lhs, fmt($lhs) + " should be truthy"); } rule infix { $lhs:expr | contain $rhs } => { assert.ok( $lhs.indexOf($rhs) > -1, fmt($lhs) + " should contain " + fmt($rhs)); } rule infix { $lhs:expr | throw } => { assert.throws( function() { $lhs }, Error, fmt($lhs) + " should throw"); } }
      
      







rule infix



構文を使用して、中置規則を定義しました。シンボル|



テンプレート内のは、マクロ名シンボルの場所を示しています。この場合should



ます。



一連のクレーム



 2 + 2 should == 4 "aabbcc" should contain "bb" [1, 2] should be truthy xy() should throw
      
      







次のES5有効なコードで明らかにされます



 var assert = require('assert'); assert.deepEqual(2 + 2, 4, '`2+2`' + ' should be equal to ' + '`4`'); assert.ok('aabbcc'.indexOf('bb') > -1, '`aabbcc`' + ' should contain ' + '`bb`'); assert.ok([ 1, 2 ], '`[1,2]`' + ' should be truthy'); assert.throws(function () { xy(); }, Error, '`xy()`' + ' should throw');
      
      







タスクが完了しました! これで、タスク用のマクロの作成を開始したり、一部のライブラリまたはフレームワーク用に独自の構文を定義したりできます。



この記事で定義したすべてのマクロ(およびそれ以上)は、npmおよびgithubで利用できます。







それらを使用するには、まずnpmから必要なパッケージをインストールする必要があります。



 % npm install --global mocha sweet.js % npm install sweet-bdd sweet-assertions
      
      







そして、コードをコンパイルしてテストします



 describe "additions" { it "works" { 2 + 2 should == 4 } }
      
      







次のコマンドを使用する



 % sjs -m sweet-bdd -m sweet-assertions ./specs.sjs > specs.js % mocha specs.js
      
      







マクロを備えた他のライブラリもnpmで利用できます。 JavaScriptのパターンとの比較(パターンマッチング)を実装するsparklerを見てみることをお勧めします。



 function myPatterns { // Match literals case 42 => 'The meaning of life' // Tag checking for JS types using Object::toString case a @ String => 'Hello ' + a // Array destructuring case [...front, back] => back.concat(front) // Object destructuring case { foo: 'bar', x, 'y' } => x // Custom extractors case Email{ user, domain: 'foo.com' } => user // Rest arguments case (a, b, ...rest) => rest // Rest patterns (mapping a pattern over many values) case [...{ x, y }] => _.zip(x, y) // Guards case x @ Number if x > 10 => x }
      
      







面白かったと思う。 すべてのコメントについては、コメント欄でお願いします



更新 Sweet.jsにはソースマップを生成する機能があることを忘れていたので、(少なくともブラウザーでは)デバッグに問題はないはずです。



All Articles