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

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

グローバル変数、代入、変数参照に対応
javascript版プログラミング言語の作り方(インタプリタ開発)

グローバル変数、代入、変数参照に対応する。

ページメニュー

現時点の言語仕様

グローバル変数、代入、変数参照に対応する。

変数に型はないので、整数、実数、文字列が代入できる。

変数の宣言をせず、初めて代入されると、変数定義される。 代入演算子としてイコール(=)を導入する。

変数を使ったら、変数に格納されている値を参照できるようにする。

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

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

$ cat source.3
//代入演算子が連続したら、右側のb=3から処理する(右結合)
a = b = 3;

//文字列の代入
msg = "a=";

//変数の値の表示
print(msg, a, ", b=", b);

parser.jsの実装

代入演算子(=)を扱うため、assign関数を導入した。

ほぼ、いつもと同じ形だが、 代入演算子(=)は、右結合であるため、right側がcommaになっている点が異なる。

「a = b = 1」の時、いつも通り、左側から、「(a = b) = 1」と処理するとおかしくなってしまう。 右結合なら、「a = (b = 1)」が先に処理される。

$ 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 assign(){
    //左辺を取得
    var left = funccall();

    //演算子が続く間は連続する
    var op;
    while(op = accept(tokens,"=")){
        (@//右辺を取得
        //代入は右結合
        //ここはfunccallでなく、commaにリセット
        var right = comma();@)
        //新しいノードを作成し階層を深める
        left = {left,op,right};
    }
    return left;

}

//カンマ=複数引数
function comma(){
    //左辺を取得
    var left = assign();

    //演算子が続く間は連続する
    var op;
    while(op = accept(tokens,",")){
        //右辺を取得
        var right = assign();
        //新しいノードを作成し階層を深める
        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の実装

グローバル変数置き場として、globalオブジェクトを作成している。

次に、変数が参照されるときは、globalに定義済みなら、その値を返している。

最後に、代入演算子(=)の場合、globalオブジェクトに変数名をキーに値を格納する。

$ cat run.js
module.exports = run;

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

(@//グローバル変数置き場
var global = {};@)


//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;
        (@//global変数だったら、中身を返す
        if(global.hasOwnProperty(a)) return global[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 == "="){
        (@//グローバル変数に格納する
        return global[run(a.left)] = run(a.right);@)
    }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
$ ./interpretor.js
処理前tokens =[
  'a',   '=',      'b',
  '=',   '3',      ';',
  'msg', '=',      '"a="',
  ';',   'print',  '(',
  'msg', ',',      'a',
  ',',   '", b="', ',',
  'b',   ')',      ';'
]
抽象構文木ast={
  left: {
    left: {
      left: { left: 'a', op: '=', right: { left: 'b', op: '=', right: '3' } },
      op: ';',
      right: { left: 'msg', op: '=', right: '"a="' }
    },
    op: ';',
    right: {
      left: 'print',
      op: '()',
      right: {
        left: {
          left: { left: 'msg', op: ',', right: 'a' },
          op: ',',
          right: '", b="'
        },
        op: ',',
        right: 'b'
      }
    }
  },
  op: ';',
  right: undefined
}

a=3, b=3


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