囲碁の棋譜フォーマット SGF の構文解析処理 (parsing / syntactic analysis) を作る前段階として、 前回作成した字句解析 (lexical analysis) のクラス Lex を利用した、簡単な数式の構文解析のクラス Parser を作成し、 単体テストのフレームワーク Jasmine によりテストしてみました。
実数の四則演算を EBNF (Extended Backus–Naur Form) の構文で書き表すと以下のようになります。 EBNF では、= は定義、, は連結、| は選択、. は終端、() はグループ化、[] は省略可能、{} は0回以上の繰り返しを表します。
expression = term, {('+' | '-'), term}.
term = factor, {('*' | '/'), factor}.
factor = ('(', expression, ')') | real | integer.
real = ((integer, '.') | ('.', digit)), {digit}.
integer = ['-'], digit, {digit}.
digit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'.
今回はこの構文をもとに構文解析プログラム (parser) を作り簡単なキーボード入力式の電卓を作成しました。
<head> <meta charset="UTF-8"> <title>JavaScriptでプログラミング - Parser 0.1</title> <link type="text/css" rel="stylesheet" href="lib/jasmine-1.3.1/jasmine.css"> <link type="text/css" rel="stylesheet" href="css/spec.css"> <link type="text/css" rel="stylesheet" href="css/shCoreEclipse.css"> <link type="text/css" rel="stylesheet" href="css/shThemeEclipse.css"> <script type="text/javascript" src="js/XRegExp.js"></script> <script type="text/javascript" src="js/shCore.js"></script> <script type="text/javascript" src="js/shBrushJScript.js"></script> <script type="text/javascript" src="js/shBrushXml.js"></script> <script type="text/javascript" src="js/shBrushCss.js"></script> <script type="text/javascript" src="lib/jasmine-1.3.1/jasmine.js"></script> <script type="text/javascript" src="lib/jasmine-1.3.1/jasmine-html.js"></script> <!-- include source files here... --> <script type="text/javascript" src="js/parser01/source01.js"></script> <script type="text/javascript" src="js/parser01/lex02.js"></script> <script type="text/javascript" src="js/parser01/parser01.js"></script> <!-- include spec files here... --> <script type="text/javascript" src="js/parser01/lexspec02.js"></script> <script type="text/javascript" src="js/parser01/parserspec01.js"></script>後半は Jasmine によるテスト結果を HTML 上で展開するレポーターと呼ばれる部分です。前回から変更ありません。
<script type="text/javascript">
SyntaxHighlighter.all();
(function() {
var jasmineEnv = jasmine.getEnv();
jasmineEnv.updateInterval = 1000;
var htmlReporter = new jasmine.HtmlReporter();
jasmineEnv.addReporter(htmlReporter);
jasmineEnv.specFilter = function(spec) {
return htmlReporter.specFilter(spec);
};
var currentWindowOnload = window.onload;
window.onload = function() {
if (currentWindowOnload) {
currentWindowOnload();
}
execJasmine();
};
function execJasmine() {
jasmineEnv.execute();
}
})();
</script>
describe("Lex 仕様 0.2", function() {
var exp = "1+2=";
var la;
beforeEach(function() {
la = new Lex(exp); // Lexical Analiser
});
it("eod() は false を返す", function() {
expect(la.eod()).toBeFalsy();
});
it("digit() は '1' を返す", function() {
expect(la.digit()).toEqual('1');
});
it("upper() は null を返す", function() {
expect(la.upper()).toBe(null);
});
it("lower() は null を返す", function() {
expect(la.lower()).toBe(null);
});
it("sp() は null を返す", function() {
expect(la.sp()).toBeFalsy();
});
it("sq() は null を返す", function() {
expect(la.sq()).toBeFalsy();
});
it("ch('1') は '1' を返す", function() {
expect(la.ch('1')).toEqual('1');
});
describe("3文字読んだ時点で、", function() {
beforeEach(function() {
la.rewind();
la.digit();
la.ch('+');
la.digit();
});
it("eod() は false を返す", function() {
expect(la.eod()).toBe(false);
});
it("digit() は null を返す", function() {
expect(la.digit()).toBe(null);
});
it("upper() は null を返す", function() {
expect(la.upper()).toBe(null);
});
it("lower() は null を返す", function() {
expect(la.lower()).toEqual(null);
});
it("ch('=') は '=' を返す", function() {
expect(la.ch('=')).toBe('=');
});
});
describe("4文字読んだ時点で、", function() {
beforeEach(function() {
la.rewind();
la.digit();
la.ch('+');
la.digit();
la.ch('=');
});
it("eod() は true を返す", function() {
expect(la.eod()).toBeTruthy();
});
});
});
describe("Parser 仕様 0.1", function() {
it("Expression(\"2-1\").expression() は 1 を返す", function() {
var e = new Expression("2-1");
expect(e.expression()).toEqual(1);
});
it("Expression(\"1.5+2.4\").expression() は 3.9 を返す", function() {
var e = new Expression("1.5+2.4");
expect(e.expression()).toEqual(3.9);
});
it("Expression(\"123*56\").expression() は 6888 を返す", function() {
var e = new Expression("123*56");
expect(e.expression()).toEqual(6888);
});
it("Expression(\"91/2\").expression() は 45.5 を返す", function() {
var e = new Expression("91/2");
expect(e.expression()).toEqual(45.5);
});
it("Expression(\"2.-1\").expression() は 1 を返す", function() {
var e = new Expression("2.-1");
expect(e.expression()).toEqual(1);
});
it("Expression(\"1+1\").expression() は 2 を返す", function() {
var e = new Expression("1+1");
expect(e.expression()).toEqual(2);
});
it("Expression(\"3-1+2\").expression() は 4 を返す", function() {
var e = new Expression("3-1+2");
expect(e.expression()).toEqual(4);
});
it("Expression(\"(3-1+2\").expression() は null を返す", function() {
var e = new Expression("(3-1+2");
expect(e.expression()).toEqual(null);
});
it("Expression(\"3-1+2\").term() は 3 を返す", function() {
var e = new Expression("3-1+2");
expect(e.term()).toEqual(3);
});
it("Expression(\"3*1/2\").term() は 1.5 を返す", function() {
var e = new Expression("3*1/2");
expect(e.term()).toEqual(1.5);
});
it("Expression(\"(3-1+2\").term() は null を返す", function() {
var e = new Expression("(3-1+2");
expect(e.term()).toEqual(null);
});
it("Expression(\"(2-1)*2\").factor() は 1 を返す", function() {
var e = new Expression("(2-1)*2");
expect(e.factor()).toEqual(1);
});
it("Expression(\"(2.-1)*2\").factor() は 1 を返す", function() {
var e = new Expression("(2.-1)*2");
expect(e.factor()).toEqual(1);
});
it("Expression(\"(-2.-1)*2\").factor() は -3 を返す", function() {
var e = new Expression("(-2.-1)*2");
expect(e.factor()).toEqual(-3);
});
it("Expression(\"(-2.-1*2\").factor() は null を返す", function() {
var e = new Expression("(-2.-1*2");
expect(e.factor()).toEqual(null);
});
it("Expression(\"3.5-1+2\").real() は 3.5 を返す", function() {
var e = new Expression("3.5-1+2");
expect(e.real()).toEqual(3.5);
});
it("Expression(\".5-1+2\").real() は .5 を返す", function() {
var e = new Expression(".5-1+2");
expect(e.real()).toEqual(.5);
});
it("Expression(\"-2.-1+2\").real() は -2. を返す", function() {
var e = new Expression("-2.-1+2");
expect(e.real()).toEqual(-2.);
});
it("Expression(\"2.\").real() は 2. を返す", function() {
var e = new Expression("2.");
expect(e.real()).toEqual(2.);
});
it("Expression(\"..3-1+2\").real() は null を返す", function() {
var e = new Expression("..3-1+2");
expect(e.real()).toEqual(null);
});
it("Expression(\"1.5+2\").integer() は 1 を返す", function() {
var e = new Expression("1.5+2");
expect(e.integer()).toEqual(1);
});
it("Expression(\".5+2\").integer() は null を返す", function() {
var e = new Expression(".5+2");
expect(e.integer()).toEqual(null);
});
});