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

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

コメント、数値、複数引数対応
javascript版プログラミング言語の作り方(インタプリタ開発)

コメント、数値、複数引数対応をする。

ページメニュー

現時点の言語仕様

print文の複数引数対応を行うが、ついでなので、 文字列だけでなく、数字(整数/実数)も導入し、表示できるようにする。

さらに、コメントも導入する。

これらの言語仕様は以下だ。

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

$ cat source.3
//引数が3つ
print("引数1個目","引数2個目","引数3個目");
//整数
print("整数=",1234);
//実数
print("実数=",1234.5);

lexer.jsの実装

字句解析lexerの正規表現を追加する。

コメント削除、実数、整数、文字列(最短マッチ)、識別子(関数名や変数名など)、空白/改行で分割。 その他各種記号は1文字で分割する。

ある程度、コメントに記載したが、正規表現の詳細については、検索してほしい。

$ cat lexer.js
//------字句解析------//
module.exports=function(source){
    //正規表現でsplit(丸カッコで囲まれると残り、囲まれていないと捨てられる)
    //コメント削除→\/\/.*$   ($を使うにはmオプションが必要)
    //実数\d+\.\d+
    //整数\d+
    //"文字列"→".*?" 最短マッチ
    //print→\w+
    //空白と改行→\s
    //セミコロンやカッコなどその他→.
    var tokens = source.split((@/\/\/.*$|(\d+\.\d+|\d+|".*?"|\w+)|\s|(.)/m@));

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

    return tokens;
}

parser.jsの実装

複数引数に対応するには、カンマ区切りを解析する、comma関数を追加した。 セミコロンsemiと全く同じ構造だ。

$ cat parser.js
//---------------構文解析---------------//

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

//その他は値としてそのまま返す
function value(){
    if(tokens.length ==0) return;
    //そのまま返す
    return tokens.shift();
}

//関数呼び出し
function funccall(){
    //関数名を取得
    var left = value();

    //関数呼び出しのカッコ
    var op;
    while(op = accept(tokens,"(")){
        //丸カッコの中の複数の引数を取得
        //ここはvalueではなく、commaであることに注意
        //カッコの中は心機一転、新しい構文解析を始める
        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 = comma();
        //新しいノードを作成し階層を深める
        left = {left,op,right};
    }
    return left;
}
var tokens;
function parser(t){
    tokens = t;
    var ast = semi();
    //処理後のtokensを表示
    if(tokens.length>0){
        show("ast=",ast);
        show("処理後tokens =",tokens);
        error("tokensが余っているので、どこかおかしいので終了");
    }
    return ast;
}
module.exports = parser;

run.jsの実装

1234や1234.5は、"1234"や"1234.5"という文字列になっているので、1を掛けて演算して数値化している。

カンマ区切りの場合、tree形式だと実行時には扱いづらいので、配列化している。

[left,right]とすることで、配列化したいのだが、 すでにleftが配列だと[[left],right]な感じになってしまう。 そこでflatすることで、[left,right]に戻しておく。

print関数側も、単一引数の場合はright配列になっていないので、[right].flat()した上で、joinで連結して1つの文字列にして表示している。

$ 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);
        //数値なら数値化して返す
        (@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"){
            //引数配列を連結して1つの文字列にする
            var args = (@[run(a.right)].flat().join("");@)
            console.log(args);
        }else{
            error("未実装の関数呼び出し func=",func);
        }
    }else{
        error("未実装の演算子 op=",a.op);
    }
}

インタプリタの実行

interpretor.jsを実行する。

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

問題なく、自作プログラミング言語を実装できていることが分かる。

$ ./interpretor.js
処理前tokens =[
  'print',       '(',
  '"引数1個目"', ',',
  '"引数2個目"', ',',
  '"引数3個目"', ')',
  ';',           'print',
  '(',           '"整数="',
  ',',           '1234',
  ')',           ';',
  'print',       '(',
  '"実数="',     ',',
  '1234.5',      ')',
  ';'
]
抽象構文木ast={
  left: {
    left: {
      left: {
        left: 'print',
        op: '()',
        right: {
          left: { left: '"引数1個目"', op: ',', right: '"引数2個目"' },
          op: ',',
          right: '"引数3個目"'
        }
      },
      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個目
整数=1234
実数=1234.5


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