Search in sources :

Example 1 with ErrorInfo

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;
}
Also used : ErrorInfo(com.squarespace.template.ErrorInfo) ArrayList(java.util.ArrayList) DoubleNode(com.fasterxml.jackson.databind.node.DoubleNode) TextNode(com.fasterxml.jackson.databind.node.TextNode)

Example 2 with ErrorInfo

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;
}
Also used : Context(com.squarespace.template.Context) ObjectNode(com.fasterxml.jackson.databind.node.ObjectNode) ErrorInfo(com.squarespace.template.ErrorInfo) JsonNode(com.fasterxml.jackson.databind.JsonNode) Instruction(com.squarespace.template.Instruction) CompiledTemplate(com.squarespace.template.CompiledTemplate)

Aggregations

ErrorInfo (com.squarespace.template.ErrorInfo)2 JsonNode (com.fasterxml.jackson.databind.JsonNode)1 DoubleNode (com.fasterxml.jackson.databind.node.DoubleNode)1 ObjectNode (com.fasterxml.jackson.databind.node.ObjectNode)1 TextNode (com.fasterxml.jackson.databind.node.TextNode)1 CompiledTemplate (com.squarespace.template.CompiledTemplate)1 Context (com.squarespace.template.Context)1 Instruction (com.squarespace.template.Instruction)1 ArrayList (java.util.ArrayList)1