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

今回は前回作った文演算子を使って、if文とwhile文を作ります。
これは、無名関数を作成するfunと同じ章で書くとよかったなと思うので、
次に書くときはまとめることを検討しないととおもいます。
というか、内部表現をいろいろ変えたのでそのへんでおなかいっぱいだったので
そのへんを直したほうがいいなということで次の話はこのくらいにしておいて
とにかく、目の前のhaXeで作っている言語をとにかく、エラー処理に問題があるにせよ
機能的に作ろうとしていた部分まで作っていきますよー。


新しいC言語を作ろうとした場合に、やっぱ、LISPのマクロ欲しい。
という欲求は必ず出てきます。
だけど、難しいとかいう話になって立ち消えになることがほとんどでしょう。
それがなぜかというと、リードマクロ相当のマクロを作って複雑さに嫌気がさすか、
あるいは、構文木ベースにすることに対しての前例がないためにだれもやらないという。
だれもやってないから作る意味があるのにもかかわらず。


Androidも結局高速化を求められるところはCかC++なんて状況は変わりません。
Objective-CもCベースに変わりはないということで、結局根本的な部分は変わらない。
それを置き換えるための基盤になるのを目指しているのがこの言語です。


ということで、今回はif else 式を作ります。
C言語でいうところのif文です。(文演算子を使ってif文ではなくて、if式をつくるなんてわけわからん話なのですが。
if文ではなくてif式を作ります。文演算子の名前が良くないと思うのであとで名前は考え直します。)

ご存知でしょうがC言語には3項演算子という演算子があります。

たとえば以下のようなif文を

if (a < 3) { b = 2; } else { b = 1; }

3項演算子を使うと以下のように書くことができます。

b = a < 3 ? 2 : 1;

この3項演算子はとくにわかりずらい演算子なので書くことをやめることがある演算子です。
この問題を解決する方法として考えられているのがif文を式として扱う方法です。

b = if (a < 3) { 2; } else { 1; }

if文を式として扱えるようにして値を返すことにすることで、3項演算子やっていたことをよりわかりやすく書くことができます。
このように、if文を式にするとメリットがあるわけですが、elseのないif文の値はどうするのかという問題が出てきます。

b = if(a < 3) { 2; }

この例の場合どうするか?です。いろいろな実装があり、2あるいは0としてもいいでしょうし、両方の値が空であるとする実装もあります。今回は動的型を持つ言語なので、2とnilを返すことにします。つまり
b = if(a < 3) {2 ; } else { nil; }と同じ意味ということです。ただし、構文木は同じではありません。


さてということで、つくります。説明書いているのがめんどくさくなってきた。

えーと、Calc9.hxmlをいつものように作りますが、今回からはnekoVM用にコンパイルしてみます。
nekoVMで動かすメリットはコマンドプロンプトで実行できることで、いちいち、ブラウザあるいは、
flashplayerを起動しなくてすむというメリットがあります。

Calc9.hxml

-main Calc9
-neko Calc9.n
a=fun(b)if(b > 1)2 else 1 a(2)

このようなプログラムを今回動かします。
class名をCalc9にして今回動かしたいコードを書きます。

class Calc9 {
	static function main() {
		var c = new Calc9();
		trace(c.eval("a=fun(b)if(b > 1)2 else 1 a(2)"));
	}

実行するには

neko Calc9

とします。結果はflashで実行した場合と同じようになります。

コンストラクタでif文演算子と2項演算子elseを追加します。

	function new(){
:
:
		opLs.set("else",2);
		opLs.set(">",190);
		opLs.set("<",190);
:
:
		opSs.set("if","(");
	}

また、expnのopSsの処理を以下のように修正

		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

t = op(e, "S"+tk+p1+op2, expn(p));をexpn(0)にしています。
この修正は文演算子の右辺はすべての式を取るために行いました。要するに、前回は若干バグってたということです。


さてこれで、パーサの作業はおしまい。


次にインタプリタに処理を加えます。
まず、if式の処理は以下のように、左辺(括弧の中身)を計算してl1に入れます。
そして、右辺がelseの式かどうかチェックして、else式ならl1が真つまり0以外ならelse式の左側を返し、
偽なら右側を返します。
else式でないなら、l1が真なら右辺を計算て返し、l1が偽ならnilを返します。

			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;

2項演算子の計算は以下のように変更。

					case "L>": return num(b - a);
					case "L<": return num(a - b);

ということで、今回はif else 式と比較演算子を追加しました。

以下全体のソースです。


Calc9.hx

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

class Calc9 {
	static function main() {
		var c = new Calc9();
		trace(c.eval("a=fun(b)if(b > 1)2 else 1 a(2)"));
	}
	var opLs:Hash<Int>;
	var opRs:Hash<Int>;
	var opPs:Hash<String>;
	var opMs:Hash<Int>;
	var opSs:Hash<String>;
	var endPs: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>();
		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("(", 0);
		opMs.set("{", 0);
		opMs.set("[", 0);
		endPs.set(")", 0);
		endPs.set("]", 0);
		endPs.set("}", 0);
		opSs.set("fun","(");
		opSs.set("if","(");
	}

	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(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);
			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;
					}
				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;
		}
	}
}