315 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
		
		
			
		
	
	
			315 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
|  | /* | ||
|  |  * Copyright (C)2005-2019 Haxe Foundation | ||
|  |  * | ||
|  |  * Permission is hereby granted, free of charge, to any person obtaining a | ||
|  |  * copy of this software and associated documentation files (the "Software"), | ||
|  |  * to deal in the Software without restriction, including without limitation | ||
|  |  * the rights to use, copy, modify, merge, publish, distribute, sublicense, | ||
|  |  * and/or sell copies of the Software, and to permit persons to whom the | ||
|  |  * Software is furnished to do so, subject to the following conditions: | ||
|  |  * | ||
|  |  * The above copyright notice and this permission notice shall be included in | ||
|  |  * all copies or substantial portions of the Software. | ||
|  |  * | ||
|  |  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
|  |  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
|  |  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
|  |  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
|  |  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | ||
|  |  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | ||
|  |  * DEALINGS IN THE SOFTWARE. | ||
|  |  */ | ||
|  | 
 | ||
|  | package haxe.macro; | ||
|  | 
 | ||
|  | import haxe.macro.Expr; | ||
|  | 
 | ||
|  | using Lambda; | ||
|  | 
 | ||
|  | /** | ||
|  | 	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 { | ||
|  | 	/** | ||
|  | 		Converts expression `e` to a human-readable String representation. | ||
|  | 
 | ||
|  | 		The result is guaranteed to be valid Haxe code, but there may be | ||
|  | 		differences from the original lexical syntax. | ||
|  | 	**/ | ||
|  | 	static public function toString(e:Expr):String | ||
|  | 		return new Printer().printExpr(e); | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		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: | ||
|  | 		```haxe | ||
|  | 		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, _), EIs(e, _) | EMeta(_, e): | ||
|  | 				f(e); | ||
|  | 			case EArray(e1, e2), EWhile(e1, e2, _), EBinop(_, e1, e2), EFor(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); | ||
|  | 				} | ||
|  | 				if (edef != null && edef.expr != null) | ||
|  | 					f(edef); | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		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: | ||
|  | 		```haxe | ||
|  | 		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), quotes: field.quotes}); | ||
|  | 					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) { | ||
|  | 						var v2:Var = {name: v.name, type: v.type, expr: opt(v.expr, f)}; | ||
|  | 						if (v.isFinal != null) | ||
|  | 							v2.isFinal = v.isFinal; | ||
|  | 						ret.push(v2); | ||
|  | 					} | ||
|  | 					EVars(ret); | ||
|  | 				case EBlock(el): EBlock(ExprArrayTools.map(el, f)); | ||
|  | 				case EFor(it, expr): EFor(f(it), f(expr)); | ||
|  | 				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 EIs(e, t): EIs(f(e), t); | ||
|  | 				case EDisplay(e, dk): EDisplay(f(e), dk); | ||
|  | 				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, edef == null || edef.expr == null ? edef : f(edef)); | ||
|  | 				case EFunction(kind, 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(kind, { | ||
|  | 						args: ret, | ||
|  | 						ret: func.ret, | ||
|  | 						params: func.params, | ||
|  | 						expr: f(func.expr) | ||
|  | 					}); | ||
|  | 				case EMeta(m, e): EMeta(m, f(e)); | ||
|  | 			} | ||
|  | 		}; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Returns the value `e` represents. | ||
|  | 
 | ||
|  | 		Supported expressions are: | ||
|  | 
 | ||
|  | 		 - `Int`, `Float` and `String` literals | ||
|  | 		 - identifiers `true`, `false` and `null` | ||
|  | 		 - structure declarations if all their fields are values | ||
|  | 		 - array declarations if all their elements are values | ||
|  | 		 - unary operators `-`, `!` and `~` if the operand is a value | ||
|  | 		 - binary operators except `=>`, `...` and assignments | ||
|  | 
 | ||
|  | 		Parentheses, metadata and the `untyped` keyword are ignored. | ||
|  | 
 | ||
|  | 		If any non-value is encountered, an exception of type `String` is | ||
|  | 		thrown. | ||
|  | 
 | ||
|  | 		If `e` is null, the result is unspecified. | ||
|  | 	**/ | ||
|  | 	static public function getValue(e:Expr):Dynamic { | ||
|  | 		return switch (e.expr) { | ||
|  | 			case EConst(CInt(v)): Std.parseInt(v); | ||
|  | 			case EConst(CFloat(v)): Std.parseFloat(v); | ||
|  | 			case EConst(CString(s)): s; | ||
|  | 			case EConst(CIdent("true")): true; | ||
|  | 			case EConst(CIdent("false")): false; | ||
|  | 			case EConst(CIdent("null")): null; | ||
|  | 			case EParenthesis(e1) | EUntyped(e1) | EMeta(_, e1): getValue(e1); | ||
|  | 			case EObjectDecl(fields): | ||
|  | 				var obj = {}; | ||
|  | 				for (field in fields) { | ||
|  | 					Reflect.setField(obj, field.field, getValue(field.expr)); | ||
|  | 				} | ||
|  | 				obj; | ||
|  | 			case EArrayDecl(el): el.map(getValue); | ||
|  | 			case EIf(econd, eif, eelse) | ETernary(econd, eif, eelse): | ||
|  | 				if (eelse == null) { | ||
|  | 					throw "If statements only have a value if the else clause is defined"; | ||
|  | 				} else { | ||
|  | 					var econd:Dynamic = getValue(econd); | ||
|  | 					econd ? getValue(eif) : getValue(eelse); | ||
|  | 				} | ||
|  | 			case EUnop(op, false, e1): | ||
|  | 				var e1:Dynamic = getValue(e1); | ||
|  | 				switch (op) { | ||
|  | 					case OpNot: !e1; | ||
|  | 					case OpNeg: -e1; | ||
|  | 					case OpNegBits: ~e1; | ||
|  | 					case _: throw 'Unsupported expression: $e'; | ||
|  | 				} | ||
|  | 			case EBinop(op, e1, e2): | ||
|  | 				var e1:Dynamic = getValue(e1); | ||
|  | 				var e2:Dynamic = getValue(e2); | ||
|  | 				switch (op) { | ||
|  | 					case OpAdd: e1 + e2; | ||
|  | 					case OpSub: e1 - e2; | ||
|  | 					case OpMult: e1 * e2; | ||
|  | 					case OpDiv: e1 / e2; | ||
|  | 					case OpMod: e1 % e2; | ||
|  | 					case OpEq: e1 == e2; | ||
|  | 					case OpNotEq: e1 != e2; | ||
|  | 					case OpLt: e1 < e2; | ||
|  | 					case OpLte: e1 <= e2; | ||
|  | 					case OpGt: e1 > e2; | ||
|  | 					case OpGte: e1 >= e2; | ||
|  | 					case OpOr: e1 | e2; | ||
|  | 					case OpAnd: e1 & e2; | ||
|  | 					case OpXor: e1 ^ e2; | ||
|  | 					case OpBoolAnd: e1 && e2; | ||
|  | 					case OpBoolOr: e1 || e2; | ||
|  | 					case OpShl: e1 << e2; | ||
|  | 					case OpShr: e1 >> e2; | ||
|  | 					case OpUShr: e1 >>> e2; | ||
|  | 					case _: throw 'Unsupported expression: $e'; | ||
|  | 				} | ||
|  | 			case _: throw 'Unsupported expression: $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); | ||
|  | 	} | ||
|  | } |