正しく動く 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>