JavaScriptで動作するパーサジェネレータPEG.jsを使ってみる
Markdownっぽいデータ記述言語を作成した際に、PEG.jsと呼ばれるJavaScript向けパーサジェネレータを使用した。
PEG.jsは文法定義から実際にパースを行うコードを生成してくれるため、何かと便利なツール。
公式ドキュメント以上の情報が載っている感じではないが、まとめてみる。
インストール
npmを使用。
npm install pegjs
以下のURLにPlaygroundも用意されているためブラウザ上でPEG.jsの動作を確認することも出来るようになっている。
PEG.js – Parser Generator for JavaScript
PEG.jsを用いてパーサを生成する
作成したファイルの拡張子は.pegjsが推奨されている模様。npmでPEG.jsをインストールするとpegjsコマンドが利用出来るようになり、これを用いてパーサを生成する。文法を定義したsample.pegjsに対して以下のような操作を行う。
#パーサの生成 アウトプットはsample.js pegjs sample.pegjs #パーサの生成(+エクスポート形式の指定) pegjs -e PEG sample.pegjs
通常はNode.jsのモジュール(module.export)として生成されるが、-eオプションを用いてエクスポート形式を指定することが出来る。二つ目の例ではsample.js内でPEGというオブジェクトがグローバルスコープに定義される。
PEG.jsで生成されたパーサを使う
Node.jsのモジュールとして使用する場合を例にとる。非常に簡単。
var PEG = require('sample.js'); PEG.parse('1 + 1');
PEG.jsの記述例
Documentation » PEG.js – Parser Generator for JavaScript
上記のドキュメントよりそのまま抜粋。整数の加算と乗算を行う。
非終端記号である加算(additive)や乗算(multiplicative)、終端記号integerなどを一つ一つ定義していく。
非終端記号は=の右辺に生成規則を記述する。右辺の終端記号/非終端記号には名前を付けることが出来、これを{}ブロック内のJavaScriptコードから変数として参照することが出来る。
start = additive additive = left:multiplicative "+" right:additive { return left + right; } / multiplicative multiplicative = left:primary "*" right:multiplicative { return left * right; } / primary primary = integer / "(" additive:additive ")" { return additive; } integer "integer" = digits:[0-9]+ { return parseInt(digits.join(""), 10); }
より詳細に記述する
上の例では整数リテラルであるintegerは整数であれば全て受理してしまうが、3桁までの整数に限定したい場合の一例。
${}のブロック内で真偽値を返す任意のJavaScriptコードを記述し、返り値がtrueと評価される場合のみ以降のパースに進めさせる処理が記述出来る。ブロックの左側で定義された変数はブロック内で利用可能なため、digits(1文字づつ配列に格納されている)の長さが3以下か判定するだけでよい。
integer "integer" = digits:[0-9]+ &{return digits.length <= 3} { return parseInt(digits.join(""), 10); }
ブロックのネストを始めとする入れ子構造を処理する際に、自分はこのブロックを使って無理やりカウンタを増減させていた。
block "block" = &{counter++; return true;} symbol:nonterminal_symbol &{counter--; return true;}
まとめ
自分が求めていた、Markdown記法+αレベルの文法定義は十分に行えた。
任意のJavaScriptコードを埋め込んで強引な処理を記述出来るため、複雑なコードを生み出してしまう可能性も無くはない。
ただしパーサジェネレータが機械的に処理可能なレベルのシンタックスは、手書きでも容易にパーサが作れてしまうかもしれない。