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コードを埋め込んで強引な処理を記述出来るため、複雑なコードを生み出してしまう可能性も無くはない。
ただしパーサジェネレータが機械的に処理可能なレベルのシンタックスは、手書きでも容易にパーサが作れてしまうかもしれない。