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

後置演算子追加

今回は、後置演算子を追加します。
後置演算子とは、後ろにつく演算子のことです。たとえば、1を足す演算子などがそれです。

i++

今回作成する言語ではセミコロン';'も後置演算子として扱います。セミコロンは式と式を分離させる意味をもっていて、
たとえば、以下のようにprintln(b)(b++)と書いた場合は括弧演算子の連続としてパースされることを防ぐ役目を持ちます。

pb=2; println(b); (b++)

if elseの中でセミコロンを書く場合でもうまくいきます。

if(a) b; else c;


今回の追加は以下のとおりです。

	var opBs:Hash<Int>;

後置演算子用のハッシュを用意します。

コンストラクタ内でHashを作成し、++と;の演算子を登録します。

	function new(){
		opBs = new Hash<Int>();
		opBs.set("++", 30);
		opBs.set(";", 1);
	}

lexに今回追加した、セミコロンを追加し、さらに、記号の並びが続いたら演算子とみなすように変更します。

		var r : EReg = ~/^[\r\n\t ]*([0-9]+|;|[+*\-\/\[\]{}()=]+|[a-zA-Z_][a-zA-Z0-9]*)/;

パーサはexpn関数に

			if(opBs.exists(token) && (tagp = opBs.get(token)) >= p) {
				var tag = lex();
				t = op(t, "B"+tag, nil);
				p = tagp;
				continue;
			}

を追加します。

それと、いままでは、2項演算子はopL,opRというプリフィックスをつけていましたが、opを取り除きました。

execute内に、++と;の動作を加えて完成です。

			case "B;": return execute(l);
:
:
:
				case "B++":
					var dt = env.get(d);
					switch(dt){
					case num(b):
						env.set(d, num(b + 1));
						return dt;
					default:
					}

以下ソースです。
Calc10.hxml

-neko Calc10.swf
-main Calc10

Calc10.hx

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

class Calc10 {
	static function main() {
		var c = new Calc10();
		trace(c.eval("b=2; println(b); (b++)"));
	}
	var opLs:Hash<Int>;
	var opRs:Hash<Int>;
	var opPs:Hash<String>;
	var opMs:Hash<Int>;
	var opSs:Hash<String>;
	var endPs:Hash<Int>;
	var opBs:Hash<Int>;

	function new(){
		opLs = new Hash<Int>();
		opRs = new Hash<Int>();
		opPs = new Hash<String>();
		opMs = new Hash<Int>();
		endPs = new Hash<Int>();
		opSs = new Hash<String>();
		opBs = new Hash<Int>();
		opLs.set("+", 10);
		opLs.set("-", 10);
		opLs.set("*", 20);
		opLs.set("/", 20);
		opLs.set(">",190);
		opLs.set("<",190);
		opLs.set("else",1);
		opRs.set("=", 5);
		opPs.set("(",")");
		opPs.set("[","]");
		opPs.set("{","}");
		opMs.set("(", 1);
		opMs.set("{", 1);
		opMs.set("[", 1);
		endPs.set(")", 0);
		endPs.set("]", 0);
		endPs.set("}", 0);
		opSs.set("fun","(");
		opSs.set("if","(");
		opBs.set("++", 30);
		opBs.set(";", 1);
	}

	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):Exp {
		var exp = parse(str);
		trace(exp);
		env = new Hash<Exp>();
		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 eat(e:String):String {
		var t = lex();
		if(t != e) throw ("expect error " + e);
		return t;
	}

	function expn(p:Int):Exp {
		if(endPs.exists(token)) return vid;
		var tk = lex();
		var numReg : EReg = ~/[0-9]+/;
		var t:Exp;

		if (opSs.exists(tk) && opSs.get(tk) == token) {
			var p1 = lex();
			var e = expn(0);
			var op2 = eat(opPs.get(p1));
			t = op(e, "S"+tk+p1+op2, expn(0));
		} else
		if(opPs.exists(tk + "")) {
			var t2 = expn(0);
			if(lex() != opPs.get(tk)) throw ("error");
			t = op(nil, "P()", t2);
		} else if(numReg.match(tk)) {
			t = num(Std.parseInt(tk));
		} else {
			t = sym(tk);
		}

		var tagp:Int;
		while(true) {
			if(opBs.exists(token) && (tagp = opBs.get(token)) >= p) {
				var tag = lex();
				t = op(t, "B"+tag, nil);
				p = tagp;
				continue;
			}
			if(opMs.exists(token) && (tagp = opMs.get(token)) >= p) {
				var tag = lex();
				var e = expn(tagp);
				var tag2 = eat(opPs.get(tag));
				t = op(t, "M"+tag+tag2, e);
				continue;
			}
			if(opLs.exists(token) && (tagp = opLs.get(token)) > p) {
				var tag = lex();
				t = op(t, "L"+tag, expn(tagp));
				continue;
			}
			if(opRs.exists(token) && (tagp = opRs.get(token)) >= p) {
				var tag = lex();
				t = op(t, "R"+tag, expn(tagp));
				continue;
			}
			break;
		}
		return t;
	}

	var env:Hash<Exp>;
	
	function bind(env, prm, l):Void{
		switch(prm) {
		case sym(p): env.set(p,l); return;
		case op(p,t,ps):
			switch(l){
			case op(lp, lt, lps):
				bind(env, p, l);
				bind(env, ps, lps);
			default: throw "error";
			}
		default: throw "error";
		}
	}

	function execute(exp:Exp):Exp {
		switch(exp) {
		case vid: return num(0);
		case nil: return num(0);
		case num(d): return num(d);
		case sym(d): return env.get(d);
		case op(l, tag, r):
			switch(tag){
			case "Sif()":
				var l1 = execute(l);
				switch(r) {
				case op(l2, tag2, r2):
					if(tag2 == "Lelse") {
						switch(l1){
						case num(n):
							if(n != 0) return execute(l2);
						default:
						}
						return execute(r2);
					}
				default:
				}
				switch(l1){
				case num(n):
					if(n != 0) return execute(r);
				default:
				}
				return nil;

			case "Sfun()": return exp;
			case "P()": return execute(r);
			case "B;": return execute(l);
			default:
			}
			switch(l) {
			case sym(d):
				switch(tag){
				case "R=":
					var rc = execute(r);
					env.set(d, rc);
					return rc;
				case "M()":
					if(d=="println"){
						var r = execute(r);
						return r;
					}
				case "B++":
					var dt = env.get(d);
					switch(dt){
					case num(b):
						env.set(d, num(b + 1));
						return dt;
					default:
					}
				default:
				}
			default:
			}
			// 左辺を評価
			var a = execute(l);
			switch (a) {
			case num(a):
				var b = execute(r);
				switch(b){
				case num(b):
					switch (tag) {
					case "L+": return num(a + b);
					case "L-": return num(a - b);
					case "L*": return num(a * b);
					case "L/": return num(cast(a / b, Int));
					case "L>": return num(b - a);
					case "L<": return num(a - b);
					}
					default:
				}
			default:
			}
			switch(tag) {
			case "@": return execute(r);
			case "M()":
				switch(a){
				case op(prm,fun,body):
					if(fun=="Sfun()"){
						var back = env;
						env = new Hash<Exp>();
						bind(env, prm, r);
						a = execute(body);
						env = back;
						return a;
					}
				default:
				}
			default:
			}
			return a;
		}
	}
}