これは楽しいPEG.jsでパーサーづくり

javascriptで簡単にパーサーがつくれてしますみたいです。その名もPEG.js

http://pegjs.org/online

ここで実際に使って実行もできてしまいます。

ということでそのサンプルコードをちょっと読んでみました。

/*
 * Simple Arithmetics Grammar
 * ==========================
 *
 * Accepts expressions like "2 * (3 + 4)" and computes their value.
 */

Expression
  = head:Term tail:(_ ("+" / "-") _ Term)* {
      var result = head, i;

      for (i = 0; i < tail.length; i++) {
        if (tail[i][1] === "+") { result += tail[i][3]; }
        if (tail[i][1] === "-") { result -= tail[i][3]; }
      }

      return result;
    }

Term
  = head:Factor tail:(_ ("*" / "/") _ Factor)* {
      var result = head, i;

      for (i = 0; i < tail.length; i++) {
        if (tail[i][1] === "*") { result *= tail[i][3]; }
        if (tail[i][1] === "/") { result /= tail[i][3]; }
      }

      return result;
    }

Factor
  = "(" _ expr:Expression _ ")" { return expr; }
  / Integer

Integer "integer"
  = [0-9]+ { return parseInt(text(), 10); }

_ "whitespace"
  = [ \t\n\r]*

簡単な四則演算のパーサーのようです。

基本は

型の名前 = マッチさせるパターン

です。

一番下から見てみましょう。

 

 

_ "whitespace"
  = [ \t\n\r]*

“_”をスペース文字にマッチさせているのがこの部分です。

=の後はregular expressionが来ます。
これで _ と書けば "スペース \t \n \r が0個以上"にマッチします。

 

Integer "integer"
  = [0-9]+ { return parseInt(text(), 10); }

次は数字とプラス記号をIntegerと定義します。
ここではパターンの後に{…}で実行されるjavascriptを書いています。
その際マッチした文字をtext()で取得しています。

このようにマッチしたらjavascriptを実行できるのは便利ですね。

 

Factor
  = "(" _ expr:Expression _ ")" { return expr; }
  / Integer

次はFactorとして数字のInteger又はカッコに囲まれたExpression(この上で定義されている)にマッチします。
ここではマッチしたExpressionをexprと名付けて{}内のjavascriptでその名前を使っています。
これでカッコで囲まれたExpressionはカッコを取り除かれてかえってきます。

Expression
  = head:Term tail:(_ ("+" / "-") _ Term)* {
      var result = head, i;

      for (i = 0; i < tail.length; i++) {
        if (tail[i][1] === "+") { result += tail[i][3]; }
        if (tail[i][1] === "-") { result -= tail[i][3]; }
      }

      return result;
    }

Term
  = head:Factor tail:(_ ("*" / "/") _ Factor)* {
      var result = head, i;

      for (i = 0; i < tail.length; i++) {
        if (tail[i][1] === "*") { result *= tail[i][3]; }
        if (tail[i][1] === "/") { result /= tail[i][3]; }
      }

      return result;
    }

次はExpressionとTermです。
ここではhead: tail:と書いて最初に…最後に…というパターンを作っています。
まずはTermを見てみましょう。
head:Factor tail:(_ ("*" / "/") _ Factor)*
というパターンです。
最初にFactorがあり最後に "スペース 掛けるか割る記号 スペース Factor が0個以上"ある時にマッチします。
そしてマッチした結果はtailに配列として格納されるので後の{}内でそれにあったjavascriptを実行します。
headには最初に来た数字かカッコ内の計算結果が入っているので

if (tail[i][1] === "*") { result *= tail[i][3]; }


このコードでもしtail配列の1番目が掛ける記号ならtail配列の3番目をheadに掛けることになります。

if (tail[i][1] === "/") { result /= tail[i][3]; }


これは同じく割算をします。

 

最後にExpressionはこの下で定義された全て(数字、カッコ内の計算結果、掛け算)をとって
それを足し算か引き算しその結果を返します。
これで簡単な計算機のパーサーができあがりました。