Template.hx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  1. /*
  2. * Copyright (C)2005-2017 Haxe Foundation
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a
  5. * copy of this software and associated documentation files (the "Software"),
  6. * to deal in the Software without restriction, including without limitation
  7. * the rights to use, copy, modify, merge, publish, distribute, sublicense,
  8. * and/or sell copies of the Software, and to permit persons to whom the
  9. * Software is furnished to do so, subject to the following conditions:
  10. *
  11. * The above copyright notice and this permission notice shall be included in
  12. * all copies or substantial portions of the Software.
  13. *
  14. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  20. * DEALINGS IN THE SOFTWARE.
  21. */
  22. package haxe;
  23. private enum TemplateExpr {
  24. OpVar( v : String );
  25. OpExpr( expr : Void -> Dynamic );
  26. OpIf( expr : Void -> Dynamic, eif : TemplateExpr, eelse : TemplateExpr );
  27. OpStr( str : String );
  28. OpBlock( l : List<TemplateExpr> );
  29. OpForeach( expr : Void -> Dynamic, loop : TemplateExpr );
  30. OpMacro( name : String, params : List<TemplateExpr> );
  31. }
  32. private typedef Token = {
  33. var s : Bool;
  34. var p : String;
  35. var l : Array<String>;
  36. }
  37. private typedef ExprToken = {
  38. var s : Bool;
  39. var p : String;
  40. }
  41. /**
  42. Template provides a basic templating mechanism to replace values in a source
  43. String, and to have some basic logic.
  44. A complete documentation of the supported syntax is available at:
  45. <https://haxe.org/manual/std-template.html>
  46. **/
  47. class Template {
  48. static var splitter = ~/(::[A-Za-z0-9_ ()&|!+=\/><*."-]+::|\$\$([A-Za-z0-9_-]+)\()/;
  49. static var expr_splitter = ~/(\(|\)|[ \r\n\t]*"[^"]*"[ \r\n\t]*|[!+=\/><*.&|-]+)/;
  50. static var expr_trim = ~/^[ ]*([^ ]+)[ ]*$/;
  51. static var expr_int = ~/^[0-9]+$/;
  52. static var expr_float = ~/^([+-]?)(?=\d|,\d)\d*(,\d*)?([Ee]([+-]?\d+))?$/;
  53. /**
  54. Global replacements which are used across all Template instances. This
  55. has lower priority than the context argument of execute().
  56. **/
  57. public static var globals : Dynamic = {};
  58. var expr : TemplateExpr;
  59. var context : Dynamic;
  60. var macros : Dynamic;
  61. var stack : List<Dynamic>;
  62. var buf : StringBuf;
  63. /**
  64. Creates a new Template instance from `str`.
  65. `str` is parsed into tokens, which are stored for internal use. This
  66. means that multiple execute() operations on a single Template instance
  67. are more efficient than one execute() operations on multiple Template
  68. instances.
  69. If `str` is null, the result is unspecified.
  70. **/
  71. public function new( str : String ) {
  72. var tokens = parseTokens(str);
  73. expr = parseBlock(tokens);
  74. if( !tokens.isEmpty() )
  75. throw "Unexpected '"+tokens.first().s+"'";
  76. }
  77. /**
  78. Executes `this` Template, taking into account `context` for
  79. replacements and `macros` for callback functions.
  80. If `context` has a field 'name', its value replaces all occurrences of
  81. ::name:: in the Template. Otherwise Template.globals is checked instead,
  82. If 'name' is not a field of that either, ::name:: is replaced with null.
  83. If `macros` has a field 'name', all occurrences of $$name(args) are
  84. replaced with the result of calling that field. The first argument is
  85. always the resolve() method, followed by the given arguments.
  86. If `macros` has no such field, the result is unspecified.
  87. If `context` is null, the result is unspecified. If `macros` is null,
  88. no macros are used.
  89. **/
  90. public function execute( context : Dynamic, ?macros : Dynamic ):String {
  91. this.macros = if( macros == null ) {} else macros;
  92. this.context = context;
  93. stack = new List();
  94. buf = new StringBuf();
  95. run(expr);
  96. return buf.toString();
  97. }
  98. function resolve( v : String ) : Dynamic {
  99. if( v == "__current__" )
  100. return context;
  101. var value = Reflect.getProperty(context, v);
  102. if( value != null || Reflect.hasField(context,v) )
  103. return value;
  104. for( ctx in stack ) {
  105. value = Reflect.getProperty(ctx,v);
  106. if( value != null || Reflect.hasField(ctx,v) )
  107. return value;
  108. }
  109. return Reflect.field(globals,v);
  110. }
  111. function parseTokens( data : String ) {
  112. var tokens = new List<Token>();
  113. while( splitter.match(data) ) {
  114. var p = splitter.matchedPos();
  115. if( p.pos > 0 )
  116. tokens.add({ p : data.substr(0,p.pos), s : true, l : null });
  117. // : ?
  118. if( data.charCodeAt(p.pos) == 58 ) {
  119. tokens.add({ p : data.substr(p.pos + 2,p.len - 4), s : false, l : null });
  120. data = splitter.matchedRight();
  121. continue;
  122. }
  123. // macro parse
  124. var parp = p.pos + p.len;
  125. var npar = 1;
  126. var params = [];
  127. var part = "";
  128. while( true ) {
  129. var c = data.charCodeAt(parp);
  130. parp++;
  131. if( c == 40 ) {
  132. npar++;
  133. } else if( c == 41 ) {
  134. npar--;
  135. if (npar <= 0) break;
  136. } else if( c == null ){
  137. throw "Unclosed macro parenthesis";
  138. }
  139. if ( c == 44 && npar == 1) {
  140. params.push(part);
  141. part = "";
  142. } else {
  143. part += String.fromCharCode(c);
  144. }
  145. }
  146. params.push(part);
  147. tokens.add({ p : splitter.matched(2), s : false, l : params });
  148. data = data.substr(parp,data.length - parp);
  149. }
  150. if( data.length > 0 )
  151. tokens.add({ p : data, s : true, l : null });
  152. return tokens;
  153. }
  154. function parseBlock( tokens : List<Token> ) {
  155. var l = new List();
  156. while( true ) {
  157. var t = tokens.first();
  158. if( t == null )
  159. break;
  160. if( !t.s && (t.p == "end" || t.p == "else" || t.p.substr(0,7) == "elseif ") )
  161. break;
  162. l.add(parse(tokens));
  163. }
  164. if( l.length == 1 )
  165. return l.first();
  166. return OpBlock(l);
  167. }
  168. function parse( tokens : List<Token> ) {
  169. var t = tokens.pop();
  170. var p = t.p;
  171. if( t.s )
  172. return OpStr(p);
  173. // macro
  174. if( t.l != null ) {
  175. var pe = new List();
  176. for( p in t.l )
  177. pe.add(parseBlock(parseTokens(p)));
  178. return OpMacro(p,pe);
  179. }
  180. // 'end' , 'else', 'elseif' can't be found here
  181. if( p.substr(0,3) == "if " ) {
  182. p = p.substr(3,p.length - 3);
  183. var e = parseExpr(p);
  184. var eif = parseBlock(tokens);
  185. var t = tokens.first();
  186. var eelse;
  187. if( t == null )
  188. throw "Unclosed 'if'";
  189. if( t.p == "end" ) {
  190. tokens.pop();
  191. eelse = null;
  192. } else if( t.p == "else" ) {
  193. tokens.pop();
  194. eelse = parseBlock(tokens);
  195. t = tokens.pop();
  196. if( t == null || t.p != "end" )
  197. throw "Unclosed 'else'";
  198. } else { // elseif
  199. t.p = t.p.substr(4,t.p.length - 4);
  200. eelse = parse(tokens);
  201. }
  202. return OpIf(e,eif,eelse);
  203. }
  204. if( p.substr(0,8) == "foreach " ) {
  205. p = p.substr(8,p.length - 8);
  206. var e = parseExpr(p);
  207. var efor = parseBlock(tokens);
  208. var t = tokens.pop();
  209. if( t == null || t.p != "end" )
  210. throw "Unclosed 'foreach'";
  211. return OpForeach(e,efor);
  212. }
  213. if( expr_splitter.match(p) )
  214. return OpExpr(parseExpr(p));
  215. return OpVar(p);
  216. }
  217. function parseExpr( data : String ) {
  218. var l = new List<ExprToken>();
  219. var expr = data;
  220. while( expr_splitter.match(data) ) {
  221. var p = expr_splitter.matchedPos();
  222. var k = p.pos + p.len;
  223. if( p.pos != 0 )
  224. l.add({ p : data.substr(0,p.pos), s : true });
  225. var p = expr_splitter.matched(0);
  226. l.add({ p : p, s : p.indexOf('"') >= 0 });
  227. data = expr_splitter.matchedRight();
  228. }
  229. if( data.length != 0 )
  230. l.add({ p : data, s : true });
  231. var e:Void->Dynamic;
  232. try {
  233. e = makeExpr(l);
  234. if( !l.isEmpty() )
  235. throw l.first().p;
  236. } catch( s : String ) {
  237. throw "Unexpected '"+s+"' in "+expr;
  238. }
  239. return function() {
  240. try {
  241. return e();
  242. } catch( exc : Dynamic ) {
  243. throw "Error : "+Std.string(exc)+" in "+expr;
  244. }
  245. }
  246. }
  247. function makeConst( v : String ) : Void -> Dynamic {
  248. expr_trim.match(v);
  249. v = expr_trim.matched(1);
  250. if( v.charCodeAt(0) == 34 ) {
  251. var str = v.substr(1,v.length-2);
  252. return function() return str;
  253. }
  254. if( expr_int.match(v) ) {
  255. var i = Std.parseInt(v);
  256. return function() { return i; };
  257. }
  258. if( expr_float.match(v) ) {
  259. var f = Std.parseFloat(v);
  260. return function() { return f; };
  261. }
  262. var me = this;
  263. return function() { return me.resolve(v); };
  264. }
  265. function makePath( e : Void -> Dynamic, l : List<ExprToken> ) {
  266. var p = l.first();
  267. if( p == null || p.p != "." )
  268. return e;
  269. l.pop();
  270. var field = l.pop();
  271. if( field == null || !field.s )
  272. throw field.p;
  273. var f = field.p;
  274. expr_trim.match(f);
  275. f = expr_trim.matched(1);
  276. return makePath(function() { return Reflect.field(e(),f); },l);
  277. }
  278. function makeExpr( l ) {
  279. return makePath(makeExpr2(l),l);
  280. }
  281. function makeExpr2( l : List<ExprToken> ) : Void -> Dynamic {
  282. var p = l.pop();
  283. if( p == null )
  284. throw "<eof>";
  285. if( p.s )
  286. return makeConst(p.p);
  287. switch( p.p ) {
  288. case "(":
  289. var e1:Dynamic = makeExpr(l);
  290. var p = l.pop();
  291. if( p == null || p.s )
  292. throw p;
  293. if( p.p == ")" )
  294. return e1;
  295. var e2:Dynamic = makeExpr(l);
  296. var p2 = l.pop();
  297. if( p2 == null || p2.p != ")" )
  298. throw p2;
  299. return switch( p.p ) {
  300. case "+": function() { return cast e1() + e2(); };
  301. case "-": function() { return cast e1() - e2(); };
  302. case "*": function() { return cast e1() * e2(); };
  303. case "/": function() { return cast e1() / e2(); };
  304. case ">": function() { return cast e1() > e2(); };
  305. case "<": function() { return cast e1() < e2(); };
  306. case ">=": function() { return cast e1() >= e2(); };
  307. case "<=": function() { return cast e1() <= e2(); };
  308. case "==": function() { return cast e1() == e2(); };
  309. case "!=": function() { return cast e1() != e2(); };
  310. case "&&": function() { return cast e1() && e2(); };
  311. case "||": function() { return cast e1() || e2(); };
  312. default: throw "Unknown operation "+p.p;
  313. }
  314. case "!":
  315. var e : Void->Dynamic = makeExpr(l);
  316. return function() {
  317. var v : Dynamic = e();
  318. return (v == null || v == false);
  319. };
  320. case "-":
  321. var e = makeExpr(l);
  322. return function() { return -e(); };
  323. }
  324. throw p.p;
  325. }
  326. function run( e : TemplateExpr ) {
  327. switch( e ) {
  328. case OpVar(v):
  329. buf.add(Std.string(resolve(v)));
  330. case OpExpr(e):
  331. buf.add(Std.string(e()));
  332. case OpIf(e,eif,eelse):
  333. var v : Dynamic = e();
  334. if( v == null || v == false ) {
  335. if( eelse != null ) run(eelse);
  336. } else
  337. run(eif);
  338. case OpStr(str):
  339. buf.add(str);
  340. case OpBlock(l):
  341. for( e in l )
  342. run(e);
  343. case OpForeach(e,loop):
  344. var v : Dynamic = e();
  345. try {
  346. var x : Dynamic = v.iterator();
  347. if( x.hasNext == null ) throw null;
  348. v = x;
  349. } catch( e : Dynamic ) try {
  350. if( v.hasNext == null ) throw null;
  351. } catch( e : Dynamic ) {
  352. throw "Cannot iter on " + v;
  353. }
  354. stack.push(context);
  355. var v : Iterator<Dynamic> = v;
  356. for( ctx in v ) {
  357. context = ctx;
  358. run(loop);
  359. }
  360. context = stack.pop();
  361. case OpMacro(m,params):
  362. var v : Dynamic = Reflect.field(macros,m);
  363. var pl = new Array<Dynamic>();
  364. var old = buf;
  365. pl.push(resolve);
  366. for( p in params ) {
  367. switch( p ) {
  368. case OpVar(v): pl.push(resolve(v));
  369. default:
  370. buf = new StringBuf();
  371. run(p);
  372. pl.push(buf.toString());
  373. }
  374. }
  375. buf = old;
  376. try {
  377. buf.add(Std.string(Reflect.callMethod(macros,v,pl)));
  378. } catch( e : Dynamic ) {
  379. var plstr = try pl.join(",") catch( e : Dynamic ) "???";
  380. var msg = "Macro call "+m+"("+plstr+") failed ("+Std.string(e)+")";
  381. #if neko
  382. neko.Lib.rethrow(msg);
  383. #else
  384. throw msg;
  385. #end
  386. }
  387. }
  388. }
  389. }