javascript/C言語/アセンブラを用い、 字句解析、構文解析、インタプリタ、コンパイラのプログラムをスクラッチから作りながら、 「プログラミング言語の作り方」を解説する。
単項演算子の符号に対応しよう。
言語仕様として、単項演算子の符号を追加する。
単項符号がちゃんと計算できるか。 掛け算の後に符号が来ることで、演算子が連続しても動くか。
//単項の符号 a = -2 * 3; print("a = -2 * 3=",a); //単項の符号2つ b = -2 * -3; print("b = -2 * -3=",b);
インタプリタを作るにあたり、以下のファイル構成で実装した。
単項の符号に対応するため、flagを追加した。 注意点としては、右結合なので、リセットで、commaであることと、 単項であるため、左辺読まず、最後のreturnでfunccallを呼んでいることだ。
さらに、プラスの符号がついても何も処理しないので、そのままrightを返し、 マイナスの符号がついたら、rightに(0-1)、つまり-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; 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', '=', '-', '2', '*', '3', ';', 'print', '(', '"a = -2 * 3="', ',', 'a', ')', ';', 'b', '=', '-', '2', '*', '-', '3', ';', 'print', '(', '"b = -2 * -3="', ',', 'b', ')', ';' ] 抽象構文木ast={ left: { left: { left: { left: { left: 'a', op: '=', right: { left: { left: '0', op: '-', right: '1' }, op: '*', right: { left: '2', op: '*', right: '3' } } }, op: ';', right: { left: 'print', op: '()', right: { left: '"a = -2 * 3="', op: ',', right: 'a' } } }, op: ';', right: { left: 'b', op: '=', right: { left: { left: '0', op: '-', right: '1' }, op: '*', right: { left: '2', op: '*', right: { left: { left: '0', op: '-', right: '1' }, op: '*', right: '3' } } } } }, op: ';', right: { left: 'print', op: '()', right: { left: '"b = -2 * -3="', op: ',', right: 'b' } } }, op: ';', right: undefined } a = -2 * 3=-6 b = -2 * -3=6