package com.hubspot.jinjava.el.ext; import static de.odysseus.el.tree.impl.Builder.Feature.METHOD_INVOCATIONS; import static de.odysseus.el.tree.impl.Builder.Feature.NULL_PROPERTIES; import static de.odysseus.el.tree.impl.Scanner.Symbol.COLON; import static de.odysseus.el.tree.impl.Scanner.Symbol.COMMA; import static de.odysseus.el.tree.impl.Scanner.Symbol.IDENTIFIER; import static de.odysseus.el.tree.impl.Scanner.Symbol.LBRACK; import static de.odysseus.el.tree.impl.Scanner.Symbol.LPAREN; import static de.odysseus.el.tree.impl.Scanner.Symbol.QUESTION; import static de.odysseus.el.tree.impl.Scanner.Symbol.RBRACK; import static de.odysseus.el.tree.impl.Scanner.Symbol.RPAREN; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import javax.el.ELException; import com.google.common.collect.Lists; import de.odysseus.el.tree.impl.Builder; import de.odysseus.el.tree.impl.Builder.Feature; import de.odysseus.el.tree.impl.Parser; import de.odysseus.el.tree.impl.Scanner; import de.odysseus.el.tree.impl.Scanner.ScanException; import de.odysseus.el.tree.impl.Scanner.Symbol; import de.odysseus.el.tree.impl.Scanner.Token; import de.odysseus.el.tree.impl.ast.AstBinary; import de.odysseus.el.tree.impl.ast.AstBracket; import de.odysseus.el.tree.impl.ast.AstDot; import de.odysseus.el.tree.impl.ast.AstFunction; import de.odysseus.el.tree.impl.ast.AstNested; import de.odysseus.el.tree.impl.ast.AstNode; import de.odysseus.el.tree.impl.ast.AstNull; import de.odysseus.el.tree.impl.ast.AstParameters; import de.odysseus.el.tree.impl.ast.AstProperty; public class ExtendedParser extends Parser { public static final String INTERPRETER = "____int3rpr3t3r____"; public static final String FILTER_PREFIX = "filter:"; public static final String EXPTEST_PREFIX = "exptest:"; static final Scanner.ExtensionToken PIPE = new Scanner.ExtensionToken("|"); static final Scanner.ExtensionToken IS = new Scanner.ExtensionToken("is"); static final Token IF = new Scanner.Token(Symbol.QUESTION, "if"); static final Token ELSE = new Scanner.Token(Symbol.COLON, "else"); static final Scanner.ExtensionToken LITERAL_DICT_START = new Scanner.ExtensionToken("{"); static final Scanner.ExtensionToken LITERAL_DICT_END = new Scanner.ExtensionToken("}"); static final Scanner.ExtensionToken TRUNC_DIV = new Scanner.ExtensionToken("//"); static final Scanner.ExtensionToken POWER_OF = new Scanner.ExtensionToken("**"); static { ExtendedScanner.addKeyToken(IF); ExtendedScanner.addKeyToken(ELSE); ExtendedScanner.addKeyToken(TruncDivOperator.TOKEN); ExtendedScanner.addKeyToken(PowerOfOperator.TOKEN); ExtendedScanner.addKeyToken(CollectionMembershipOperator.TOKEN); } public ExtendedParser(Builder context, String input) { super(context, input); putExtensionHandler(AbsOperator.TOKEN, AbsOperator.HANDLER); putExtensionHandler(NamedParameterOperator.TOKEN, NamedParameterOperator.HANDLER); putExtensionHandler(StringConcatOperator.TOKEN, StringConcatOperator.HANDLER); putExtensionHandler(TruncDivOperator.TOKEN, TruncDivOperator.HANDLER); putExtensionHandler(PowerOfOperator.TOKEN, PowerOfOperator.HANDLER); putExtensionHandler(CollectionMembershipOperator.TOKEN, CollectionMembershipOperator.HANDLER); putExtensionHandler(PIPE, new ExtensionHandler(ExtensionPoint.AND) { @Override public AstNode createAstNode(AstNode... children) { throw new ELException("Illegal use of '|' operator"); } }); putExtensionHandler(LITERAL_DICT_START, NULL_EXT_HANDLER); putExtensionHandler(LITERAL_DICT_END, NULL_EXT_HANDLER); } protected AstNode interpreter() { return identifier(INTERPRETER); } @Override protected AstNode expr(boolean required) throws ScanException, ParseException { AstNode v = or(required); if (v == null) { return null; } if (getToken().getSymbol() == QUESTION) { if (!getToken().getImage().equals("if")) { consumeToken(); AstNode a = expr(true); consumeToken(COLON); AstNode b = expr(true); v = createAstChoice(v, a, b); } else { consumeToken(); AstNode cond = expr(true); AstNode elseNode = new AstNull(); if (getToken().getImage().equals("else")) { consumeToken(); elseNode = expr(true); } v = createAstChoice(cond, v, elseNode); } } return v; } @Override protected AstNode or(boolean required) throws ScanException, ParseException { AstNode v = and(required); if (v == null) { return null; } while (true) { switch (getToken().getSymbol()) { case OR: consumeToken(); v = createAstBinary(v, and(true), OrOperator.OP); break; case EXTENSION: if (getExtensionHandler(getToken()).getExtensionPoint() == ExtensionPoint.OR) { v = getExtensionHandler(consumeToken()).createAstNode(v, and(true)); break; } default: return v; } } } @Override protected AstNode add(boolean required) throws ScanException, ParseException { AstNode v = mul(required); if (v == null) { return null; } while (true) { switch (getToken().getSymbol()) { case PLUS: consumeToken(); v = createAstBinary(v, mul(true), AdditionOperator.OP); break; case MINUS: consumeToken(); v = createAstBinary(v, mul(true), AstBinary.SUB); break; case EXTENSION: if (getExtensionHandler(getToken()).getExtensionPoint() == ExtensionPoint.ADD) { v = getExtensionHandler(consumeToken()).createAstNode(v, mul(true)); break; } default: return v; } } } @Override protected AstParameters params() throws ScanException, ParseException { return params(LPAREN, RPAREN); } protected AstParameters params(Symbol left, Symbol right) throws ScanException, ParseException { consumeToken(left); List<AstNode> l = Collections.emptyList(); AstNode v = expr(false); if (v != null) { l = new ArrayList<AstNode>(); l.add(v); while (getToken().getSymbol() == COMMA) { consumeToken(); l.add(expr(true)); } } consumeToken(right); return new AstParameters(l); } protected AstDict dict() throws ScanException, ParseException { consumeToken(); Map<AstNode, AstNode> dict = new LinkedHashMap<>(); AstNode k = expr(false); if (k != null) { consumeToken(COLON); AstNode v = expr(true); dict.put(k, v); while (getToken().getSymbol() == COMMA) { consumeToken(); // python is lenient about dangling commas...! k = expr(false); if (k != null) { consumeToken(COLON); v = expr(true); dict.put(k, v); } } } if (!getToken().getImage().equals("}")) { fail("}"); } consumeToken(); return new AstDict(dict); } @Override protected AstFunction createAstFunction(String name, int index, AstParameters params) { return new AstMacroFunction(name, index, params, context.isEnabled(Feature.VARARGS)); } @Override protected AstNode nonliteral() throws ScanException, ParseException { AstNode v = null; switch (getToken().getSymbol()) { case IDENTIFIER: String name = consumeToken().getImage(); if (getToken().getSymbol() == COLON && lookahead(0).getSymbol() == IDENTIFIER && lookahead(1).getSymbol() == LPAREN) { // ns:f(...) consumeToken(); name += ":" + getToken().getImage(); consumeToken(); } if (getToken().getSymbol() == LPAREN) { // function v = function(name, params()); } else { // identifier v = identifier(name); } break; case LPAREN: int i = 0; Symbol s; do { s = lookahead(i++).getSymbol(); if (s == Symbol.COMMA) { return new AstTuple(params()); } } while (s != Symbol.RPAREN && s != Symbol.EOF); consumeToken(); v = expr(true); consumeToken(RPAREN); v = new AstNested(v); break; default: break; } return v; } @Override protected AstNode literal() throws ScanException, ParseException { AstNode v = null; switch (getToken().getSymbol()) { case LBRACK: v = new AstList(params(LBRACK, RBRACK)); break; case LPAREN: v = new AstTuple(params()); break; case EXTENSION: if (getToken() == LITERAL_DICT_START) { v = dict(); } else if (getToken() == LITERAL_DICT_END) { return null; } break; default: break; } if (v != null) { return v; } return super.literal(); } protected AstRangeBracket createAstRangeBracket(AstNode base, AstNode rangeStart, AstNode rangeMax, boolean lvalue, boolean strict) { return new AstRangeBracket(base, rangeStart, rangeMax, lvalue, strict, context.isEnabled(Feature.IGNORE_RETURN_TYPE)); } @Override protected AstNode value() throws ScanException, ParseException { boolean lvalue = true; AstNode v = nonliteral(); if (v == null) { v = literal(); if (v == null) { return null; } lvalue = false; } while (true) { switch (getToken().getSymbol()) { case DOT: consumeToken(); String name = consumeToken(IDENTIFIER).getImage(); AstDot dot = createAstDot(v, name, lvalue); if (getToken().getSymbol() == LPAREN && context.isEnabled(METHOD_INVOCATIONS)) { v = createAstMethod(dot, params()); } else { v = dot; } break; case LBRACK: consumeToken(); AstNode property = expr(false); boolean strict = !context.isEnabled(NULL_PROPERTIES); Token nextToken = consumeToken(); if (nextToken.getSymbol() == COLON) { AstNode rangeMax = expr(false); consumeToken(RBRACK); v = createAstRangeBracket(v, property, rangeMax, lvalue, strict); } else if (nextToken.getSymbol() == RBRACK) { AstBracket bracket = createAstBracket(v, property, lvalue, strict); if (getToken().getSymbol() == LPAREN && context.isEnabled(METHOD_INVOCATIONS)) { v = createAstMethod(bracket, params()); } else { v = bracket; } } else { fail(RBRACK); } break; default: if ("|".equals(getToken().getImage()) && lookahead(0).getSymbol() == IDENTIFIER) { do { consumeToken(); // '|' String filterName = consumeToken().getImage(); List<AstNode> filterParams = Lists.newArrayList(v, interpreter()); // optional filter args if (getToken().getSymbol() == Symbol.LPAREN) { AstParameters astParameters = params(); for (int i = 0; i < astParameters.getCardinality(); i++) { filterParams.add(astParameters.getChild(i)); } } AstProperty filterProperty = createAstDot(identifier(FILTER_PREFIX + filterName), "filter", true); v = createAstMethod(filterProperty, new AstParameters(filterParams)); // function("filter:" + filterName, new AstParameters(filterParams)); } while ("|".equals(getToken().getImage())); } else if ("is".equals(getToken().getImage()) && lookahead(0).getSymbol() == IDENTIFIER) { consumeToken(); // 'is' String exptestName = consumeToken().getImage(); List<AstNode> exptestParams = Lists.newArrayList(v, interpreter()); // optional exptest arg AstNode arg = expr(false); if (arg != null) { exptestParams.add(arg); } AstProperty exptestProperty = createAstDot(identifier(EXPTEST_PREFIX + exptestName), "evaluate", true); v = createAstMethod(exptestProperty, new AstParameters(exptestParams)); } else if ("//".equals(getToken().getImage()) && lookahead(0).getSymbol() == IDENTIFIER) { consumeToken(); // '//' v = createAstBinary(v, mul(true), TruncDivOperator.OP); } else if ("**".equals(getToken().getImage()) && lookahead(0).getSymbol() == IDENTIFIER) { consumeToken(); // '**' v = createAstBinary(v, mul(true), PowerOfOperator.OP); } return v; } } } @Override protected Scanner createScanner(String expression) { return new ExtendedScanner(expression); } private static final ExtensionHandler NULL_EXT_HANDLER = new ExtensionHandler(null) { @Override public AstNode createAstNode(AstNode... children) { return null; } }; }