use of com.squarespace.template.ErrorInfo in project template-compiler by Squarespace.
the class Expr method reduceExpr.
/**
* Reduce an expression to its simplest form.
*/
public JsonNode reduceExpr(Context ctx, List<Token> expr) {
Stack<Token> stack = new Stack<>();
loop: for (Token t : expr) {
switch(t.type) {
case BOOLEAN:
case STRING:
case NUMBER:
case NULL:
case VARIABLE:
case ARGS:
stack.push(t);
continue;
case CALL:
{
List<Token> args = new ArrayList<>();
Token top = stack.top();
// Pop values from the stack until we hit the argument delimiter.
while (top != null && top.type != ExprTokenType.ARGS) {
Token arg = asliteral(ctx, stack.pop());
if (arg != null) {
args.add(arg);
}
top = stack.top();
}
// Pop the argument delimiter
stack.pop();
// Reverse the arguments and apply them.
Collections.reverse(args);
// Get a reference to the function implementation.
CallToken call = (CallToken) t;
FunctionDef fimpl = FUNCTIONS.get(call.name);
// The function is guaranteed to exist here, since its existence
// was verified when the call token was constructed.
Token r = fimpl.apply(args);
if (r == null) {
ErrorInfo error = ctx.error(ExecuteErrorType.EXPRESSION_REDUCE).data("Error calling function " + call.name);
ctx.addError(error);
break loop;
}
// Push the result onto the stack.
stack.push(r);
continue;
}
case OPERATOR:
{
// Unary operators directly manipulate top of stack to avoid a pop-then-push
Operator o = ((OperatorToken) t).value;
switch(o.type) {
case MINUS:
{
Token arg = asliteral(ctx, stack.top());
stack.top(arg == null ? null : mul(MINUS_ONE, arg));
continue;
}
case PLUS:
{
// unary plus casts the argument to number but doesn't change sign
Token arg = asliteral(ctx, stack.top());
stack.top(arg == null ? null : num(asnum(arg)));
continue;
}
case LNOT:
{
Token arg = asliteral(ctx, stack.top());
stack.top(arg == null ? null : bool(!asbool(arg)));
continue;
}
case BNOT:
{
Token arg = asliteral(ctx, stack.top());
stack.top(arg == null ? null : num(~asint(arg)));
continue;
}
case ASN:
{
Token b = asliteral(ctx, stack.pop());
Token a = stack.pop();
// Make sure the arguments to the assignment are valid
if (a != null && a.type == ExprTokenType.VARIABLE && b != null) {
Object[] name = ((VarToken) a).name;
if (name != null && name.length == 1 && name[0] instanceof String && ((String) name[0]).charAt(0) == '@') {
// Set the variable in the context.
ctx.setVar((String) name[0], asnode(b));
}
}
// When an assignment operator is encountered, we consider the expression
// complete. This leaves no result.
stack.top(null);
break loop;
}
default:
}
// Binary operators, pop 2 args from the stack and push result
Token b = asliteral(ctx, stack.pop());
Token a = asliteral(ctx, stack.pop());
// Validate operator args are present and valid
if (a == null || b == null) {
// Invalid arguments to operator, bail out.
ErrorInfo error = ctx.error(ExecuteErrorType.EXPRESSION_REDUCE).data("Invalid arguments to operator " + o.desc);
ctx.addError(error);
break loop;
}
Token r = null;
switch(o.type) {
case MUL:
r = mul(a, b);
break;
case DIV:
{
double v = asnum(b);
r = num(v == 0 ? Double.NaN : asnum(a) / v);
break;
}
case ADD:
// Numeric addition or string concatenation.
if (a.type == ExprTokenType.STRING || b.type == ExprTokenType.STRING) {
// Convert both arguments to string
String _a = asstr(a);
String _b = asstr(b);
// Ensure a concatenated string won't exceed the configured limit.
if (this.maxStringLen > 0 && (_a.length() + _b.length() > this.maxStringLen)) {
ErrorInfo error = ctx.error(ExecuteErrorType.EXPRESSION_REDUCE).data("Concatenation would exceed maximum string length " + this.maxStringLen);
ctx.addError(error);
break loop;
}
r = str(_a + _b);
} else {
r = num(asnum(a) + asnum(b));
}
break;
case SUB:
r = num(asnum(a) - asnum(b));
break;
case POW:
r = num(Math.pow(asnum(a), asnum(b)));
break;
case MOD:
{
double v = asnum(b);
r = num(v == 0 ? Double.NaN : asnum(a) % v);
break;
}
case SHL:
r = num(asint(a) << asint(b));
break;
case SHR:
r = num(asint(a) >> asint(b));
break;
case LT:
r = Operations.lt(a, b);
break;
case LTEQ:
r = Operations.lteq(a, b);
break;
case GT:
r = Operations.gt(a, b);
break;
case GTEQ:
r = Operations.gteq(a, b);
break;
case EQ:
r = Operations.eq(a, b);
break;
case NEQ:
r = Operations.neq(a, b);
break;
case SEQ:
r = Operations.seq(a, b);
break;
case SNEQ:
r = Operations.sneq(a, b);
break;
case BAND:
r = num(asint(a) & asint(b));
break;
case BXOR:
r = num(asint(a) ^ asint(b));
break;
case BOR:
r = num(asint(a) | asint(b));
break;
case LAND:
r = bool(asbool(a) && asbool(b));
break;
case LOR:
r = bool(asbool(a) || asbool(b));
break;
default:
// all unary and binary operators should be handled above.
// other operators (parenthesis, semicolon) are eliminated when
// the expressions are assembled.
this.errors.add(E_UNEXPECTED_OPERATOR + " " + o.desc);
stack.top(null);
break loop;
}
// Push result onto stack
stack.push(r);
}
}
}
// Return a valid literal from the top of the stack, or null
// if an unexpected token is present.
Token r = stack.top();
if (r != null) {
// Ensure the value is a literal
Token v = asliteral(ctx, r);
if (v != null) {
// We have a supported value.
switch(v.type) {
case BOOLEAN:
return ((BooleanToken) v).value ? BooleanNode.TRUE : BooleanNode.FALSE;
case NUMBER:
return new DoubleNode(((NumberToken) v).value);
case STRING:
return new TextNode(((StringToken) v).value);
case NULL:
return NullNode.getInstance();
default:
// Fall through
break;
}
}
// The token was an unexpected type, which is an error
ErrorInfo error = ctx.error(ExecuteErrorType.EXPRESSION_REDUCE).data("Reduce error: unexpected token on stack");
ctx.addError(error);
}
return null;
}
use of com.squarespace.template.ErrorInfo in project template-compiler by Squarespace.
the class TemplateC method compile.
/**
* Compile a template against a given json tree and emit the result.
*/
protected int compile(String templatePath, String jsonPath, String partialsPath, String locale, boolean preprocess) throws CodeException, IOException {
String template = readFile(templatePath);
String json = "{}";
if (jsonPath != null) {
json = readFile(jsonPath);
}
String partials = null;
if (partialsPath != null) {
partials = readFile(partialsPath);
}
if (locale == null) {
locale = "en-US";
}
CompiledTemplate compiled = compiler().compile(template, true, preprocess);
StringBuilder errorBuf = new StringBuilder();
List<ErrorInfo> errors = compiled.errors();
if (!errors.isEmpty()) {
errorBuf.append("Caught errors executing template:\n");
for (ErrorInfo error : errors) {
errorBuf.append(" ").append(error.getMessage()).append('\n');
}
}
Instruction code = compiled.code();
// Parse the JSON context
JsonNode jsonTree = null;
try {
jsonTree = JsonUtils.decode(json);
} catch (IllegalArgumentException e) {
System.err.println("Caught error trying to parse JSON: " + e.getCause().getMessage());
return 1;
}
// Parse the optional JSON partials dictionary.
JsonNode partialsTree = null;
if (partials != null) {
try {
partialsTree = JsonUtils.decode(partials);
if (!(partialsTree instanceof ObjectNode)) {
System.err.println("Partials map JSON must be an object. Found " + partialsTree.getNodeType());
return 1;
}
} catch (IllegalArgumentException e) {
System.err.println("Caught error trying to parse partials: " + e.getCause().getMessage());
return 1;
}
}
// Perform the compile.
Context context = compiler().newExecutor().code(code).json(jsonTree).locale(Locale.forLanguageTag(locale)).safeExecution(true).partialsMap((ObjectNode) partialsTree).enableExpr(true).enableInclude(true).execute();
// If compile was successful, print the output.
System.out.print(context.buffer().toString());
if (errorBuf.length() > 0) {
System.err.println(errorBuf.toString());
}
return 0;
}
Aggregations