Include haxe.Template for now (to patch Haxe 3.3.0-rc1)

This commit is contained in:
Joshua Granick
2016-08-24 17:00:21 -07:00
parent 48226e8a94
commit 496708cb74

417
haxe/Template.hx Normal file
View File

@@ -0,0 +1,417 @@
/*
* Copyright (C)2005-2016 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:
<http://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
}
}
}
}