JavaでBasicライク言語のインタプリタ実装 #4 実行編
今日書くこと
昨日の式の解析ができれば構文解析は完成したような物なので、 構文解析はできたということで今日は実行の方の解説をしようと思います。 なんかあれな理由で時間がないので、軽く書くだけ。
過去記事リンクは以下
今日で完結です。 (適当なネタが無くなってしまった)
実行
文字通りプログラムを実行することです。
例えば
a=1
というコードであれば実際に、変数aを用意し、そこに1という値を代入する処理を行います。
また`PRINT "Hello"であればPRINT関数を実行します。
構文木インスタンスと実行
構文木の要素(LoopBlock
クラスなど)はそれ自体がどんな実行をすれば良いかを知っています。
つまり、それぞれのクラスに対して、evel()
メソッドを実装して、これを呼んでやればインスタンスがよしなにやってくれるようにします。
オブジェクト指向って感じがしますね。
例えば、LoopBlock
クラスのevel()
メソッドは
- 子のCondNodeをevalしループ実行判定
- 実行するならStmtListを実行
- 繰り返す
という処理を行います。
ExprNode
クラスのeval()
メソッドであれば単純に式を計算して1つの値を返すようにします。
それぞれの構文要素のクラスにあった計算方法をそれぞれのクラスのevalとして実装することで ProgramNodeのevalを呼ぶと再帰的に実行が行えるようになります。 (programはstmtlistのevalを行い、stmtlistのevalは子それぞれのevalを順々に呼び・・・と)
実行とEnviroment
これまで適当に扱ってきたEnviroment
クラスが本気を出し始めます。
このクラスには、
- 変数テーブル
- 関数テーブル
を持たせ、変数を問い合わせする処理や新規定義する処理、関数を呼び出す処理などを行います。
実行中の変数テーブルとか関数テーブルをここに作っていくことになります。
関数定義を動的に行うことは今回の言語仕様にないので、関数テーブルについてはコンストラクタで決め打ちにしてしまえばいいと思います。
実装
AssigneStmt.java
代入文のクラスであるAssigneStmtならば、こんな感じにします。
式を評価して、その結果をname
の変数に代入
@Override public Value eval() { // 変数テーブルに入れる Value value = exprNode.eval(); // Debug // System.out.println(name + "に" + value.getSValue() + "をいれる!"); env.setVarValue(name, value); return null; }
ここのenvはenviromentで変数テーブルを持ちます。
Enviroment.java
/** * 実行環境を持たせる. * * 変数名表とか関数テーブルとか */ public class Environment { private LexicalAnalyzer input; private Hashtable<String, Value> varTable; private Hashtable<String, Function> funcTable; public Environment(LexicalAnalyzer my_input) { input = my_input; varTable = new Hashtable<>(); funcTable = new Hashtable<>(); funcInit(); } private void funcInit() { funcTable.put("PRINT", new PrintFunction()); funcTable.put("SQRT", new SqrtFunction()); } public LexicalAnalyzer getInput() { return input; } public void setVarValue(LexicalUnit varName, Value value) { varTable.put(varName.getValue().getSValue(), value); } public Value getVarValue(LexicalUnit varName) { Value res = varTable.get(varName.getValue().getSValue()); if (res == null) { throw new IllegalStateException("存在しない変数を参照しようとしました!"); } return res; } public void setFunc(LexicalUnit funcName, Function function) { funcTable.put(funcName.getValue().getSValue(), function); } public Function getFunction(LexicalUnit name) { Function res = funcTable.get(name.getValue().getSValue()); if (res == null) { throw new IllegalStateException("存在しない関数を参照しようとしました!"); } return res; } }
こんなかんじで変数テーブルと関数テーブルを用意しておき、使えば良いでしょう。
関数定義と実行
/** * 平方根出す関数 */ public class SqrtFunction extends Function { @Override public Value eval(ExprListNode arg) { Value val = arg.getValue(0); return new ValueImpl(Math.sqrt(val.getDValue())); } }
こんなクラスをつくっておいて、enviromentのコンストラクタで、関数テーブルに登録します。
これで、
FunctionCallNodeクラスのevalで
@Override public Value eval() { Function function = env.getFunction(name); return function.eval(exprList); }
こんな感じで使えるようになります。
IFBlockNode.java
IF文などの条件判定ノードやループノードの実行は小のCondNodeの評価を行って、ループを行ったり、どのStmtを実行するか決めたりすれば良いです。 IF文はIF-ELSEで続いてるときがあって、ちょっと面倒だったけど
@Override public Value eval() { Value condValue = cond.eval(); if (condValue.getBValue()) { getThenNode().eval(); } else { // 連続するIF文を実行する // ELSEIFですべてつながってるので順繰りにやっていく // どれか実行されたらevalElseIfNodeの戻り値がfalseで抜ける. int n = 0; while (evalElseIfNode(n)) { n++; } // 本体のELSEを実行する. if (getElseNode() != null) { getElseNode().eval(); } } return null; }
こんなんでいけます。
さいご
駆け足で実行系を書いちゃったけどこんなんでいけるはずです。
構文木つくれたのなら余裕?。
そろそろレポートの提出締め切りが近いようですが頑張って下さい。
書いてて思ったけどそんな難しくないけど実装量多いなって思いました。
アノテーションで文法定義だけ書いてよしなにやる実装とかみたので、そうやるとSyntaxNode部分は大分分量を削れるかなぁと今になって思う。
まぁ頑張ってささっと終わらせちゃって下さい
どうでもいいはなし
今日はコミケ1日目でした。
やる気がないのでビックサイト周辺にいるけどならんでない
— namazu (@namazu510) 2017年12月28日
こんなんで、東京テレポートの方の橋の上に列が来たときに並ぶなんていう物見遊山勢してました。
お使いがけっこうあって、2つくらいいけば終わるはずが、普通に回ってしまいました。 これなら速く入ってさっさと終わらせとけばよかったなぁって今では反省しています。
明後日が本番なので体調に気をつけて準備していきたいですね。
今日は昔から買ってる毎度の藍様本、なぜか1日目にいたけもみみクラスタさん、性癖買いしたFGO本とかを買いました。 昔1日目の始発で狙ってた狐サークルさんが艦これをずっと書いててしょぼーんです。 」
てか3日目のオリジナルの半分をどっかに隔離して1日目、2日目にやれば大分平和になると思うんだけどなんでしないんだろう?