BNFでパーサコンビネータに触れる

この記事は2日遅れで投稿された OUCC Advent Calendar 2016 4日目の記事です.遅くなりすみません…

昨日の担当は@klta6154さんですが霊圧が消えているようです.

www.adventar.org

loquat

ところで,同じくOUCC部員のid:susisuによって作られたパーサコンビネータライブラリ loquat というのがあり,最近はそのソースコードを読んでいたりしていました.他の人のコードを読むのはとても勉強になります.

github.com

loquat自体はとても高機能なライブラリですが,もっと簡単にパーサを作りたい時があるかもしれません.たとえばBNFで定義した文法がそのままパーサになるともっと便利になると思いませんか? そういうわけで下のようなものを作ってみました.

github.com

使い方

インストール

$ npm i lqbnf

実数をパースしたい時はこんな感じで,

const BNF = require('lqbnf');

const parser = BNF(`
  <number>                ::= "-" <non-negative-number> | <non-negative-number>
  <non-negative-number>   ::= <non-negative-integer> "." <digits0> | <non-negative-integer>
  <non-negative-integer>  ::= <digits> | "0"
  <digits>                ::= <digit-without-zero> <digits0> | <digit-without-zero>
  <digits0>               ::= <digit> <digits0> | <digit>
  <digit-without-zero>    ::= "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
  <digit>                 ::= "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "0"
`, 'number');

第1引数にそのままBNFを記述した文字列,第2引数に開始記号の名前を入れるとあら不思議,loquat互換のパーサが出来上がります.

const lq = require('loquat')();

const result = lq.parse(parser, '', '-1230.0456');
if (result.success) {
  const util = require('util');
  console.log(util.inspect(
    result.value,
    { colors: true, depth: undefined }
  ));
}
else {
  console.error(result.error.toString());
}

/*
result.value = [ '-',
  [ [ [ [ '1' ], [ [ '2' ], [ [ '3' ], [ [ '0' ] ] ] ] ] ],
    '.',
    [ [ '0' ], [ [ '4' ], [ [ '5' ], [ [ '6' ] ] ] ] ] ] ]
*/

作っておいてなんですが,現状ではパース結果をただ配列で返すだけなのであんまり実用的ではない気がします,あとは繰り返し記号が使えたり,EBNFに対応したりするともっと良いですね,どなたかPull Requestお待ちしています(投げやり).

明日(今日)の担当は@shinoshinospさんです.