فهرست منبع

added haxe.macro.ExprTools + unit tests

Simon Krajewski 12 سال پیش
والد
کامیت
260c0081c9
2فایلهای تغییر یافته به همراه344 افزوده شده و 0 حذف شده
  1. 206 0
      std/haxe/macro/ExprTools.hx
  2. 138 0
      tests/unit/unitstd/haxe/macro/ExprTools.unit.hx

+ 206 - 0
std/haxe/macro/ExprTools.hx

@@ -0,0 +1,206 @@
+package haxe.macro;
+import haxe.macro.Expr;
+
+/**
+	This class provides some utility methods to work with expressions. It is
+	best used through 'using haxe.macro.exprTools' syntax and then provides
+	additional methods on haxe.macro.Expr instances.
+	
+	While mainly intended to be used in macros, it works in non-macro code as
+	well.
+**/
+class ExprTools {
+	
+	/**
+		Calls function [f] on each sub-expression of [e].
+		
+		If [e] has no sub-expressions, this operation has no effect.
+	
+		Otherwise [f] is called once per sub-expression of [e], with the
+		sub-expression as argument. These calls are done in order of the
+		sub-expression declarations.
+		
+		This method does not call itself recursively. It should instead be used
+		in a recursive function which handles the expression nodes of interest.
+		
+		Usage example:
+			
+		function findStrings(e:Expr) {
+			switch(e.expr) {
+				case EConst(CString(s)):
+					// handle s
+				case _:
+					ExprTools.iter(e, findStrings);
+			}
+		}
+	**/
+	static public function iter( e : Expr, f : Expr -> Void ) : Void {
+		switch(e.expr) {
+			case EConst(_),
+				EContinue,
+				EBreak,
+				EDisplayNew:
+			case EField(e, _),
+				EParenthesis(e),
+				EUntyped(e),
+				EThrow(e),
+				EDisplay(e, _),
+				ECheckType(e, _),
+				EUnop(_, _, e),
+				ECast(e, _),
+				#if !haxe3
+				EType(e, _),
+				#end
+				EMeta(_, e):
+					f(e);
+			case EArray(e1, e2),
+				EWhile(e1, e2, _),
+				EBinop(_, e1, e2),
+				EFor(e1, e2),
+				EIn(e1, e2):
+					f(e1);
+					f(e2);
+			case EVars(vl):
+				for (v in vl)
+					opt2(v.expr, f);
+			case ETry(e, cl):
+				f(e);
+				for (c in cl)
+					f(c.expr);
+			case ETernary(e1, e2, e3)
+			| EIf(e1, e2, e3):
+				f(e1);
+				f(e2);
+				opt2(e3, f);
+			case EArrayDecl(el),
+				ENew(_, el),
+				EBlock(el):
+					ExprArrayTools.iter(el, f);
+			case EObjectDecl(fl):
+				for (fd in fl)
+					f(fd.expr);
+			case ECall(e, el):
+				f(e);
+				ExprArrayTools.iter(el, f);
+			case EReturn(e):
+				opt2(e, f);
+			case EFunction(_, func):
+				for (arg in func.args)
+					opt2(arg.value, f);
+				opt2(func.expr, f);
+			case ESwitch(e, cl, edef):
+				f(e);
+				for (c in cl) {
+					ExprArrayTools.iter(c.values, f);
+					opt2(c.guard, f);
+					opt2(c.expr, f);
+				}
+		}
+	}
+	
+	/**
+		Transforms the sub-expressions of [e] by calling [f] on each of them.
+		
+		If [e] has no sub-expressions, this operation returns [e] unchanged.
+	
+		Otherwise [f] is called once per sub-expression of [e], with the
+		sub-expression as argument. These calls are done in order of the
+		sub-expression declarations.
+		
+		This method does not call itself recursively. It should instead be used
+		in a recursive function which handles the expression nodes of interest.
+		
+		Usage example:
+		
+		function capitalizeStrings(e:Expr) {
+			return switch(e.expr) {
+				case EConst(CString(s)):
+					{ expr: EConst(CString(s.toUpperCase())), pos: e.pos };
+				case _:
+					ExprTools.map(e, capitalizeStrings);
+			}
+		}
+	**/
+	static public function map( e : Expr, f : Expr -> Expr ) : Expr {
+		return {pos: e.pos, expr: switch(e.expr) {
+			case EConst(_): e.expr;
+			case EArray(e1, e2): EArray(f(e1), f(e2));
+			case EBinop(op, e1, e2): EBinop(op, f(e1), f(e2));
+			case EField(e, field): EField(f(e), field);
+			case EParenthesis(e): EParenthesis(f(e));
+			case EObjectDecl(fields):
+				var ret = [];
+				for (field in fields)
+					ret.push( { field: field.field, expr: f(field.expr) } );
+				EObjectDecl(ret);
+			case EArrayDecl(el): EArrayDecl(ExprArrayTools.map(el, f));
+			case ECall(e, params): ECall(f(e), ExprArrayTools.map(params, f));
+			case ENew(tp, params): ENew(tp, ExprArrayTools.map(params, f));
+			case EUnop(op, postFix, e): EUnop(op, postFix, f(e));
+			case EVars(vars):
+				var ret = [];
+				for (v in vars)
+					ret.push( { name: v.name, type:v.type, expr: opt(v.expr, f) } );
+				EVars(ret);
+			case EBlock(el): EBlock(ExprArrayTools.map(el, f));
+			case EFor(it, expr): EFor(f(it), f(expr));
+			case EIn(e1, e2): EIn(f(e1), f(e2));
+			case EIf(econd, eif, eelse): EIf(f(econd), f(eif), opt(eelse, f));
+			case EWhile(econd, e, normalWhile): EWhile(f(econd), f(e), normalWhile);
+			case EReturn(e): EReturn(opt(e,f));
+			case EUntyped(e): EUntyped(f(e));
+			case EThrow(e): EThrow(f(e));
+			case ECast(e, t): ECast(f(e), t);
+			case EDisplay(e, isCall): EDisplay(f(e), isCall);
+			case ETernary(econd, eif, eelse): ETernary(f(econd), f(eif), f(eelse));
+			case ECheckType(e, t): ECheckType(f(e), t);
+			case EDisplayNew(_),
+				EContinue,
+				EBreak:
+					e.expr;
+			case ETry(e, catches):
+				var ret = [];
+				for (c in catches)
+					ret.push( { name:c.name, type:c.type, expr:f(c.expr) } );
+				ETry(f(e), ret);
+			case ESwitch(e, cases, edef):
+				var ret = [];
+				for (c in cases)
+					ret.push( { expr: opt (c.expr, f), guard: opt(c.guard, f), values: ExprArrayTools.map(c.values, f) } );
+				ESwitch(f(e), ret, opt(edef, f));
+			case EFunction(name, func):
+				var ret = [];
+				for (arg in func.args)
+					ret.push( { name: arg.name, opt: arg.opt, type: arg.type, value: opt(arg.value, f) } );
+				EFunction(name, { args: ret, ret: func.ret, params: func.params, expr: f(func.expr) } );
+			#if !haxe3
+			case EType(e, field): EType(f(e), field);
+			#end
+			case EMeta(m, e): EMeta(m, f(e));
+		}};
+	}
+	
+	static inline function opt(e:Null<Expr>, f : Expr -> Expr):Expr
+		return e == null ? null : f(e)
+		
+	static inline function opt2(e:Null<Expr>, f : Expr -> Void):Void
+		if (e != null) f(e)		
+}
+
+/**
+	This class provides functions on expression arrays for convenience. For a
+	detailed reference on each method, see the documentation of ExprTools.
+ */
+class ExprArrayTools {
+	static public function map( el : Array<Expr>, f : Expr -> Expr):Array<Expr> {
+		var ret = [];
+		for (e in el)
+			ret.push(f(e));
+		return ret;
+	}
+	
+	static public function iter( el : Array<Expr>, f : Expr -> Void):Void {
+		for (e in el)
+			f(e);
+	}
+}

