javascript/C言語/アセンブラを用い、 字句解析、構文解析、インタプリタ、コンパイラのプログラムをスクラッチから作りながら、 「プログラミング言語の作り方」を解説する。
print文専用の構文解析をやめ、汎用的な関数呼び出しの構文解析に対応する。
関数呼び出しの構文解析funccallに対応する。 また、どれにも該当しない場合、そのままの値を返すvalue関数を作成。
funccallもsemi関数と同じ構造にすることで汎用化する。 ただし、関数呼び出し特有のカッコの部分は異なる。
left側の呼び出しは、valueだが、 right側の呼び出しは、カッコの中なので、心機一転、semiから新しい構文解析を始める
$ cat 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,"(")){ //ここはvalueではなく、semiであることに注意 //カッコの中は心機一転、新しい構文解析を始める (@var right = semi();@) //閉じカッコであることを確認して取得 (@op += expect(tokens,")");@) //新しいノードを作成し階層を深める left = {left,op,right}; } return left; } //セミコロン=複数文 function semi(){ //左辺を取得 var left = funccall(); //演算子が続く間は連続する var op; while(op = accept(tokens,";")){ //右辺を取得 var right = funccall(); //新しいノードを作成し階層を深める left = {left,op,right}; } return left; }
parserの処理を汎用的に変えたが、結果は同じだ。
$ ./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