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