/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. */
  25. package haxe;
  26. private enum TemplateExpr {
  27. OpVar( v : String );
  28. OpExpr( expr : Void -> Dynamic );
  29. OpIf( expr : Void -> Dynamic, eif : TemplateExpr, eelse : TemplateExpr );
  30. OpStr( str : String );
  31. OpBlock( l : List<TemplateExpr> );
  32. OpForeach( expr : Void -> Dynamic, loop : TemplateExpr );
  33. OpMacro( name : String, params : List<TemplateExpr> );
  34. }
  35. private typedef Token = {
  36. var s : Bool;
  37. var p : String;
  38. var l : Array<String>;
  39. }
  40. private typedef ExprToken = {
  41. var s : Bool;
  42. var p : String;
  43. }
  44. class Template {
  45. static var splitter = ~/(::[A-Za-z0-9_ ()&|!+=\/><*."-]+::|\$\$([A-Za-z0-9_-]+)\()/;
  46. static var expr_splitter = ~/(\(|\)|[ \r\n\t]*"[^"]*"[ \r\n\t]*|[!+=\/><*.&|-]+)/;
  47. static var expr_trim = ~/^[ ]*([^ ]+)[ ]*$/;
  48. static var expr_int = ~/^[0-9]+$/;
  49. static var expr_float = ~/^([+-]?)(?=\d|,\d)\d*(,\d*)?([Ee]([+-]?\d+))?$/;
  50. public static var globals : Dynamic = {};
  51. var expr : TemplateExpr;
  52. var context : Dynamic;
  53. var macros : Dynamic;
  54. var stack : List<Dynamic>;
  55. var buf : StringBuf;
  56. public function new( str : String ) {
  57. var tokens = parseTokens(str);
  58. expr = parseBlock(tokens);
  59. if( !tokens.isEmpty() )
  60. throw "Unexpected '"+tokens.first().s+"'";
  61. }
  62. public function execute( context : Dynamic, ?macros : Dynamic ) {
  63. this.macros = if( macros == null ) {} else macros;
  64. this.context = context;
  65. stack = new List();
  66. buf = new StringBuf();
  67. run(expr);
  68. return buf.toString();
  69. }
  70. function resolve( v : String ) : Dynamic {
  71. if( Reflect.hasField(context,v) )
  72. return Reflect.field(context,v);
  73. for( ctx in stack )
  74. if( Reflect.hasField(ctx,v) )
  75. return Reflect.field(ctx,v);
  76. if( v == "__current__" )
  77. return context;
  78. return Reflect.field(globals,v);
  79. }
  80. function parseTokens( data : String ) {
  81. var tokens = new List<Token>();
  82. while( splitter.match(data) ) {
  83. var p = splitter.matchedPos();
  84. if( p.pos > 0 )
  85. tokens.add({ p : data.substr(0,p.pos), s : true, l : null });
  86. // : ?
  87. if( data.charCodeAt(p.pos) == 58 ) {
  88. tokens.add({ p : data.substr(p.pos + 2,p.len - 4), s : false, l : null });
  89. data = splitter.matchedRight();
  90. continue;
  91. }
  92. // macro parse
  93. var parp = p.pos + p.len;
  94. var npar = 1;
  95. while( npar > 0 ) {
  96. var c = data.charCodeAt(parp);
  97. if( c == 40 )
  98. npar++;
  99. else if( c == 41 )
  100. npar--;
  101. else if( c == null )
  102. throw "Unclosed macro parenthesis";
  103. parp++;
  104. }
  105. var params = data.substr(p.pos+p.len,parp - (p.pos+p.len) - 1).split(",");
  106. tokens.add({ p : splitter.matched(2), s : false, l : params });
  107. data = data.substr(parp,data.length - parp);
  108. }
  109. if( data.length > 0 )
  110. tokens.add({ p : data, s : true, l : null });
  111. return tokens;
  112. }
  113. function parseBlock( tokens : List<Token> ) {
  114. var l = new List();
  115. while( true ) {
  116. var t = tokens.first();
  117. if( t == null )
  118. break;
  119. if( !t.s && (t.p == "end" || t.p == "else" || t.p.substr(0,7) == "elseif ") )
  120. break;
  121. l.add(parse(tokens));
  122. }
  123. if( l.length == 1 )
  124. return l.first();
  125. return OpBlock(l);
  126. }
  127. function parse( tokens : List<Token> ) {
  128. var t = tokens.pop();
  129. var p = t.p;
  130. if( t.s )
  131. return OpStr(p);
  132. // macro
  133. if( t.l != null ) {
  134. var pe = new List();
  135. for( p in t.l )
  136. pe.add(parseBlock(parseTokens(p)));
  137. return OpMacro(p,pe);
  138. }
  139. // 'end' , 'else', 'elseif' can't be found here
  140. if( p.substr(0,3) == "if " ) {
  141. p = p.substr(3,p.length - 3);
  142. var e = parseExpr(p);
  143. var eif = parseBlock(tokens);
  144. var t = tokens.first();
  145. var eelse;
  146. if( t == null )
  147. throw "Unclosed 'if'";
  148. if( t.p == "end" ) {
  149. tokens.pop();
  150. eelse = null;
  151. } else if( t.p == "else" ) {
  152. tokens.pop();
  153. eelse = parseBlock(tokens);
  154. t = tokens.pop();
  155. if( t == null || t.p != "end" )
  156. throw "Unclosed 'else'";
  157. } else { // elseif
  158. t.p = t.p.substr(4,t.p.length - 4);
  159. eelse = parse(tokens);
  160. }
  161. return OpIf(e,eif,eelse);
  162. }
  163. if( p.substr(0,8) == "foreach " ) {
  164. p = p.substr(8,p.length - 8);
  165. var e = parseExpr(p);
  166. var efor = parseBlock(tokens);
  167. var t = tokens.pop();
  168. if( t == null || t.p != "end" )
  169. throw "Unclosed 'foreach'";
  170. return OpForeach(e,efor);
  171. }
  172. if( expr_splitter.match(p) )
  173. return OpExpr(parseExpr(p));
  174. return OpVar(p);
  175. }
  176. function parseExpr( data : String ) {
  177. var l = new List<ExprToken>();
  178. var expr = data;
  179. while( expr_splitter.match(data) ) {
  180. var p = expr_splitter.matchedPos();
  181. var k = p.pos + p.len;
  182. if( p.pos != 0 )
  183. l.add({ p : data.substr(0,p.pos), s : true });
  184. var p = expr_splitter.matched(0);
  185. l.add({ p : p, s : p.indexOf('"') >= 0 });
  186. data = expr_splitter.matchedRight();
  187. }
  188. if( data.length != 0 )
  189. l.add({ p : data, s : true });
  190. var e;
  191. try {
  192. e = makeExpr(l);
  193. if( !l.isEmpty() )
  194. throw l.first().p;
  195. } catch( s : String ) {
  196. throw "Unexpected '"+s+"' in "+expr;
  197. }
  198. return function() {
  199. try {
  200. return e();
  201. } catch( exc : Dynamic ) {
  202. throw "Error : "+Std.string(exc)+" in "+expr;
  203. }
  204. }
  205. }
  206. function makeConst( v : String ) : Void -> Dynamic {
  207. expr_trim.match(v);
  208. v = expr_trim.matched(1);
  209. if( v.charCodeAt(0) == 34 ) {
  210. var str = v.substr(1,v.length-2);
  211. return function() return str;
  212. }
  213. if( expr_int.match(v) ) {
  214. var i = Std.parseInt(v);
  215. return function() { return i; };
  216. }
  217. if( expr_float.match(v) ) {
  218. var f = Std.parseFloat(v);
  219. return function() { return f; };
  220. }
  221. var me = this;
  222. return function() { return me.resolve(v); };
  223. }
  224. function makePath( e : Void -> Dynamic, l : List<ExprToken> ) {
  225. var p = l.first();
  226. if( p == null || p.p != "." )
  227. return e;
  228. l.pop();
  229. var field = l.pop();
  230. if( field == null || !field.s )
  231. throw field.p;
  232. var f = field.p;
  233. expr_trim.match(f);
  234. f = expr_trim.matched(1);
  235. return makePath(function() { return Reflect.field(e(),f); },l);
  236. }
  237. function makeExpr( l ) {
  238. return makePath(makeExpr2(l),l);
  239. }
  240. function makeExpr2( l : List<ExprToken> ) : Void -> Dynamic {
  241. var p = l.pop();
  242. if( p == null )
  243. throw "<eof>";
  244. if( p.s )
  245. return makeConst(p.p);
  246. switch( p.p ) {
  247. case "(":
  248. var e1 = makeExpr(l);
  249. var p = l.pop();
  250. if( p == null || p.s )
  251. throw p.p;
  252. if( p.p == ")" )
  253. return e1;
  254. var e2 = makeExpr(l);
  255. var p2 = l.pop();
  256. if( p2 == null || p2.p != ")" )
  257. throw p2.p;
  258. return switch( p.p ) {
  259. case "+": function() { return cast e1() + e2(); };
  260. case "-": function() { return cast e1() - e2(); };
  261. case "*": function() { return cast e1() * e2(); };
  262. case "/": function() { return cast e1() / e2(); };
  263. case ">": function() { return cast e1() > e2(); };
  264. case "<": function() { return cast e1() < e2(); };
  265. case ">=": function() { return cast e1() >= e2(); };
  266. case "<=": function() { return cast e1() <= e2(); };
  267. case "==": function() { return cast e1() == e2(); };
  268. case "!=": function() { return cast e1() != e2(); };
  269. case "&&": function() { return cast e1() && e2(); };
  270. case "||": function() { return cast e1() || e2(); };
  271. default: throw "Unknown operation "+p.p;
  272. }
  273. case "!":
  274. var e = makeExpr(l);
  275. return function() {
  276. var v : Dynamic = e();
  277. return (v == null || v == false);
  278. };
  279. case "-":
  280. var e = makeExpr(l);
  281. return function() { return -e(); };
  282. }
  283. throw p.p;
  284. }
  285. function run( e : TemplateExpr ) {
  286. switch( e ) {
  287. case OpVar(v):
  288. buf.add(Std.string(resolve(v)));
  289. case OpExpr(e):
  290. buf.add(Std.string(e()));
  291. case OpIf(e,eif,eelse):
  292. var v : Dynamic = e();
  293. if( v == null || v == false ) {
  294. if( eelse != null ) run(eelse);
  295. } else
  296. run(eif);
  297. case OpStr(str):
  298. buf.add(str);
  299. case OpBlock(l):
  300. for( e in l )
  301. run(e);
  302. case OpForeach(e,loop):
  303. var v : Dynamic = e();
  304. try {
  305. if( v.hasNext == null ) {
  306. var x : Dynamic = v.iterator();
  307. if( x.hasNext == null ) throw null;
  308. v = x;
  309. }
  310. } catch( e : Dynamic ) {
  311. throw "Cannot iter on " + v;
  312. }
  313. stack.push(context);
  314. var v : Iterator<Dynamic> = v;
  315. for( ctx in v ) {
  316. context = ctx;
  317. run(loop);
  318. }
  319. context = stack.pop();
  320. case OpMacro(m,params):
  321. var v : Dynamic = Reflect.field(macros,m);
  322. var pl = new Array<Dynamic>();
  323. var old = buf;
  324. pl.push(resolve);
  325. for( p in params ) {
  326. switch( p ) {
  327. case OpVar(v): pl.push(resolve(v));
  328. default:
  329. buf = new StringBuf();
  330. run(p);
  331. pl.push(buf.toString());
  332. }
  333. }
  334. buf = old;
  335. try {
  336. buf.add(Std.string(Reflect.callMethod(macros,v,pl)));
  337. } catch( e : Dynamic ) {
  338. var plstr = try pl.join(",") catch( e : Dynamic ) "???";
  339. var msg = "Macro call "+m+"("+plstr+") failed ("+Std.string(e)+")";
  340. #if neko
  341. neko.Lib.rethrow(msg);
  342. #else
  343. throw msg;
  344. #end
  345. }
  346. }
  347. }
  348. }