| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417 | /* * Copyright (C)2005-2017 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;private enum TemplateExpr {	OpVar( v : String );	OpExpr( expr :  Void -> Dynamic );	OpIf( expr : Void -> Dynamic,  eif : TemplateExpr, eelse : TemplateExpr );	OpStr( str : String );	OpBlock( l : List<TemplateExpr> );	OpForeach( expr : Void -> Dynamic, loop : TemplateExpr );	OpMacro( name : String, params : List<TemplateExpr> );}private typedef Token = {	var s : Bool;	var p : String;	var l : Array<String>;}private typedef ExprToken = {	var s : Bool;	var p : String;}/**	Template provides a basic templating mechanism to replace values in a source	String, and to have some basic logic.	A complete documentation of the supported syntax is available at:	<https://haxe.org/manual/std-template.html>**/class Template {	static var splitter = ~/(::[A-Za-z0-9_ ()&|!+=\/><*."-]+::|\$\$([A-Za-z0-9_-]+)\()/;	static var expr_splitter = ~/(\(|\)|[ \r\n\t]*"[^"]*"[ \r\n\t]*|[!+=\/><*.&|-]+)/;	static var expr_trim = ~/^[ ]*([^ ]+)[ ]*$/;	static var expr_int = ~/^[0-9]+$/;	static var expr_float = ~/^([+-]?)(?=\d|,\d)\d*(,\d*)?([Ee]([+-]?\d+))?$/;	/**		Global replacements which are used across all Template instances. This		has lower priority than the context argument of execute().	**/	public static var globals : Dynamic = {};	var expr : TemplateExpr;	var context : Dynamic;	var macros : Dynamic;	var stack : List<Dynamic>;	var buf : StringBuf;	/**		Creates a new Template instance from `str`.		`str` is parsed into tokens, which are stored for internal use. This		means that multiple execute() operations on a single Template instance		are more efficient than one execute() operations on multiple Template		instances.		If `str` is null, the result is unspecified.	**/	public function new( str : String ) {		var tokens = parseTokens(str);		expr = parseBlock(tokens);		if( !tokens.isEmpty() )			throw "Unexpected '"+tokens.first().s+"'";	}	/**		Executes `this` Template, taking into account `context` for		replacements and `macros` for callback functions.		If `context` has a field 'name', its value replaces all occurrences of		::name:: in the Template. Otherwise Template.globals is checked instead,		If 'name' is not a field of that either, ::name:: is replaced with null.		If `macros` has a field 'name', all occurrences of $$name(args) are		replaced with the result of calling that field. The first argument is		always the resolve() method, followed by the given arguments.		If `macros` has no such field, the result is unspecified.		If `context` is null, the result is unspecified. If `macros` is null,		no macros are used.	**/	public function execute( context : Dynamic, ?macros : Dynamic ):String {		this.macros = if( macros == null ) {} else macros;		this.context = context;		stack = new List();		buf = new StringBuf();		run(expr);		return buf.toString();	}	function resolve( v : String ) : Dynamic {		if( v == "__current__" )			return context;		var value = Reflect.getProperty(context, v);		if( value != null || Reflect.hasField(context,v) )			return value;		for( ctx in stack ) {			value = Reflect.getProperty(ctx,v);			if( value != null || Reflect.hasField(ctx,v) )				return value;		}		return Reflect.field(globals,v);	}	function parseTokens( data : String ) {		var tokens = new List<Token>();		while( splitter.match(data) ) {			var p = splitter.matchedPos();			if( p.pos > 0 )				tokens.add({ p : data.substr(0,p.pos), s : true, l : null });			// : ?			if( data.charCodeAt(p.pos) == 58 ) {				tokens.add({ p : data.substr(p.pos + 2,p.len - 4), s : false, l : null });				data = splitter.matchedRight();				continue;			}			// macro parse			var parp = p.pos + p.len;			var npar = 1;			var params = [];			var part = "";			while( true ) {				var c = data.charCodeAt(parp);				parp++;				if( c == 40 ) {					npar++;				} else if( c == 41 ) {					npar--;					if (npar <= 0) break;				} else if( c == null ){					throw "Unclosed macro parenthesis";				}				if ( c == 44 && npar == 1) {					params.push(part);					part = "";				} else {					part += String.fromCharCode(c);				}			}			params.push(part);			tokens.add({ p : splitter.matched(2), s : false, l : params });			data = data.substr(parp,data.length - parp);		}		if( data.length > 0 )			tokens.add({ p : data, s : true, l : null });		return tokens;	}	function parseBlock( tokens : List<Token> ) {		var l = new List();		while( true ) {			var t = tokens.first();			if( t == null )				break;			if( !t.s && (t.p == "end" || t.p == "else" || t.p.substr(0,7) == "elseif ") )				break;			l.add(parse(tokens));		}		if( l.length == 1 )			return l.first();		return OpBlock(l);	}	function parse( tokens : List<Token> ) {		var t = tokens.pop();		var p = t.p;		if( t.s )			return OpStr(p);		// macro		if( t.l != null ) {			var pe = new List();			for( p in t.l )				pe.add(parseBlock(parseTokens(p)));			return OpMacro(p,pe);		}		// 'end' , 'else', 'elseif' can't be found here		if( p.substr(0,3) == "if " ) {			p = p.substr(3,p.length - 3);			var e = parseExpr(p);			var eif = parseBlock(tokens);			var t = tokens.first();			var eelse;			if( t == null )				throw "Unclosed 'if'";			if( t.p == "end" ) {				tokens.pop();				eelse = null;			} else if( t.p == "else" ) {				tokens.pop();				eelse = parseBlock(tokens);				t = tokens.pop();				if( t == null || t.p != "end" )					throw "Unclosed 'else'";			} else { // elseif				t.p = t.p.substr(4,t.p.length - 4);				eelse = parse(tokens);			}			return OpIf(e,eif,eelse);		}		if( p.substr(0,8) == "foreach " ) {			p = p.substr(8,p.length - 8);			var e = parseExpr(p);			var efor = parseBlock(tokens);			var t = tokens.pop();			if( t == null || t.p != "end" )				throw "Unclosed 'foreach'";			return OpForeach(e,efor);		}		if( expr_splitter.match(p) )			return OpExpr(parseExpr(p));		return OpVar(p);	}	function parseExpr( data : String ) {		var l = new List<ExprToken>();		var expr = data;		while( expr_splitter.match(data) ) {			var p = expr_splitter.matchedPos();			var k = p.pos + p.len;			if( p.pos != 0 )				l.add({ p : data.substr(0,p.pos), s : true });			var p = expr_splitter.matched(0);			l.add({ p : p, s : p.indexOf('"') >= 0 });			data = expr_splitter.matchedRight();		}		if( data.length != 0 )			l.add({ p : data, s : true });		var e:Void->Dynamic;		try {			e = makeExpr(l);			if( !l.isEmpty() )				throw l.first().p;		} catch( s : String ) {			throw "Unexpected '"+s+"' in "+expr;		}		return function() {			try {				return e();			} catch( exc : Dynamic ) {				throw "Error : "+Std.string(exc)+" in "+expr;			}		}	}	function makeConst( v : String ) : Void -> Dynamic {		expr_trim.match(v);		v = expr_trim.matched(1);		if( v.charCodeAt(0) == 34 ) {			var str = v.substr(1,v.length-2);			return function() return str;		}		if( expr_int.match(v) ) {			var i = Std.parseInt(v);			return function() { return i; };		}		if( expr_float.match(v) ) {			var f = Std.parseFloat(v);			return function() { return f; };		}		var me = this;		return function() { return me.resolve(v); };	}	function makePath( e : Void -> Dynamic, l : List<ExprToken> ) {		var p = l.first();		if( p == null || p.p != "." )			return e;		l.pop();		var field = l.pop();		if( field == null || !field.s )			throw field.p;		var f = field.p;		expr_trim.match(f);		f = expr_trim.matched(1);		return makePath(function() { return Reflect.field(e(),f); },l);	}	function makeExpr( l ) {		return makePath(makeExpr2(l),l);	}	function makeExpr2( l : List<ExprToken> ) : Void -> Dynamic {		var p = l.pop();		if( p == null )			throw "<eof>";		if( p.s )			return makeConst(p.p);		switch( p.p ) {		case "(":			var e1:Dynamic = makeExpr(l);			var p = l.pop();			if( p == null || p.s )				throw p;			if( p.p == ")" )				return e1;			var e2:Dynamic = makeExpr(l);			var p2 = l.pop();			if( p2 == null || p2.p != ")" )				throw p2;			return switch( p.p ) {			case "+": function() { return cast e1() + e2(); };			case "-": function() { return cast e1() - e2(); };			case "*": function() { return cast e1() * e2(); };			case "/": function() { return cast e1() / e2(); };			case ">": function() { return cast e1() > e2(); };			case "<": function() { return cast e1() < e2(); };			case ">=": function() { return cast e1() >= e2(); };			case "<=": function() { return cast e1() <= e2(); };			case "==": function() { return cast e1() == e2(); };			case "!=": function() { return cast e1() != e2(); };			case "&&": function() { return cast e1() && e2(); };			case "||": function() { return cast e1() || e2(); };			default: throw "Unknown operation "+p.p;			}		case "!":			var e : Void->Dynamic = makeExpr(l);			return function() {				var v : Dynamic = e();				return (v == null || v == false);			};		case "-":			var e = makeExpr(l);			return function() { return -e(); };		}		throw p.p;	}	function run( e : TemplateExpr ) {		switch( e ) {		case OpVar(v):			buf.add(Std.string(resolve(v)));		case OpExpr(e):			buf.add(Std.string(e()));		case OpIf(e,eif,eelse):			var v : Dynamic = e();			if( v == null || v == false ) {				if( eelse != null ) run(eelse);			} else				run(eif);		case OpStr(str):			buf.add(str);		case OpBlock(l):			for( e in l )				run(e);		case OpForeach(e,loop):			var v : Dynamic = e();			try {				var x : Dynamic = v.iterator();				if( x.hasNext == null ) throw null;				v = x;			} catch( e : Dynamic ) try {				if( v.hasNext == null ) throw null;			} catch( e : Dynamic ) {				throw "Cannot iter on " + v;			}			stack.push(context);			var v : Iterator<Dynamic> = v;			for( ctx in v ) {				context = ctx;				run(loop);			}			context = stack.pop();		case OpMacro(m,params):			var v : Dynamic = Reflect.field(macros,m);			var pl = new Array<Dynamic>();			var old = buf;			pl.push(resolve);			for( p in params ) {				switch( p ) {				case OpVar(v): pl.push(resolve(v));				default:					buf = new StringBuf();					run(p);					pl.push(buf.toString());				}			}			buf = old;			try {				buf.add(Std.string(Reflect.callMethod(macros,v,pl)));			} catch( e : Dynamic ) {				var plstr = try pl.join(",") catch( e : Dynamic ) "???";				var msg = "Macro call "+m+"("+plstr+") failed ("+Std.string(e)+")";				#if neko				neko.Lib.rethrow(msg);				#else				throw msg;				#end			}		}	}}
 |