BASIC Parser 0.1

BASIC の構文解析処理 (parsing / syntactic analysis) を作る前段階として、 以前作成したパーサージェネレーターを利用して、BASIC (Beginners' All-purpose Symbolic Instuction Code) の構文解析のクラス BASICParser を生成してみます。以前というのがもう6年も前なので、なるべく小さく始められるよう、使用する範囲を絞ります。

実行結果

以下は今回使用する BASIC の構文です。連載「BASICでコンバータを作る」の最初に紹介した2つのプログラム Q0.BAS と P1-1.BAS に含まれる構文だけを抽出しています。 上記の BASIC の構文の文字列を EBNFParser に渡し、BASICParser のコードを以下の通り生成しました。 字下げは Visual Studio Code 等のツールで簡単にできるので行っていません。

ドキュメント

JsDoc Toolkit で作成したドキュメントはこちら(再掲)。

ソース


basic01.html

このソースコードは Jasmine のサンプル SpecRunner.html から大部分を持ってきました。前半はスタイルシートとスクリプトを読み込んでいる部分です。
<head>
<meta charset="UTF-8">
<title>JavaScriptでプログラミング - BASIC 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/generator03/source01.js"></script>
<script type="text/javascript" src="js/generator03/lex03.js"></script>
<script type="text/javascript" src="js/generator03/generator03.js"></script>

<!-- include spec files here... -->
<script type="text/javascript" src="js/generator03/sourcespec01.js"></script>
<script type="text/javascript" src="js/generator03/lexspec03.js"></script>
<script type="text/javascript" src="js/generator03/generatorspec03.js"></script>

構文の定義を EBNFParser に渡す処理が、HTML ファイルの <head> タグの最後に置いてあります。 これは色々な構文を試すときも js ファイルのほうを変更しなくて済ますためです。

63行以降は Jasmine によるテスト結果を HTML 上で展開するレポーターと呼ばれる部分です。前回から変更ありません。

<script type="text/javascript">
// define BASIC syntax
var gapFree = ["int", "text"];
var basicSyntax = "program = {line}, eof.\n";
basicSyntax += "line = int, statements, [comment], eol.\n";
basicSyntax += "comment = ('REM' | sq), any.\n";
basicSyntax += "statements = {statement, ':'}, (comment | statement).\n";
basicSyntax += "statement = (dim | assign | for | next | print | if | goto | end).\n";
basicSyntax += "dim = 'DIM', array.\n";
basicSyntax += "array = identifier, '(', int, ')'.\n";
basicSyntax += "identifier = upper, {upper | digit}, ['$'].\n";
basicSyntax += "int = digit, {digit}.\n";
basicSyntax += "assign = {variable | array}, '=', expression.\n";
basicSyntax += "variable = identifier.\n";
basicSyntax += "for = 'FOR', variable, '=', expression, 'TO', expression.\n";
basicSyntax += "next = 'NEXT', [variable, {',', variable}].\n";
basicSyntax += "print = 'PRINT', expression, [';'].\n";
basicSyntax += "if = 'IF', expression, 'THEN', (int | statement).\n";
basicSyntax += "goto = 'GOTO', int.\n";
basicSyntax += "end = 'END'.\n";
basicSyntax += "expression = and, {'OR', and}.\n";
basicSyntax += "and = not, {'AND', not}.\n";
basicSyntax += "not = ['NOT'], condition.\n";
basicSyntax += "condition = sum, ('=' | '<>' | '<' | '>' | '<=' | '>='), sum.\n";
basicSyntax += "sum = mod, {('+' | '-'), mod}.\n";
basicSyntax += "mod = div, {'MOD', div}.\n";
basicSyntax += "div = term, {'\\\\', term}.\n";
basicSyntax += "term = minus, {('*' | '/'), minus}.\n";
basicSyntax += "minus = ['-'], power.\n";
basicSyntax += "power = {factor, '^'}, factor.\n";
basicSyntax += "factor = ('(', expression, ')' | varialbe | array | int | string).\n";
basicSyntax += "string = '\"', {any}, '\"'.\n";
var ebnf = new EBNFParser(basicSyntax, gapFree, "BASIC", "0.1");
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>
また、<body> タグの実行結果のところに直接スクリプトを書いてあります。 これは結果に対して SyntaxHighlighter を使用するためです。
<h2>実行結果</h2>
以下は今回使用する BASIC の構文です。連載「BASICでコンバータを作る」の最初に紹介した2つのプログラム Q0.BAS と P1-1.BAS
に含まれる構文だけを抽出しています。
<script type="text/javascript">
  // get element from HTML and set text (code)
  var html = '<pre class="code">\n';
  html += basicSyntax;
  html += '</pre>\n';
  document.writeln(html);
</script>
上記の BASIC の構文の文字列を EBNFParser に渡し、BASICParser のコードを以下の通り生成しました。
字下げは Visual Studio Code 等のツールで簡単にできるので行っていません。
<script type="text/javascript">
  // get element from HTML and set text (code)
  var html = "<pre class=\"brush: js; class-name: 'list'\">\n";
  html += ebnf.syntax();
  html += "</pre>\n";
  document.writeln(html);
</script>

generator03.js

クラス EBNFParser のソースコードです。パーサーを生成するためのツールです。今回 BASIC で使われているシンボルをいくつか増やしました。
/**
 * symbol = '=' | ',' | '.' | '|' | '+' | '-' | '*' | '/' | '\\' | '^' |
 *          ':' | ';' | '[' | ']' | '{' | '}' | '(' | ')' | '<' | '>' | '"' | '$'.
 * @return {String} code generated if matched, null if not matched
 * @since 0.32
 */
EBNFParser.prototype.symbol = function() {
    var match = [];
    var save = this.ptr;
    var n = -1;
    this.sp();
    match[++n] = this.text('=');
    if (!match[n]) {
        n--;
        this.sp();
        match[++n] = this.text(',');
    }
    if (!match[n]) {
        n--;
        this.sp();
        match[++n] = this.text('.');
    }
    if (!match[n]) {
        n--;
        this.sp();
        match[++n] = this.text('|');
    }
    if (!match[n]) {
        n--;
        this.sp();
        match[++n] = this.text('+');
    }
    if (!match[n]) {
        n--;
        this.sp();
        match[++n] = this.text('-');
    }
    if (!match[n]) {
        n--;
        this.sp();
        match[++n] = this.text('*');
    }
    if (!match[n]) {
        n--;
        this.sp();
        match[++n] = this.text('/');
    }
    if (!match[n]) {
        n--;
        this.sp();
        match[++n] = this.text('\\');
    }
    if (!match[n]) {
        n--;
        this.sp();
        match[++n] = this.text('^');
    }
    if (!match[n]) {
        n--;
        this.sp();
        match[++n] = this.text(':');
    }
    if (!match[n]) {
        n--;
        this.sp();
        match[++n] = this.text(';');
    }
    if (!match[n]) {
        n--;
        this.sp();
        match[++n] = this.text('[');
    }
    if (!match[n]) {
        n--;
        this.sp();
        match[++n] = this.text(']');
    }
    if (!match[n]) {
        n--;
        this.sp();
        match[++n] = this.text('{');
    }
    if (!match[n]) {
        n--;
        this.sp();
        match[++n] = this.text('}');
    }
    if (!match[n]) {
        n--;
        this.sp();
        match[++n] = this.text('(');
    }
    if (!match[n]) {
        n--;
        this.sp();
        match[++n] = this.text(')');
    }
    if (!match[n]) {
        n--;
        this.sp();
        match[++n] = this.text('<');
    }
    if (!match[n]) {
        n--;
        this.sp();
        match[++n] = this.text('>');
    }
    if (!match[n]) {
        n--;
        this.sp();
        match[++n] = this.text('"');
    }
    if (!match[n]) {
        n--;
        this.sp();
        match[++n] = this.text('$');
    }
    if (!match[n]) {
        this.ptr = save;
        return null;
    }
    return this.run(n, match, "symbol");
 };
 
変更前のソースはリンク先をご覧下さい。

source01.js

クラス Source のソースコードです。Lex, Parser の対象となるテキストを格納するクラスです。前回から変更ありません。 リンク先をご覧下さい。

lex03.js

クラス Lex のソースコードです。字句解析のためのツールです。前回から変更ありません。 リンク先をご覧下さい。

sourcespec01.js

このソースでは Source のテスト仕様を定義しています。前回から変更ありません。 リンク先をご覧下さい。

lexspec03.js

このソースでは Lex のテスト仕様を定義しています。前回から変更ありません。 リンク先をご覧下さい。

generatorspec03.js

このソースでは Generator のテスト仕様を定義しています。前回から変更ありません。 リンク先をご覧下さい。

テスト結果