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

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

グループ化の丸カッコ対応
javascript版プログラミング言語の作り方(インタプリタ開発)

先に処理してほしい部分に指定する、グループ化の丸カッコに対応しよう。

ページメニュー

現時点の言語仕様

言語仕様として、関数呼び出しとは異なる、優先されるグループ化の丸カッコを追加する。

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

丸カッコが優先されるか。

//カッコが優先されるか
a=(5-2)*3;
print("a = (5-2)*3=",a);

//カッコが優先されるか
b = 2 * (3 - 5);
print("b = 2 * (3 - 5)=",b);

//単項の符号と丸カッコ
c = -(2+1) * 3;
print("c = -(2+1) * 3=",c);

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

インタプリタを作るにあたり、以下のファイル構成で実装した。

parser.jsの実装

グループ化の丸カッコを扱うparenを追加した。 注意点としては、カッコ内をリセットで、commaであることと、 単項であるため、左辺読まず、最後のreturnでvalueを呼んでいることだ。

さらに、グループ化のカッコ自体は、優先する以外に何も処理しないので、 while内のreturnで、rightを直接返すことで、階層を作らないようにしている。

$ cat parser.js
//---------------構文解析---------------//
var {expect,accept,show,error}  = require("./utils.js");

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

//グループ化の丸カッコ
function paren(){
    //左辺を読まない

    //グループ化の丸カッコ開始
    var op;
    while(op = accept(tokens,"(")){
        //丸カッコの中はvalueではなく、commaであることに注意
        var right = (@comma();@)

        //閉じカッコであることを確認して取得
        console.log();
        op += expect(tokens,")");

        (@//opを捨て、rightをそのまま返すのは階層を縮めたいから。
        return right@);
    }

    (@//ここはleftじゃなく、value呼び出しであることに注意
    return value();@)
}


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

    //関数呼び出しのカッコ
    var op;
    if(op = accept(tokens,"(")){
        //丸カッコの中の複数の引数を取得
        //ここはvalueではなく、commaであることに注意
        var right = comma();
        //閉じカッコであることを確認して取得
        op += expect(tokens,")");
        //新しいノードを作成し階層を深める
        left = {left,op,right};
    }
    return left;
}

//単項の符号(右結合)
function flag(){
    //左辺を取得しない

    //演算子が続く間は連続する
    var op;
    while(op = accept(tokens,"+","-")){
        //右辺を取得
        var right = comma();
        //プラス符号なら何もしない
        if(op == "+") return right;
        //マイナス符号なら、(0-1)を掛ける階層を作る
	return {left:{left:"0",op:"-",right:"1"},op:"*",right};
    }

    //ここがleftじゃなく、funccallなことに注意
    return funccall();
}


//掛け算、割り算
function mul(){
    //左辺を取得
    var left = flag();

    //演算子が続く間は連続する
    var op;
    while(op = accept(tokens,"*","/")){
        //右辺を取得
        var right = flag();
        //新しいノードを作成し階層を深める
        left = {left,op,right};
    }
    return left;
}

//足し算、引き算
function plus(){
    //左辺を取得
    var left = mul();

    //演算子が続く間は連続する
    var op;
    while(op = accept(tokens,"+","-")){
        //右辺を取得
        var right = mul();
        //新しいノードを作成し階層を深める
        left = {left,op,right};
    }
    return left;
}

//イコール代入は右結合
function assign(){
    //左辺を取得
    var left = plus();

    //演算子が続く間は連続する
    var op;
    while(op = accept(tokens,"=")){
        //右辺を取得
        //ここはplusでなく、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;

インタプリタの実行

interpretor.jsを実行する。

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

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

$ ./interpretor.js
処理前tokens =[
  'a',              '=',     '(',
  '5',              '-',     '2',
  ')',              '*',     '3',
  ';',              'print', '(',
  '"a = (5-2)*3="', ',',     'a',
  ')',              ';',     'b',
  '=',              '2',     '*',
  '(',              '3',     '-',
  '5',              ')',     ';',
  'print',          '(',     '"b = 2 * (3 - 5)="',
  ',',              'b',     ')',
  ';',              'c',     '=',
  '-',              '(',     '2',
  '+',              '1',     ')',
  '*',              '3',     ';',
  'print',          '(',     '"c = -(2+1) * 3="',
  ',',              'c',     ')',
  ';'
]



抽象構文木ast={
  left: {
    left: {
      left: {
        left: {
          left: {
            left: {
              left: 'a',
              op: '=',
              right: {
                left: { left: '5', op: '-', right: '2' },
                op: '*',
                right: '3'
              }
            },
            op: ';',
            right: {
              left: 'print',
              op: '()',
              right: { left: '"a = (5-2)*3="', op: ',', right: 'a' }
            }
          },
          op: ';',
          right: {
            left: 'b',
            op: '=',
            right: {
              left: '2',
              op: '*',
              right: { left: '3', op: '-', right: '5' }
            }
          }
        },
        op: ';',
        right: {
          left: 'print',
          op: '()',
          right: { left: '"b = 2 * (3 - 5)="', op: ',', right: 'b' }
        }
      },
      op: ';',
      right: {
        left: 'c',
        op: '=',
        right: {
          left: { left: '0', op: '-', right: '1' },
          op: '*',
          right: {
            left: { left: '2', op: '+', right: '1' },
            op: '*',
            right: '3'
          }
        }
      }
    },
    op: ';',
    right: {
      left: 'print',
      op: '()',
      right: { left: '"c = -(2+1) * 3="', op: ',', right: 'c' }
    }
  },
  op: ';',
  right: undefined
}

a = (5-2)*3=9
b = 2 * (3 - 5)=-4
c = -(2+1) * 3=-9


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