囲碁の棋譜フォーマット 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); }); });