[プログラミング言語の作り方] コメント、数値、複数引数(コンマ)対応の作り方

言語仕様を拡張する
– 文字列
– 整数123, 実数123.4
– print関数でカンマ区切りで複数の引数指定
– 複数文は、セミコロンで区切る
– // から行末まではコメント

### source.3

print("引数1個目","引数1個目","引数1個目");
print("整数=",1234);
print("実数=",1234.5);

### lexer.js
\/\/.*$ =>コメント削除、mオプションが必要
\d+\.\d+ =>実数
\d+ => 整数
“.*?” => 文字列
\w+ => print
\s => 空白、改行
. => セミコロン、カッコ、その他

module.exports = function(source){
    var tokens = source.split(/\/\/.*$|(\d+\.\d+|\d+|".*?"|\w+)|\s|(.)/m);

    tokens = tokens.filter(a=>a);

    return tokens;
}

### parser.js

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

var tokens;

function parser(t){
    tokens = t;
    var ast = semi();
    if(tokens.length>0){
        show("ast=", ast);
        show("処理後tokens =", tokens);
        error("tokensが余っています。");
    }
    return ast;
}

function value(){
    if(tokens.length == 0) return;
    return tokens.shift();
}

function funccall(){
    var left = value();

    var op;
    while(op = accept(tokens, "(")){
        var right = comma();
        op += expect(tokens, ")");

        left = {left, op, right};
    }
    return left;
}

function comma(){
    var left = funccall();

    var op;
    while(op = accept(tokens,",")){
        var right = funccall();
        left = {left, op, right};
    }
    return left;
}

function semi(){
    var left = comma();

    var op;
    while(op = accept(tokens, ";")){
        var right = funccall();
        left = {left, op, right};
    }
    return left;
}

### run.js
1234, 1234.5は文字列になっているので、1を掛けて数値化
tree形式から配列化している
flatに[left, right]に戻す

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

module.exports = run;

function run(a){
    if(!a) return;
    if(!a.op){
        if(a[0] == '"') return a.substr(1, a.length-2);
        if(/\d/.test(a[0])) return 1*a;
        return a;
    } else if (a.op==";"){
        run(a.left);
        run(a.right);
    } else if (a.op==","){
        return [run(a.left), run(a.right)].flat();
    } else if(a.op == "()") {
        var func = run(a.left);
        if(func == "print"){
            var msg = [run(a.right)].flat().join("");
            console.log(msg);
        } else {
            error("未実装の関数呼び出し func=", func);
        } 
    } else {
        error("未実装の演算子 op=", a.op)
    }
} 

opに追加する場合は、字句解析の正規表現と実行時の処理を追加する
カンマがあった場合は、left, op, rightで入れ子にするが、なかった場合は、そのまま処理を続ける

$ node interpretor.js
token =[
‘print’, ‘(‘, ‘”引数1個目”‘,
‘,’, ‘”引数2個目”‘, ‘,’,
‘”引数3個目”‘, ‘)’, ‘;’,
‘print’, ‘(‘, ‘”引数4″‘,
‘)’, ‘;’, ‘print’,
‘(‘, ‘”整数=”‘, ‘,’,
‘1234’, ‘)’, ‘;’,
‘print’, ‘(‘, ‘”実数=”‘,
‘,’, ‘1234.5’, ‘)’,
‘;’
]
ast={
left: {
left: {
left: {
left: {
left: ‘print’,
op: ‘()’,
right: {
left: { left: ‘”引数1個目”‘, op: ‘,’, right: ‘”引数2個目”‘ },
op: ‘,’,
right: ‘”引数3個目”‘
}
},
op: ‘;’,
right: { left: ‘print’, op: ‘()’, right: ‘”引数4″‘ }
},
op: ‘;’,
right: {
left: ‘print’,
op: ‘()’,
right: { left: ‘”整数=”‘, op: ‘,’, right: ‘1234’ }
}
},
op: ‘;’,
right: {
left: ‘print’,
op: ‘()’,
right: { left: ‘”実数=”‘, op: ‘,’, right: ‘1234.5’ }
}
},
op: ‘;’,
right: undefined
}
引数1個目引数2個目引数3個目
引数4
整数=1234
実数=1234.5