javascript/C言語/アセンブラを用い、 字句解析、構文解析、インタプリタ、コンパイラのプログラムをスクラッチから作りながら、 「プログラミング言語の作り方」を解説する。
先に処理してほしい部分に指定する、グループ化の丸カッコに対応しよう。
言語仕様として、関数呼び出しとは異なる、優先されるグループ化の丸カッコを追加する。
丸カッコが優先されるか。
//カッコが優先されるか 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);
インタプリタを作るにあたり、以下のファイル構成で実装した。
グループ化の丸カッコを扱う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