正しく動く JavaScript版 Parser
id:bellbind:20060529:1148843131 のコード設計で正しい文字列APIを使うようにして、さらに id:bellbind:20060530:1148941763 の識別子と対応付けたバージョン
var PState = function (code, pos, result) { this.codeField = code; this.posField = pos; this.resultField = result; return this; }; var Parser = function (parse) { this.parseMethod = parse; return this; }; var parserBind = function (src, dst) { var newParse = function (inCode, inPos) { var newState = src.parseMethod(inCode, inPos); if (newState == null) { return null; } // fail; var newCode = newState.codeField; var newPos = newState.posField; return dst(newState.resultField).parseMethod(newCode, newPos); }; return new Parser(newParse); }; var parserNext = function (src, dst) { var newParse = function (inCode, inPos) { var newState = src.parseMethod(inCode, inPos); if (newState == null) { return null; } // fail; var newCode = newState.codeField; var newPos = newState.posField; return dst.parseMethod(newCode, newPos); }; return new Parser(newParse); }; var parserReturn = function (resultValue) { var newParse = function (inCode, inPos) { var nstate = new PState(inCode, inPos, resultValue); return nstate; }; return new Parser(newParse); }; var parserFail = function (error) { var newParse = function (inCode, inPos) { return null; }; return new Parser(newParse); }; var charParser = function (ch) { var newParse = function (inCode, inPos) { if (inPos < inCode.length && inCode.substr(inPos, 1) == ch) { var nstate = new PState(inCode, inPos + 1, ch); return nstate; } else { return null; } }; return new Parser(newParse); }; var manyParser = function (parser) { var newParse = function (inCode, inPos) { var curCode = inCode; var curPos = inPos; var results = []; while (true) { var next = parser.parseMethod(curCode, curPos); if (next != null) { results.push(next.resultField); curCode = next.codeField; curPos = next.posField; } else { // fail var newState = new PState(curCode, curPos, results); return newState; } } }; return new Parser(newParse); }; var orElseParser = function (parser1, parser2) { var newParse = function (inCode, inPos) { var newState = parser1.parseMethod(inCode, inPos); if (newState != null) { return newState; } return parser2.parseMethod(inCode, inPos); }; return new Parser(newParse); }; var Bind = parserBind; var Next = parserNext; var Return = parserReturn; var Fail = parserFail; var stringParser = function (text) { if (text == "") return Return(""); return Bind(charParser(text.substr(0, 1)), function (ch) { return Bind(stringParser(text.substr(1)), function (results) { return Return(ch + results); }); }); }; // ex var abcOrDefParser = manyParser(orElseParser(stringParser("abc"), stringParser("def"))); var parseCode = function (parser, code) { return parser.parseMethod(code, 0); }; /* print(parseCode(abcOrDefParser, "abcd").resultField); // ["abc"] print(parseCode(abcOrDefParser, "abcdefabcd").resultField); // ["abc", "def", "abc"] print(parseCode(abcOrDefParser, "abd").resultField); // [] */
manyParserはHaskell版と違い、再帰ではなくループを使って記述してます(当然再帰で書くことも可能)。多少、短くなってますね。これはFirefoxで動作させて確認しています。
問題はJavaScriptでは文字列は配列じゃないので、manyParser(charParser("a"))では一文字づつ入った配列が返ることか。あとはJavaScriptランタイムの実装しだいで再帰でのスタックオーバーフローがでる可能性があることくらいか。
以下は、一応、チェックで使ったHTMLソース(上記パーザーソースはparser.jsとしてます):
<html> <head> <script type="text/javascript" src="parser.js"></script> <script type="text/javascript"> //<!-- var print = function(obj) { var output = document.getElementById("output"); output.innerHTML = output.innerHTML + "<br />" + obj; }; var runCode = function () { var code = document.forms["codeForm"].codeArea.value; eval(code); }; var clearOutput = function () { var output = document.getElementById("output"); output.innerHTML = ""; }; var load = function () { document.forms["codeForm"].runButton.onclick = runCode; document.forms["codeForm"].clearButton.onclick = clearOutput; }; //--> </script> </head> <body onload="load()"> <script type="text/javascript"> document.write("Write JavaScript code here:"); </script> <form id="codeForm" name="codeForm"> <div> <textarea id="codeArea" name="codeArea" rows="20" cols="80"></textarea> </div> <div> <input type="button" id="runButton" name="runButton" value="run"/> ↓ <input type="button" id="clearButton" name="clearButton" value="clear output"/> </div> </form> <div id="output"></div> </body> </html>