プログラミング言語の作り方

javascript/C言語/アセンブラを用い、 字句解析、構文解析、インタプリタ、コンパイラのプログラムをスクラッチから作りながら、 「プログラミング言語の作り方」を解説する。

複数文対応
javascript版プログラミング言語の作り方(インタプリタ開発)

print文が何回呼ばれても良いように、複数文対応をしよう。

ページメニュー

現時点の言語仕様

複数文に対応するため、セミコロンで区切るという言語仕様を追加する。

自作言語で記述されたソース

ソースコードには、セミコロンで区切られたprint文を3つを並べよう。

$ cat source.3
print("hello world");
print("hello world2");
print("hello world3");

インタプリタのファイル構成

複数文対応する際に、変更となる箇所は、lexer.js、parser.js、run.jsだ。

lexer.jsの実装

字句解析lexerで指定している正規表現に、セミコロンを追加する。

$ cat lexer.js
//------字句解析------//
module.exports = function(source){
    //正規表現を使い、"文字列" or print or セミコロン or 改行で分割。
    //丸カッコで囲まれると残り、囲まれていないと捨てられる
    var tokens = source.split(/(".*"|print|;)|\n/);

    //splitの仕様上、undefinedや''などが残るので、不要なものは捨てる
    tokens = tokens.filter(a=>a);

    return tokens;
}

parser.jsの実装

構文解析parserでは、関数semiを追加した。

文法規則にそって、semiでは文と文の間にはセミコロンがあり、 何個でも連続しても良いようになっている。

ポイントは、semi関数の最後に新しく作ったノードをleftに代入する部分だ。 こうすることで、while文がループする度に、tree階層を深くすることができる。

$ cat parser.js
module.exports = parser;
var {expect,accept,show,error}  = require("./utils.js");
var tokens;

//構文解析開始
function parser(t){
    tokens = t;
    return semi();
}

//セミコロン
function semi(){
    //print文
    var left = callprint();

    //セミコロンがきたら次のprint文があるかも
    var op;
    while(op = accept(tokens,";")){
        //次のprint文
        var right = callprint();
        (@//新しいノードを作成し、次のleftに入れることで、階層を作っていく
        left = {left,op,right};@)
    }
    return left;
}

//print関数呼び出しの構文解析
function callprint(){
    if(tokens.length==0)return;

    //関数名がprintであること
    var left = expect(tokens,"print");

    //関数呼び出しの丸カッコであること
    var op = expect(tokens,"(");

    //文字列を取得
    var right = tokens.shift();

    //閉じカッコであること
    op += expect(tokens,")");

    //新しいノードを作成
    return {left, op, right};
}

run.jsの実装

セミコロンだと、left/rightを実行するだけだ。

 cat run.js
module.exports = run;

var {error}  = require("./utils.js");

//astの階層をたどりながら実行
function run(a){
    if(!a) return;
    if(!a.op){
        //文字列ならダブルクォーテーションを取り除く
        if(a[0] == '"') return a.substr(1,a.length-2);
        //それ以外ならそのまま返す
        return a;
    }else if(a.op == ";"){
        (@//セミコロンだとleft/rightを実行するだけ
        run(a.left) ;
        run(a.right);@)
    }else if(a.op == "()"){
        //leftに関数名
        var func = run(a.left);
        if( func == "print"){
            //rightに表示する文字列
            var msg = run(a.right)
            //表示=実行
            console.log(msg);
        }else{
            error("未実装の関数呼び出し func=",func);
        }
    }else{
        error("未実装の演算子 op=",a.op);
    }
}

インタプリタの実行

interpretor.jsに実行権限を付けて、実行する。

まず、tokensとastが意図どおりに分割されているか、念のため表示しているので、確認しよう。

特に、astの階層の違いによって、処理の順番が明確になっていることに注意しよう。

leftの方を、先に深く深くたどり、行き詰ると、そのノードのleft/rightの順で処理する。 次は、1つ上にもどる、という順だ。

このASTの順番通りに、「hello world」、「hello world2」、「hello world3」と表示されている。

$ ./interpretor.js
tokens =[
  'print',          '(',
  '"hello world"',  ')',
  ';',              'print',
  '(',              '"hello world2"',
  ')',              ';',
  'print',          '(',
  '"hello world3"', ')',
  ';'
]
ast={
  left: {
    left: {
      left: { left: 'print', op: '()', right: '"hello world"' },
      op: ';',
      right: { left: 'print', op: '()', right: '"hello world2"' }
    },
    op: ';',
    right: { left: 'print', op: '()', right: '"hello world3"' }
  },
  op: ';',
  right: undefined
}
hello world
hello world2
hello world3

複数文対応のhello worldであっても、とても簡単だ。


このページの目次へ戻るサイトの最上位へ戻る