haXeで作るプログラミング言語(5)

今回は変数を追加します。例)a = 1

それと代入演算子は左結合と右結合の話もちょっとします。
同じ優先順位の演算子が2つ並んだ場合に、
どっちが優先して結合されるかっていうのが右結合とか左結合というわけ方になります。

変数への値の代入

変数への値の代入が出来るようにするには環境となるハッシュテーブルを用意して値を保存します。
また、変数を参照できるようにする必要が出てきます。
式の連結 a=2 a*3は 空白の意味を持つ@演算子というのを用意して接続していることにします。

op(
  op(sym("a"),"=",num(2)),
  "@",
  op(sym("a"),"*",num(3))
)

このようなかんじ。この@を使うのはClean Bookの簡単なインタプリタを参考にしています。

右結合と左結合

a = b = cという式があったときに、( ( a = b) = c )と左が優先的に繋がるのが左結合、( a = ( b = c ) )と右側が優先的に繋がるのが
右結合です。

a = b = 1 + 2 * 3

と書いたときに、aとbにちゃんと代入されてほしいってことですね。
そのためには、ぜひとも右側が優先してくっついて欲しいってときは右結合です。
対して、左側が優先的に繋がって欲しい!ってときは左結合です。
1 + 2 + 3は((1 + 2) + 3)って左側が繋がって欲しいから、左結合なんです。

環境変数

haXeではハッシュテーブルを使うには以下のように、JavaのHashtableのような形で使います。
また、型は厳正に決めないといけません。今回はStringが名前でIntが値になっています。

	var env:Hash<Int>;

値の設定はこんなかんじ

env.set(name, value);

値の取得はこんなかんじです。

env.get(name);

てことで、作ったのがこちらです。

以下ソースです。

enum Exp {
	nil;
	num(d:Int);
	sym(d:String);
	op(l:Exp, tag:String, r:Exp);
}

class Calc5 {
	static function main() {
		var c = new Calc5();
		trace(c.eval("a=1+2*3 (a+2)*3"));
	}
	var opLs:Hash<Int>;
	var opRs:Hash<Int>;
	function new(){
		opLs = new Hash<Int>();
		opRs = new Hash<Int>();
		opLs.set("+", 10);
		opLs.set("-", 10);
		opLs.set("*", 20);
		opLs.set("/", 20);
		opRs.set("=", 5);
	}

	var src:String;
	var token:String;
	var token2:String;

	function lex():String {
		var r : EReg = ~/^[\r\n\t ]*([0-9]+|[+*\-\/=()]|[a-zA-Z_][a-zA-Z0-9]*)/;
		token2 = token;
		if(r.match(src)){
			token = r.matched(1);
			src = src.substr(r.matched(0).length);
			return token2;
		}
		token = "";
		return token2;
	}

	function eval(str:String):Int {
		var exp = parse(str);
		trace(exp);
		env = new Hash<Int>();
		return execute(exp);
	}

	function parse(str:String):Exp {
		src = str;
		lex();
		var rc = expn(0);
		while (token != "") {
			rc = op(rc, "@", expn(0));
		}
		return rc;
	}

	function expn(p:Int):Exp {
		var t = fact();
		var tagp:Int;
		while(true) {
			if(opLs.exists(token) && (tagp = opLs.get(token)) > p) {
				var tag = lex();
				t = op(t, "opL"+tag, expn(tagp));
				continue;
			}
			if(opRs.exists(token) && (tagp = opRs.get(token)) >= p) {
				var tag = lex();
				t = op(t, "opR"+tag, expn(tagp));
				continue;
			}
			break;
		}
		return t;
	}

	function fact():Exp {
		var t = lex();
		if(t == "(") {
			var t2 = expn(0);
			if(lex() != ")") throw ("error");
			return t2;
		}
		var numReg : EReg = ~/[0-9]+/;
		if(numReg.match(t)) {
			return num(Std.parseInt(t));
		}
		return sym(t);
	}
	var env:Hash<Int>;
	
	function execute(exp:Exp):Int {
		switch(exp) {
		case nil: return 0;
		case num(d): return d;
		case op(l, tag, r):
			switch(tag){
			case "@": execute(l); return execute(r);
			case "opL+": return execute(l)+execute(r);
			case "opL-": return execute(l)-execute(r);
			case "opL*": return execute(l)*execute(r);
			case "opL/": return cast(execute(l)/execute(r),Int);
			case "opR=":
				var rc = execute(r);
				switch(l){
				case sym(d):
					env.set(cast(d, String), rc);
					return rc;
				default: throw "error";
				}
			default: return 0;
			}
		case sym(d): return env.get(d);
		}
	}
}

Calc5.hxml

-swf Calc5.swf
-main Calc5