javascript/C言語/アセンブラを用い、 字句解析、構文解析、インタプリタ、コンパイラのプログラムをスクラッチから作りながら、 「プログラミング言語の作り方」を解説する。
print文が何回呼ばれても良いように、複数文対応をしよう。
複数文に対応するため、セミコロンで区切るという言語仕様を追加する。
ソースコードには、セミコロンで区切られたprint文を3つを並べよう。
$ cat source.3 print("hello world"); print("hello world2"); print("hello world3");
複数文対応する際に、変更となる箇所は、lexer.js、parser.js、run.jsだ。
字句解析lexerで指定している正規表現に、セミコロンを追加する。
$ cat lexer.js //------字句解析------// module.exports = function(source){ //正規表現を使い、"文字列" or print or セミコロン or 改行で分割。 //丸カッコで囲まれると残り、囲まれていないと捨てられる var tokens = source.split(/(".*"|print|;)|\n/); //splitの仕様上、undefinedや''などが残るので、不要なものは捨てる tokens = tokens.filter(a=>a); return tokens; }
構文解析parserでは、関数semiを追加した。
文法規則にそって、semiでは文と文の間にはセミコロンがあり、 何個でも連続しても良いようになっている。
ポイントは、semi関数の最後に新しく作ったノードをleftに代入する部分だ。 こうすることで、while文がループする度に、tree階層を深くすることができる。
$ cat parser.js module.exports = parser; var {expect,accept,show,error} = require("./utils.js"); var tokens; //構文解析開始 function parser(t){ tokens = t; return semi(); } //セミコロン function semi(){ //print文 var left = callprint(); //セミコロンがきたら次のprint文があるかも var op; while(op = accept(tokens,";")){ //次のprint文 var right = callprint(); (@//新しいノードを作成し、次のleftに入れることで、階層を作っていく left = {left,op,right};@) } return left; } //print関数呼び出しの構文解析 function callprint(){ if(tokens.length==0)return; //関数名がprintであること var left = expect(tokens,"print"); //関数呼び出しの丸カッコであること var op = expect(tokens,"("); //文字列を取得 var right = tokens.shift(); //閉じカッコであること op += expect(tokens,")"); //新しいノードを作成 return {left, op, right}; }
セミコロンだと、left/rightを実行するだけだ。
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); //それ以外ならそのまま返す return a; }else if(a.op == ";"){ (@//セミコロンだとleft/rightを実行するだけ run(a.left) ; run(a.right);@) }else if(a.op == "()"){ //leftに関数名 var func = run(a.left); if( func == "print"){ //rightに表示する文字列 var msg = run(a.right) //表示=実行 console.log(msg); }else{ error("未実装の関数呼び出し func=",func); } }else{ error("未実装の演算子 op=",a.op); } }
interpretor.jsに実行権限を付けて、実行する。
まず、tokensとastが意図どおりに分割されているか、念のため表示しているので、確認しよう。
特に、astの階層の違いによって、処理の順番が明確になっていることに注意しよう。
leftの方を、先に深く深くたどり、行き詰ると、そのノードのleft/rightの順で処理する。 次は、1つ上にもどる、という順だ。
このASTの順番通りに、「hello world」、「hello world2」、「hello world3」と表示されている。
$ ./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
複数文対応のhello worldであっても、とても簡単だ。