+ 138 - 0
tests/unit/unitstd/haxe/macro/ExprTools.unit.hx

@@ -0,0 +1,138 @@
+var econst = macro 1;
+var econtinue = macro continue;
+var ebreak = macro break;
+var efield = macro "foo".length;
+var eparenthesis = macro (1);
+var euntyped = macro untyped 1;
+var ethrow = macro throw 1;
+var eunop = macro -1;
+var ecast = macro cast 1;
+var emeta = macro @myMeta 1;
+var earray = macro 1[0];
+var ewhile1 = macro while (1) "foo";
+var ewhile2 = macro do "foo" while (1);
+var ebinop = macro 1 + 1;
+var efor = macro for (1) "foo";
+var ein = macro i in 1;
+var evars = macro var x = 1, y = 2;
+var etry = macro try 1 catch (e:Dynamic) "foo" catch(e2:String) "bar";
+var eternary = macro 1 ? 2 : 3;
+var earraydecl = macro [1, 2];
+var enew = macro new String(1, 2);
+var eblock = macro { 1; 2; };
+var eobjectdecl = macro { foo: 1, bar: 2 };
+var ecall = macro foo(1, 2);
+var ereturn = macro return 1;
+var efunction = macro function n(x = 1, y = 2) 3;
+var eswitch = macro switch 1 { case 2,3: case 4 if (5): case 6: };
+
+// iter
+var subject = macro {
+	var p = new neko.io.Process("java", ["-jar", neko.Web.getCwd() + "/java/java.jar"]);
+	if( neko.Web.isModNeko )
+		neko.Web.setHeader("Content-Type","text/plain");
+	try {
+		while( true ) {
+			var c = p.stdout.readByte();
+			neko.Lib.print(StringTools.htmlEscape(String.fromCharCode(c)));
+		}
+	} catch ( e : haxe.io.Eof ) {
+	}
+	neko.Lib.print(StringTools.htmlEscape(p.stderr.readAll().toString()));
+};
+
+var strings = [];
+var upperIdents = [];
+
+function extract(e) {
+	switch(e.expr) {
+		case haxe.macro.Expr.ExprDef.EConst(haxe.macro.Expr.Constant.CString(s)):
+			strings.push(s);
+		case haxe.macro.Expr.ExprDef.EConst(haxe.macro.Expr.Constant.CIdent(s)) if (s.charCodeAt(0) >= 'A'.code && s.charCodeAt(0) <= 'Z'.code):
+			upperIdents.push(s);			
+		case _:
+			haxe.macro.ExprTools.iter(e, extract);
+	}
+}
+extract(subject);
+strings == ["java", "-jar", "/java/java.jar", "Content-Type", "text/plain"];
+upperIdents == ["StringTools", "String", "StringTools"];
+
+var iter = haxe.macro.ExprTools.iter;
+var fail = function(e) throw "I was called";
+function seq(s, e) eq(s, Std.string(e.expr));
+function check(e, exp, ?pos) {
+	function f(e2) {
+		eq(Std.string(e2.expr), exp.shift(), pos);
+	}
+	iter(e, f);
+}
+
+iter(econst, fail);
+iter(econtinue, fail);
+iter(ebreak, fail);
+iter(efield, seq.callback("EConst(CString(foo))"));
+iter(eparenthesis, seq.callback("EConst(CInt(1))"));
+iter(euntyped, seq.callback("EConst(CInt(1))"));
+iter(ethrow, seq.callback("EConst(CInt(1))"));
+iter(eunop, seq.callback("EConst(CInt(1))"));
+iter(ecast, seq.callback("EConst(CInt(1))"));
+iter(emeta, seq.callback("EConst(CInt(1))"));
+check(earray, ["EConst(CInt(1))", "EConst(CInt(0))"]);
+check(ewhile1, ["EConst(CInt(1))", "EConst(CString(foo))"]);
+check(ewhile2, ["EConst(CInt(1))", "EConst(CString(foo))"]);
+check(ebinop, ["EConst(CInt(1))", "EConst(CInt(1))"]);
+check(efor, ["EConst(CInt(1))", "EConst(CString(foo))"]);
+check(ein, ["EConst(CIdent(i))", "EConst(CInt(1))"]);
+check(evars, ["EConst(CInt(1))", "EConst(CInt(2))"]);
+check(etry, ["EConst(CInt(1))", "EConst(CString(foo))", "EConst(CString(bar))"]);
+check(eternary, ["EConst(CInt(1))", "EConst(CInt(2))", "EConst(CInt(3))"]);
+check(earraydecl, ["EConst(CInt(1))", "EConst(CInt(2))"]);
+check(enew, ["EConst(CInt(1))", "EConst(CInt(2))"]);
+check(eblock, ["EConst(CInt(1))", "EConst(CInt(2))"]);
+check(eobjectdecl, ["EConst(CInt(1))", "EConst(CInt(2))"]);
+check(ecall, ["EConst(CIdent(foo))", "EConst(CInt(1))", "EConst(CInt(2))"]);
+check(ereturn, ["EConst(CInt(1))"]);
+check(efunction, ["EConst(CInt(1))", "EConst(CInt(2))", "EConst(CInt(3))"]);
+check(eswitch, ["EConst(CInt(1))", "EConst(CInt(2))", "EConst(CInt(3))", "EConst(CInt(4))", "EConst(CInt(5))", "EConst(CInt(6))"]);
+
+// map
+function wrap(e) return macro ($e);
+function unwrap(e) return switch(e.expr) {
+	case haxe.macro.Expr.ExprDef.EParenthesis(e): e;
+	case _: e;
+}
+var map = haxe.macro.ExprTools.map;
+function check(e, ?pos) {
+	var e2 = map(e, wrap);
+	var e3 = map(e, unwrap);
+	eq(Std.string(e.expr), Std.string(e3.expr), pos);
+}
+map(econst, wrap).expr == econst.expr;
+map(econtinue, wrap).expr == econtinue.expr;
+map(ebreak, wrap).expr == ebreak.expr;
+check(efield);
+check(eparenthesis);
+check(euntyped);
+check(ethrow);
+check(eunop);
+check(ecast);
+check(emeta);
+check(earray);
+check(ewhile1);
+check(ewhile2);
+check(ebinop);
+check(efor);
+check(ein);
+check(evars);
+check(etry);
+check(eternary);
+check(earraydecl);
+check(enew);
+check(eblock);
+check(eobjectdecl);
+check(ecall);
+// we can check these once ExprTools.toString has been added
+//check(efunction);
+//check(eswitch);
+check(ereturn);