HaskellのMonadをJavaScriptで実装するとしたら
論理面も怪しいし、利用してるAPIも適当なんでアレで動くものじゃないですが、大枠だけ書いてみるテスト。
参考はParsecですが、状態管理はだいぶ省略してます(特にエラー情報)。
- http://www.cs.uu.nl/people/daan/download/parsec/parsec.html
- http://www.lab2.kuis.kyoto-u.ac.jp/~hanatani/tmp/Parsec.html
あと以下のPPTは、関数型なパーザーがどういう仕組みかわかりやすいかもしれない
- Programming in Haskellの8章: http://www.cs.nott.ac.uk/~gmh/chapter8.ppt
(function (){ var PState = { source: ""; index: 0; result: null; }; var Parser = { parse: function (pstate) { return pstate; } }; var Bind = function (src, dst) { var o = {}; o.parse = function (pstate) { var nstate = src.parse(pstate); if (nstate == null) return null; // fail; return dst(nstate.result).parse(nstate); }; return o; }; var Return = function (value) { var o = {}; o.parse = function (pstate) { var nstate = pstate.clone(); // TBD nstate.result = value; return nstate; }; return o; }; var Fail = function () { var o = {}; o.parse = function (pstate) { return null; }; return o; }; var Char = function (ch) { var o = {}; o.parse = function (pstate) { if (pstate.source[pstate.index] == ch) { var nstate = pstate.clone(); nstate.index = pstate.index + 1; nstate.result = ch; return nstate; } else { return null; } }; return o; }; var Many = function (parser) { var o = {}; o.parse = function (pstate) { var current = pstate; var result = []; while (true) { var next = parser.parse(current); if (next != null) { result.push(next.result); current = next; } else { // fail var nstate = current.clone(); nstate.result = result; return nstate; } } }; return o; }; var OrElse = function (p1, p2) { var o = {}; o.parse = function (pstate) { var p1state = p1.parse(pstate); if (p1state == null) return p2.parse(pstate); else return p1state; }; return o; }; // ex abcParser = Bind(Char('a'), function (a) { return Bind(Char('b'), function (b) { return Bind(Char('c'), function (c) { return Return(a + b + c); }); }); }); print(abcParser.parse("abc").result); // "abc" print(abcParser.parse("abcd").result); // "abc" print(abcParser.parse("abd")); // null print(abcParser.parse("ab")); // null })();
でかいw。
PStateってのは状態です。ここには適当に必要そうなものを入れる。ただし、引数や戻り値は絶対に上書きしてはいけません(受け渡しの前にコピーすれば、気にしなくていいけど)。
ParserってのがMonad型の例です。メソッドは、実際に起動するparseを用意。そしてMonadの基本のBind、Return、Failを用意。各種parserはparseメソッドを上書きしていきます。終端パーザーとしてChar、組合せパーザーとしてManyとOrElseを用意。
仕組み的には、
- 関数呼び出しではParserオブジェクト(parseメソッドを持ったオブジェクト)を返す。
- そしてparseメソッドで初めて計算する。parseメソッドは組み立て時には(普通は)呼ばず、組み立て後に呼ぶ。
- パーザー組み立てでは、Bindなどを使って組み立てていく
というものになる。
ただ、このままだと組合せパーザーを書くのも正直一苦労するけど。