PageRenderTime 56ms CodeModel.GetById 14ms app.highlight 36ms RepoModel.GetById 1ms app.codeStats 1ms

/bin/std/haxe/Template.hx

http://github.com/Yoomee/clippy
Haxe | 370 lines | 326 code | 16 blank | 28 comment | 59 complexity | 47b6f92cac1a683653f1bb967e020474 MD5 | raw file
  1/*
  2 * Copyright (c) 2005, The haXe Project Contributors
  3 * All rights reserved.
  4 * Redistribution and use in source and binary forms, with or without
  5 * modification, are permitted provided that the following conditions are met:
  6 *
  7 *   - Redistributions of source code must retain the above copyright
  8 *     notice, this list of conditions and the following disclaimer.
  9 *   - Redistributions in binary form must reproduce the above copyright
 10 *     notice, this list of conditions and the following disclaimer in the
 11 *     documentation and/or other materials provided with the distribution.
 12 *
 13 * THIS SOFTWARE IS PROVIDED BY THE HAXE PROJECT CONTRIBUTORS "AS IS" AND ANY
 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 15 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 16 * DISCLAIMED. IN NO EVENT SHALL THE HAXE PROJECT CONTRIBUTORS BE LIABLE FOR
 17 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 18 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 19 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 20 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 21 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 22 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 23 * DAMAGE.
 24 */
 25package haxe;
 26
 27private enum TemplateExpr {
 28	OpVar( v : String );
 29	OpExpr( expr :  Void -> Dynamic );
 30	OpIf( expr : Void -> Dynamic,  eif : TemplateExpr, eelse : TemplateExpr );
 31	OpStr( str : String );
 32	OpBlock( l : List<TemplateExpr> );
 33	OpForeach( expr : Void -> Dynamic, loop : TemplateExpr );
 34	OpMacro( name : String, params : List<TemplateExpr> );
 35}
 36
 37private typedef Token = {
 38	var s : Bool;
 39	var p : String;
 40	var l : Array<String>;
 41}
 42
 43private typedef ExprToken = {
 44	var s : Bool;
 45	var p : String;
 46}
 47
 48class Template {
 49
 50	static var splitter = ~/(::[A-Za-z0-9_ ()&|!+=\/><*."-]+::|\$\$([A-Za-z0-9_-]+)\()/;
 51	static var expr_splitter = ~/(\(|\)|[ \r\n\t]*"[^"]*"[ \r\n\t]*|[!+=\/><*.&|-]+)/;
 52	static var expr_trim = ~/^[ ]*([^ ]+)[ ]*$/;
 53	static var expr_int = ~/^[0-9]+$/;
 54	static var expr_float = ~/^([+-]?)(?=\d|,\d)\d*(,\d*)?([Ee]([+-]?\d+))?$/;
 55
 56	public static var globals : Dynamic = {};
 57
 58	var expr : TemplateExpr;
 59	var context : Dynamic;
 60	var macros : Dynamic;
 61	var stack : List<Dynamic>;
 62	var buf : StringBuf;
 63
 64	public function new( str : String ) {
 65		var tokens = parseTokens(str);
 66		expr = parseBlock(tokens);
 67		if( !tokens.isEmpty() )
 68			throw "Unexpected '"+tokens.first().s+"'";
 69	}
 70
 71	public function execute( context : Dynamic, ?macros : Dynamic ) {
 72		this.macros = if( macros == null ) {} else macros;
 73		this.context = context;
 74		stack = new List();
 75		buf = new StringBuf();
 76		run(expr);
 77		return buf.toString();
 78	}
 79
 80	function resolve( v : String ) : Dynamic {
 81		if( Reflect.hasField(context,v) )
 82			return Reflect.field(context,v);
 83		for( ctx in stack )
 84			if( Reflect.hasField(ctx,v) )
 85				return Reflect.field(ctx,v);
 86		if( v == "__current__" )
 87			return context;
 88		return Reflect.field(globals,v);
 89	}
 90
 91	function parseTokens( data : String ) {
 92		var tokens = new List<Token>();
 93		while( splitter.match(data) ) {
 94			var p = splitter.matchedPos();
 95			if( p.pos > 0 )
 96				tokens.add({ p : data.substr(0,p.pos), s : true, l : null });
 97
 98			// : ?
 99			if( data.charCodeAt(p.pos) == 58 ) {
100				tokens.add({ p : data.substr(p.pos + 2,p.len - 4), s : false, l : null });
101				data = splitter.matchedRight();
102				continue;
103			}
104
105			// macro parse
106			var parp = p.pos + p.len;
107			var npar = 1;
108			while( npar > 0 ) {
109				var c = data.charCodeAt(parp);
110				if( c == 40 )
111					npar++;
112				else if( c == 41 )
113					npar--;
114				else if( c == null )
115					throw "Unclosed macro parenthesis";
116				parp++;
117			}
118			var params = data.substr(p.pos+p.len,parp - (p.pos+p.len) - 1).split(",");
119			tokens.add({ p : splitter.matched(2), s : false, l : params });
120			data = data.substr(parp,data.length - parp);
121		}
122		if( data.length > 0 )
123			tokens.add({ p : data, s : true, l : null });
124		return tokens;
125	}
126
127	function parseBlock( tokens : List<Token> ) {
128		var l = new List();
129		while( true ) {
130			var t = tokens.first();
131			if( t == null )
132				break;
133			if( !t.s && (t.p == "end" || t.p == "else" || t.p.substr(0,7) == "elseif ") )
134				break;
135			l.add(parse(tokens));
136		}
137		if( l.length == 1 )
138			return l.first();
139		return OpBlock(l);
140	}
141
142	function parse( tokens : List<Token> ) {
143		var t = tokens.pop();
144		var p = t.p;
145		if( t.s )
146			return OpStr(p);
147		// macro
148		if( t.l != null ) {
149			var pe = new List();
150			for( p in t.l )
151				pe.add(parseBlock(parseTokens(p)));
152			return OpMacro(p,pe);
153		}
154		// 'end' , 'else', 'elseif' can't be found here
155		if( p.substr(0,3) == "if " ) {
156			p = p.substr(3,p.length - 3);
157			var e = parseExpr(p);
158			var eif = parseBlock(tokens);
159			var t = tokens.first();
160			var eelse;
161			if( t == null )
162				throw "Unclosed 'if'";
163			if( t.p == "end" ) {
164				tokens.pop();
165				eelse = null;
166			} else if( t.p == "else" ) {
167				tokens.pop();
168				eelse = parseBlock(tokens);
169				t = tokens.pop();
170				if( t == null || t.p != "end" )
171					throw "Unclosed 'else'";
172			} else { // elseif
173				t.p = t.p.substr(4,t.p.length - 4);
174				eelse = parse(tokens);
175			}
176			return OpIf(e,eif,eelse);
177		}
178		if( p.substr(0,8) == "foreach " ) {
179			p = p.substr(8,p.length - 8);
180			var e = parseExpr(p);
181			var efor = parseBlock(tokens);
182			var t = tokens.pop();
183			if( t == null || t.p != "end" )
184				throw "Unclosed 'foreach'";
185			return OpForeach(e,efor);
186		}
187		if( expr_splitter.match(p) )
188			return OpExpr(parseExpr(p));
189		return OpVar(p);
190	}
191
192	function parseExpr( data : String ) {
193		var l = new List<ExprToken>();
194		var expr = data;
195		while( expr_splitter.match(data) ) {
196			var p = expr_splitter.matchedPos();
197			var k = p.pos + p.len;
198			if( p.pos != 0 )
199				l.add({ p : data.substr(0,p.pos), s : true });
200			var p = expr_splitter.matched(0);
201			l.add({ p : p, s : p.indexOf('"') >= 0 });
202			data = expr_splitter.matchedRight();
203		}
204		if( data.length != 0 )
205			l.add({ p : data, s : true });
206		var e;
207		try {
208			e = makeExpr(l);
209			if( !l.isEmpty() )
210				throw l.first().p;
211		} catch( s : String ) {
212			throw "Unexpected '"+s+"' in "+expr;
213		}
214		return function() {
215			try {
216				return e();
217			} catch( exc : Dynamic ) {
218				throw "Error : "+Std.string(exc)+" in "+expr;
219			}
220		}
221	}
222
223	function makeConst( v : String ) : Void -> Dynamic {
224		expr_trim.match(v);
225		v = expr_trim.matched(1);
226		if( v.charCodeAt(0) == 34 ) {
227			var str = v.substr(1,v.length-2);
228			return function() return str;
229		}
230		if( expr_int.match(v) ) {
231			var i = Std.parseInt(v);
232			return function() { return i; };
233		}
234		if( expr_float.match(v) ) {
235			var f = Std.parseFloat(v);
236			return function() { return f; };
237		}
238		var me = this;
239		return function() { return me.resolve(v); };
240	}
241
242	function makePath( e : Void -> Dynamic, l : List<ExprToken> ) {
243		var p = l.first();
244		if( p == null || p.p != "." )
245			return e;
246		l.pop();
247		var field = l.pop();
248		if( field == null || !field.s )
249			throw field.p;
250		var f = field.p;
251		expr_trim.match(f);
252		f = expr_trim.matched(1);
253		return makePath(function() { return Reflect.field(e(),f); },l);
254	}
255
256	function makeExpr( l ) {
257		return makePath(makeExpr2(l),l);
258	}
259
260	function makeExpr2( l : List<ExprToken> ) : Void -> Dynamic {
261		var p = l.pop();
262		if( p == null )
263			throw "<eof>";
264		if( p.s )
265			return makeConst(p.p);
266		switch( p.p ) {
267		case "(":
268			var e1 = makeExpr(l);
269			var p = l.pop();
270			if( p == null || p.s )
271				throw p.p;
272			if( p.p == ")" )
273				return e1;
274			var e2 = makeExpr(l);
275			var p2 = l.pop();
276			if( p2 == null || p2.p != ")" )
277				throw p2.p;
278			return switch( p.p ) {
279			case "+": function() { return cast e1() + e2(); };
280			case "-": function() { return cast e1() - e2(); };
281			case "*": function() { return cast e1() * e2(); };
282			case "/": function() { return cast e1() / e2(); };
283			case ">": function() { return cast e1() > e2(); };
284			case "<": function() { return cast e1() < e2(); };
285			case ">=": function() { return cast e1() >= e2(); };
286			case "<=": function() { return cast e1() <= e2(); };
287			case "==": function() { return cast e1() == e2(); };
288			case "!=": function() { return cast e1() != e2(); };
289			case "&&": function() { return cast e1() && e2(); };
290			case "||": function() { return cast e1() || e2(); };
291			default: throw "Unknown operation "+p.p;
292			}
293		case "!":
294			var e = makeExpr(l);
295			return function() {
296				var v : Dynamic = e();
297				return (v == null || v == false);
298			};
299		case "-":
300			var e = makeExpr(l);
301			return function() { return -e(); };
302		}
303		throw p.p;
304	}
305
306	function run( e : TemplateExpr ) {
307		switch( e ) {
308		case OpVar(v):
309			buf.add(Std.string(resolve(v)));
310		case OpExpr(e):
311			buf.add(Std.string(e()));
312		case OpIf(e,eif,eelse):
313			var v : Dynamic = e();
314			if( v == null || v == false ) {
315				if( eelse != null ) run(eelse);
316			} else
317				run(eif);
318		case OpStr(str):
319			buf.add(str);
320		case OpBlock(l):
321			for( e in l )
322				run(e);
323		case OpForeach(e,loop):
324			var v : Dynamic = e();
325			try {
326				if( v.hasNext == null ) {
327					var x : Dynamic = v.iterator();
328					if( x.hasNext == null ) throw null;
329					v = x;
330				}
331			} catch( e : Dynamic ) {
332				throw "Cannot iter on " + v;
333			}
334			stack.push(context);
335			var v : Iterator<Dynamic> = v;
336			for( ctx in v ) {
337				context = ctx;
338				run(loop);
339			}
340			context = stack.pop();
341		case OpMacro(m,params):
342			var v : Dynamic = Reflect.field(macros,m);
343			var pl = new Array<Dynamic>();
344			var old = buf;
345			pl.push(resolve);
346			for( p in params ) {
347				switch( p ) {
348				case OpVar(v): pl.push(resolve(v));
349				default:
350					buf = new StringBuf();
351					run(p);
352					pl.push(buf.toString());
353				}
354			}
355			buf = old;
356			try {
357				buf.add(Std.string(Reflect.callMethod(macros,v,pl)));
358			} catch( e : Dynamic ) {
359				var plstr = try pl.join(",") catch( e : Dynamic ) "???";
360				var msg = "Macro call "+m+"("+plstr+") failed ("+Std.string(e)+")";
361				#if neko
362				neko.Lib.rethrow(msg);
363				#else
364				throw msg;
365				#end
366			}
367		}
368	}
369
370}