package org.ssssssss.magicapi.expression.parsing; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import org.ssssssss.magicapi.expression.ExpressionError; import org.ssssssss.magicapi.expression.ExpressionError.TemplateException; import org.ssssssss.magicapi.expression.ExpressionTemplate; import org.ssssssss.magicapi.expression.ExpressionTemplateContext; import org.ssssssss.magicapi.expression.interpreter.AbstractReflection; import org.ssssssss.magicapi.expression.interpreter.AstInterpreter; import org.ssssssss.magicapi.expression.interpreter.JavaReflection; import javax.xml.transform.Source; import java.io.IOException; import java.lang.reflect.Array; import java.lang.reflect.InvocationTargetException; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; /** Templates are parsed into an abstract syntax tree (AST) nodes by a Parser. This class contains all AST node types. */ public abstract class Ast { /** Base class for all AST nodes. A node minimally stores the {@link Span} that references its location in the * {@link Source}. **/ public abstract static class Node { private final Span span; public Node (Span span) { this.span = span; } /** Returns the {@link Span} referencing this node's location in the {@link Source}. **/ public Span getSpan () { return span; } @Override public String toString () { return span.getText(); } public abstract Object evaluate (ExpressionTemplate template, ExpressionTemplateContext context) throws IOException; } /** A text node represents an "un-templated" span in the source that should be emitted verbatim. **/ public static class Text extends Node { private final String content; public Text (Span text) { super(text); String unescapedValue = text.getText(); StringBuilder builder = new StringBuilder(); CharacterStream stream = new CharacterStream(unescapedValue); while (stream.hasMore()) { if (stream.match("\\{", true)) { builder.append('{'); } else if (stream.match("\\}", true)) { builder.append('}'); } else { builder.append(stream.consume()); } } content = builder.toString(); } /** Returns the UTF-8 representation of this text node. **/ public String getContent () { return content; } @Override public Object evaluate (ExpressionTemplate template, ExpressionTemplateContext context) throws IOException { return null; } } /** All expressions are subclasses of this node type. Expressions are separated into unary operations (!, -), binary operations * (+, -, *, /, etc.) and ternary operations (?:). */ public abstract static class Expression extends Node { public Expression (Span span) { super(span); } } /** An unary operation node represents a logical or numerical negation. **/ public static class UnaryOperation extends Expression { public static enum UnaryOperator { Not, Negate, Positive; public static UnaryOperator getOperator (Token op) { if (op.getType() == TokenType.Not) { return UnaryOperator.Not; } if (op.getType() == TokenType.Plus) { return UnaryOperator.Positive; } if (op.getType() == TokenType.Minus) { return UnaryOperator.Negate; } ExpressionError.error("Unknown unary operator " + op + ".", op.getSpan()); return null; // not reached } } private final UnaryOperator operator; private final Expression operand; public UnaryOperation (Token operator, Expression operand) { super(operator.getSpan()); this.operator = UnaryOperator.getOperator(operator); this.operand = operand; } public UnaryOperator getOperator () { return operator; } public Expression getOperand () { return operand; } @Override public Object evaluate (ExpressionTemplate template, ExpressionTemplateContext context) throws IOException { Object operand = getOperand().evaluate(template, context); if (getOperator() == UnaryOperator.Negate) { if (operand instanceof Integer) { return -(Integer)operand; } else if (operand instanceof Float) { return -(Float)operand; } else if (operand instanceof Double) { return -(Double)operand; } else if (operand instanceof Byte) { return -(Byte)operand; } else if (operand instanceof Short) { return -(Short)operand; } else if (operand instanceof Long) { return -(Long)operand; } else { ExpressionError.error("Operand of operator '" + getOperator().name() + "' must be a number, got " + operand, getSpan()); return null; // never reached } } else if (getOperator() == UnaryOperator.Not) { if (!(operand instanceof Boolean)) { ExpressionError.error("Operand of operator '" + getOperator().name() + "' must be a boolean", getSpan()); } return !(Boolean)operand; } else { return operand; } } } /** A binary operation represents arithmetic operators, like addition or division, comparison operators, like less than or * equals, logical operators, like and, or an assignment. **/ public static class BinaryOperation extends Expression { public static enum BinaryOperator { Addition, Subtraction, Multiplication, Division, Modulo, Equal, NotEqual, Less, LessEqual, Greater, GreaterEqual, And, Or, Xor, Assignment; public static BinaryOperator getOperator (Token op) { if (op.getType() == TokenType.Plus) { return BinaryOperator.Addition; } if (op.getType() == TokenType.Minus) { return BinaryOperator.Subtraction; } if (op.getType() == TokenType.Asterisk) { return BinaryOperator.Multiplication; } if (op.getType() == TokenType.ForwardSlash) { return BinaryOperator.Division; } if (op.getType() == TokenType.Percentage) { return BinaryOperator.Modulo; } if (op.getType() == TokenType.Equal) { return BinaryOperator.Equal; } if (op.getType() == TokenType.NotEqual) { return BinaryOperator.NotEqual; } if (op.getType() == TokenType.Less) { return BinaryOperator.Less; } if (op.getType() == TokenType.LessEqual) { return BinaryOperator.LessEqual; } if (op.getType() == TokenType.Greater) { return BinaryOperator.Greater; } if (op.getType() == TokenType.GreaterEqual) { return BinaryOperator.GreaterEqual; } if (op.getType() == TokenType.And) { return BinaryOperator.And; } if (op.getType() == TokenType.Or) { return BinaryOperator.Or; } if (op.getType() == TokenType.Xor) { return BinaryOperator.Xor; } if (op.getType() == TokenType.Assignment) { return BinaryOperator.Assignment; } ExpressionError.error("Unknown binary operator " + op + ".", op.getSpan()); return null; // not reached } } private final Expression leftOperand; private final BinaryOperator operator; private final Expression rightOperand; public BinaryOperation (Expression leftOperand, Token operator, Expression rightOperand) { super(operator.getSpan()); this.leftOperand = leftOperand; this.operator = BinaryOperator.getOperator(operator); this.rightOperand = rightOperand; } public Expression getLeftOperand () { return leftOperand; } public BinaryOperator getOperator () { return operator; } public Expression getRightOperand () { return rightOperand; } private Object evaluateAddition (Object left, Object right) { if (left instanceof String || right instanceof String) { return left.toString() + right.toString(); } if (left instanceof Double || right instanceof Double) { return ((Number)left).doubleValue() + ((Number)right).doubleValue(); } if (left instanceof Float || right instanceof Float) { return ((Number)left).floatValue() + ((Number)right).floatValue(); } if (left instanceof Long || right instanceof Long) { return ((Number)left).longValue() + ((Number)right).longValue(); } if (left instanceof Integer || right instanceof Integer) { return ((Number)left).intValue() + ((Number)right).intValue(); } if (left instanceof Short || right instanceof Short) { return ((Number)left).shortValue() + ((Number)right).shortValue(); } if (left instanceof Byte || right instanceof Byte) { return ((Number)left).byteValue() + ((Number)right).byteValue(); } ExpressionError.error("Operands for addition operator must be numbers or strings, got " + left + ", " + right + ".", getSpan()); return null; // never reached } private Object evaluateSubtraction (Object left, Object right) { if (left instanceof Double || right instanceof Double) { return ((Number)left).doubleValue() - ((Number)right).doubleValue(); } else if (left instanceof Float || right instanceof Float) { return ((Number)left).floatValue() - ((Number)right).floatValue(); } else if (left instanceof Long || right instanceof Long) { return ((Number)left).longValue() - ((Number)right).longValue(); } else if (left instanceof Integer || right instanceof Integer) { return ((Number)left).intValue() - ((Number)right).intValue(); } else if (left instanceof Short || right instanceof Short) { return ((Number)left).shortValue() - ((Number)right).shortValue(); } else if (left instanceof Byte || right instanceof Byte) { return ((Number)left).byteValue() - ((Number)right).byteValue(); } else { ExpressionError.error("Operands for subtraction operator must be numbers" + left + ", " + right + ".", getSpan()); return null; // never reached } } private Object evaluateMultiplication (Object left, Object right) { if (left instanceof Double || right instanceof Double) { return ((Number)left).doubleValue() * ((Number)right).doubleValue(); } else if (left instanceof Float || right instanceof Float) { return ((Number)left).floatValue() * ((Number)right).floatValue(); } else if (left instanceof Long || right instanceof Long) { return ((Number)left).longValue() * ((Number)right).longValue(); } else if (left instanceof Integer || right instanceof Integer) { return ((Number)left).intValue() * ((Number)right).intValue(); } else if (left instanceof Short || right instanceof Short) { return ((Number)left).shortValue() * ((Number)right).shortValue(); } else if (left instanceof Byte || right instanceof Byte) { return ((Number)left).byteValue() * ((Number)right).byteValue(); } else { ExpressionError.error("Operands for multiplication operator must be numbers" + left + ", " + right + ".", getSpan()); return null; // never reached } } private Object evaluateDivision (Object left, Object right) { if (left instanceof Double || right instanceof Double) { return ((Number)left).doubleValue() / ((Number)right).doubleValue(); } else if (left instanceof Float || right instanceof Float) { return ((Number)left).floatValue() / ((Number)right).floatValue(); } else if (left instanceof Long || right instanceof Long) { return ((Number)left).longValue() / ((Number)right).longValue(); } else if (left instanceof Integer || right instanceof Integer) { return ((Number)left).intValue() / ((Number)right).intValue(); } else if (left instanceof Short || right instanceof Short) { return ((Number)left).shortValue() / ((Number)right).shortValue(); } else if (left instanceof Byte || right instanceof Byte) { return ((Number)left).byteValue() / ((Number)right).byteValue(); } else { ExpressionError.error("Operands for division operator must be numbers" + left + ", " + right + ".", getSpan()); return null; // never reached } } private Object evaluateModulo (Object left, Object right) { if (left instanceof Double || right instanceof Double) { return ((Number)left).doubleValue() % ((Number)right).doubleValue(); } else if (left instanceof Float || right instanceof Float) { return ((Number)left).floatValue() % ((Number)right).floatValue(); } else if (left instanceof Long || right instanceof Long) { return ((Number)left).longValue() % ((Number)right).longValue(); } else if (left instanceof Integer || right instanceof Integer) { return ((Number)left).intValue() % ((Number)right).intValue(); } else if (left instanceof Short || right instanceof Short) { return ((Number)left).shortValue() % ((Number)right).shortValue(); } else if (left instanceof Byte || right instanceof Byte) { return ((Number)left).byteValue() % ((Number)right).byteValue(); } else { ExpressionError.error("Operands for modulo operator must be numbers" + left + ", " + right + ".", getSpan()); return null; // never reached } } private boolean evaluateLess (Object left, Object right) { if (left instanceof Double || right instanceof Double) { return ((Number)left).doubleValue() < ((Number)right).doubleValue(); } else if (left instanceof Float || right instanceof Float) { return ((Number)left).floatValue() < ((Number)right).floatValue(); } else if (left instanceof Long || right instanceof Long) { return ((Number)left).longValue() < ((Number)right).longValue(); } else if (left instanceof Integer || right instanceof Integer) { return ((Number)left).intValue() < ((Number)right).intValue(); } else if (left instanceof Short || right instanceof Short) { return ((Number)left).shortValue() < ((Number)right).shortValue(); } else if (left instanceof Byte || right instanceof Byte) { return ((Number)left).byteValue() < ((Number)right).byteValue(); } else { ExpressionError.error("Operands for less operator must be numbers" + left + ", " + right + ".", getSpan()); return false; // never reached } } private Object evaluateLessEqual (Object left, Object right) { if (left instanceof Double || right instanceof Double) { return ((Number)left).doubleValue() <= ((Number)right).doubleValue(); } else if (left instanceof Float || right instanceof Float) { return ((Number)left).floatValue() <= ((Number)right).floatValue(); } else if (left instanceof Long || right instanceof Long) { return ((Number)left).longValue() <= ((Number)right).longValue(); } else if (left instanceof Integer || right instanceof Integer) { return ((Number)left).intValue() <= ((Number)right).intValue(); } else if (left instanceof Short || right instanceof Short) { return ((Number)left).shortValue() <= ((Number)right).shortValue(); } else if (left instanceof Byte || right instanceof Byte) { return ((Number)left).byteValue() <= ((Number)right).byteValue(); } else { ExpressionError.error("Operands for less/equal operator must be numbers" + left + ", " + right + ".", getSpan()); return null; // never reached } } private Object evaluateGreater (Object left, Object right) { if(left == null || right == null){ return false; } if (left instanceof Double || right instanceof Double) { return ((Number)left).doubleValue() > ((Number)right).doubleValue(); } else if (left instanceof Float || right instanceof Float) { return ((Number)left).floatValue() > ((Number)right).floatValue(); } else if (left instanceof Long || right instanceof Long) { return ((Number)left).longValue() > ((Number)right).longValue(); } else if (left instanceof Integer || right instanceof Integer) { return ((Number)left).intValue() > ((Number)right).intValue(); } else if (left instanceof Short || right instanceof Short) { return ((Number)left).shortValue() > ((Number)right).shortValue(); } else if (left instanceof Byte || right instanceof Byte) { return ((Number)left).byteValue() > ((Number)right).byteValue(); } else { ExpressionError.error("Operands for greater operator must be numbers" + left + ", " + right + ".", getSpan()); return null; // never reached } } private Object evaluateGreaterEqual (Object left, Object right) { if (left instanceof Double || right instanceof Double) { return ((Number)left).doubleValue() >= ((Number)right).doubleValue(); } else if (left instanceof Float || right instanceof Float) { return ((Number)left).floatValue() >= ((Number)right).floatValue(); } else if (left instanceof Long || right instanceof Long) { return ((Number)left).longValue() >= ((Number)right).longValue(); } else if (left instanceof Integer || right instanceof Integer) { return ((Number)left).intValue() >= ((Number)right).intValue(); } else if (left instanceof Short || right instanceof Short) { return ((Number)left).shortValue() >= ((Number)right).shortValue(); } else if (left instanceof Byte || right instanceof Byte) { return ((Number)left).byteValue() >= ((Number)right).byteValue(); } else { ExpressionError.error("Operands for greater/equal operator must be numbers" + left + ", " + right + ".", getSpan()); return null; // never reached } } private Object evaluateAnd (Object left, ExpressionTemplate template, ExpressionTemplateContext context) throws IOException { if (!(left instanceof Boolean)) { ExpressionError.error("Left operand must be a boolean, got " + left + ".", getLeftOperand().getSpan()); } if (!(Boolean)left) { return false; } Object right = getRightOperand().evaluate(template, context); if (!(right instanceof Boolean)) { ExpressionError.error("Right operand must be a boolean, got " + right + ".", getRightOperand().getSpan()); } return (Boolean)left && (Boolean)right; } private Object evaluateOr (Object left, ExpressionTemplate template, ExpressionTemplateContext context) throws IOException { if (!(left instanceof Boolean)) { ExpressionError.error("Left operand must be a boolean, got " + left + ".", getLeftOperand().getSpan()); } if ((Boolean)left) { return true; } Object right = getRightOperand().evaluate(template, context); if (!(right instanceof Boolean)) { ExpressionError.error("Right operand must be a boolean, got " + right + ".", getRightOperand().getSpan()); } return (Boolean)left || (Boolean)right; } private Object evaluateXor (Object left, Object right) { if (!(left instanceof Boolean)) { ExpressionError.error("Left operand must be a boolean, got " + left + ".", getLeftOperand().getSpan()); } if (!(right instanceof Boolean)) { ExpressionError.error("Right operand must be a boolean, got " + right + ".", getRightOperand().getSpan()); } return (Boolean)left ^ (Boolean)right; } private Object evaluateEqual (Object left, Object right) { if (left != null) { return left.equals(right); } if (right != null) { return right.equals(left); } return true; } private Object evaluateNotEqual (Object left, Object right) { return !(Boolean)evaluateEqual(left, right); } @Override public Object evaluate (ExpressionTemplate template, ExpressionTemplateContext context) throws IOException { if (getOperator() == BinaryOperator.Assignment) { if (!(getLeftOperand() instanceof VariableAccess)) { ExpressionError.error("Can only assign to top-level variables in context.", getLeftOperand().getSpan()); } Object value = getRightOperand().evaluate(template, context); context.set(((VariableAccess)getLeftOperand()).getVariableName().getText(), value); return null; } Object left = getLeftOperand().evaluate(template, context); Object right = getOperator() == BinaryOperator.And || getOperator() == BinaryOperator.Or ? null : getRightOperand().evaluate(template, context); switch (getOperator()) { case Addition: return evaluateAddition(left, right); case Subtraction: return evaluateSubtraction(left, right); case Multiplication: return evaluateMultiplication(left, right); case Division: return evaluateDivision(left, right); case Modulo: return evaluateModulo(left, right); case Less: return evaluateLess(left, right); case LessEqual: return evaluateLessEqual(left, right); case Greater: return evaluateGreater(left, right); case GreaterEqual: return evaluateGreaterEqual(left, right); case Equal: return evaluateEqual(left, right); case NotEqual: return evaluateNotEqual(left, right); case And: return evaluateAnd(left, template, context); case Or: return evaluateOr(left, template, context); case Xor: return evaluateXor(left, right); default: ExpressionError.error("Binary operator " + getOperator().name() + " not implemented", getSpan()); return null; } } } /** A ternary operation is an abbreviated if/then/else operation, and equivalent to the the ternary operator in Java. **/ public static class TernaryOperation extends Expression { private final Expression condition; private final Expression trueExpression; private final Expression falseExpression; public TernaryOperation (Expression condition, Expression trueExpression, Expression falseExpression) { super(new Span(condition.getSpan(), falseExpression.getSpan())); this.condition = condition; this.trueExpression = trueExpression; this.falseExpression = falseExpression; } public Expression getCondition () { return condition; } public Expression getTrueExpression () { return trueExpression; } public Expression getFalseExpression () { return falseExpression; } @Override public Object evaluate (ExpressionTemplate template, ExpressionTemplateContext context) throws IOException { Object condition = getCondition().evaluate(template, context); if (!(condition instanceof Boolean)) { ExpressionError.error("Condition of ternary operator must be a boolean, got " + condition + ".", getSpan()); } return ((Boolean)condition) ? getTrueExpression().evaluate(template, context) : getFalseExpression().evaluate(template, context); } } /** A null literal, with the single value null **/ public static class NullLiteral extends Expression { public NullLiteral (Span span) { super(span); } @Override public Object evaluate (ExpressionTemplate template, ExpressionTemplateContext context) throws IOException { return null; } } /** A boolean literal, with the values true and false **/ public static class BooleanLiteral extends Expression { private final Boolean value; public BooleanLiteral (Span literal) { super(literal); this.value = Boolean.parseBoolean(literal.getText()); } public Boolean getValue () { return value; } @Override public Object evaluate (ExpressionTemplate template, ExpressionTemplateContext context) throws IOException { return value; } } /** A double precision floating point literal. Must be marked with the d suffix, e.g. "1.0d". **/ public static class DoubleLiteral extends Expression { private final Double value; public DoubleLiteral (Span literal) { super(literal); this.value = Double.parseDouble(literal.getText().substring(0, literal.getText().length() - 1)); } public Double getValue () { return value; } @Override public Object evaluate (ExpressionTemplate template, ExpressionTemplateContext context) throws IOException { return value; } } /** A single precision floating point literla. May be optionally marked with the f suffix, e.g. "1.0f". **/ public static class FloatLiteral extends Expression { private final Float value; public FloatLiteral (Span literal) { super(literal); String text = literal.getText(); if (text.charAt(text.length() - 1) == 'f') { text = text.substring(0, text.length() - 1); } this.value = Float.parseFloat(text); } public Float getValue () { return value; } @Override public Object evaluate (ExpressionTemplate template, ExpressionTemplateContext context) throws IOException { return value; } } /** A byte literal. Must be marked with the b suffix, e.g. "123b". **/ public static class ByteLiteral extends Expression { private final Byte value; public ByteLiteral (Span literal) { super(literal); this.value = Byte.parseByte(literal.getText().substring(0, literal.getText().length() - 1)); } public Byte getValue () { return value; } @Override public Object evaluate (ExpressionTemplate template, ExpressionTemplateContext context) throws IOException { return value; } } /** A short literal. Must be marked with the s suffix, e.g. "123s". **/ public static class ShortLiteral extends Expression { private final Short value; public ShortLiteral (Span literal) { super(literal); this.value = Short.parseShort(literal.getText().substring(0, literal.getText().length() - 1)); } public Short getValue () { return value; } @Override public Object evaluate (ExpressionTemplate template, ExpressionTemplateContext context) throws IOException { return value; } } /** An integer literal. **/ public static class IntegerLiteral extends Expression { private final Integer value; public IntegerLiteral (Span literal) { super(literal); this.value = Integer.parseInt(literal.getText()); } public Integer getValue () { return value; } @Override public Object evaluate (ExpressionTemplate template, ExpressionTemplateContext context) throws IOException { return value; } } /** A long integer literal. Must be marked with the l suffix, e.g. "123l". **/ public static class LongLiteral extends Expression { private final Long value; public LongLiteral (Span literal) { super(literal); this.value = Long.parseLong(literal.getText().substring(0, literal.getText().length() - 1)); } public Long getValue () { return value; } @Override public Object evaluate (ExpressionTemplate template, ExpressionTemplateContext context) throws IOException { return value; } } /** A character literal, enclosed in single quotes. Supports escape sequences \n, \r,\t, \' and \\. **/ public static class CharacterLiteral extends Expression { private final Character value; public CharacterLiteral (Span literal) { super(literal); String text = literal.getText(); if (text.length() > 3) { if (text.charAt(2) == 'n') { value = '\n'; } else if (text.charAt(2) == 'r') { value = '\r'; } else if (text.charAt(2) == 't') { value = '\t'; } else if (text.charAt(2) == '\\') { value = '\\'; } else if (text.charAt(2) == '\'') { value = '\''; } else { ExpressionError.error("Unknown escape sequence '" + literal.getText() + "'.", literal); value = 0; // never reached } } else { this.value = literal.getText().charAt(1); } } public Character getValue () { return value; } @Override public Object evaluate (ExpressionTemplate template, ExpressionTemplateContext context) throws IOException { return value; } } /** A string literal, enclosed in double quotes. Supports escape sequences \n, \r, \t, \" and \\. **/ public static class StringLiteral extends Expression { private final String value; public StringLiteral (Span literal) { super(literal); String text = getSpan().getText(); String unescapedValue = text.substring(1, text.length() - 1); StringBuilder builder = new StringBuilder(); CharacterStream stream = new CharacterStream(unescapedValue); while (stream.hasMore()) { if (stream.match("\\\\", true)) { builder.append('\\'); } else if (stream.match("\\n", true)) { builder.append('\n'); } else if (stream.match("\\r", true)) { builder.append('\r'); } else if (stream.match("\\t", true)) { builder.append('\t'); } else if (stream.match("\\\"", true)) { builder.append('"'); } else { builder.append(stream.consume()); } } value = builder.toString(); } /** Returns the literal without quotes **/ public String getValue () { return value; } @Override public Object evaluate (ExpressionTemplate template, ExpressionTemplateContext context) throws IOException { return value; } } /** Represents a top-level variable access by name. E.g. in the expression "a + 1", a would be encoded as a * VariableAccess node. Variables can be both read (in expressions) and written to (in assignments). Variable values are looked * up and written to a {@link ExpressionTemplateContext}. **/ public static class VariableAccess extends Expression { public VariableAccess (Span name) { super(name); } public Span getVariableName () { return getSpan(); } @Override public Object evaluate (ExpressionTemplate template, ExpressionTemplateContext context) throws IOException { Object value = context.get(getSpan().getText()); //if (value == null) ExpressionError.error("找不到变量'" + getSpan().getText() + "'或变量值为null", getSpan()); return value; } } /** Represents a map or array element access of the form mapOrArray[keyOrIndex]. Maps and arrays may only be read * from. **/ public static class MapOrArrayAccess extends Expression { private final Expression mapOrArray; private final Expression keyOrIndex; public MapOrArrayAccess (Span span, Expression mapOrArray, Expression keyOrIndex) { super(span); this.mapOrArray = mapOrArray; this.keyOrIndex = keyOrIndex; } /** Returns an expression that must evaluate to a map or array. **/ public Expression getMapOrArray () { return mapOrArray; } /** Returns an expression that is used as the key or index to fetch a map or array element. **/ public Expression getKeyOrIndex () { return keyOrIndex; } @SuppressWarnings("rawtypes") @Override public Object evaluate (ExpressionTemplate template, ExpressionTemplateContext context) throws IOException { Object mapOrArray = getMapOrArray().evaluate(template, context); if (mapOrArray == null) { return null; } Object keyOrIndex = getKeyOrIndex().evaluate(template, context); if (keyOrIndex == null) { return null; } if (mapOrArray instanceof Map) { return ((Map)mapOrArray).get(keyOrIndex); } else if (mapOrArray instanceof List) { if (!(keyOrIndex instanceof Number)) { ExpressionError.error("List index must be an integer, but was " + keyOrIndex.getClass().getSimpleName(), getKeyOrIndex().getSpan()); } int index = ((Number)keyOrIndex).intValue(); return ((List)mapOrArray).get(index); } else { if (!(keyOrIndex instanceof Number)) { ExpressionError.error("Array index must be an integer, but was " + keyOrIndex.getClass().getSimpleName(), getKeyOrIndex().getSpan()); } int index = ((Number)keyOrIndex).intValue(); if (mapOrArray instanceof int[]) { return ((int[])mapOrArray)[index]; } else if (mapOrArray instanceof float[]) { return ((float[])mapOrArray)[index]; } else if (mapOrArray instanceof double[]) { return ((double[])mapOrArray)[index]; } else if (mapOrArray instanceof boolean[]) { return ((boolean[])mapOrArray)[index]; } else if (mapOrArray instanceof char[]) { return ((char[])mapOrArray)[index]; } else if (mapOrArray instanceof short[]) { return ((short[])mapOrArray)[index]; } else if (mapOrArray instanceof long[]) { return ((long[])mapOrArray)[index]; } else if (mapOrArray instanceof byte[]) { return ((byte[])mapOrArray)[index]; } else if (mapOrArray instanceof String) { return Character.toString(((String)mapOrArray).charAt(index)); } else { return ((Object[])mapOrArray)[index]; } } } } /** Represents an access of a member (field or method or entry in a map) of the form object.member. Members may * only be read from. **/ public static class MemberAccess extends Expression { private final Expression object; private final Span name; private Object cachedMember; public MemberAccess (Expression object, Span name) { super(name); this.object = object; this.name = name; } /** Returns the object on which to access the member. **/ public Expression getObject () { return object; } /** The name of the member. **/ public Span getName () { return name; } /** Returns the cached member descriptor as returned by {@link AbstractReflection#getField(Object, String)} or * {@link AbstractReflection#getMethod(Object, String, Object...)}. See {@link #setCachedMember(Object)}. **/ public Object getCachedMember () { return cachedMember; } /** Sets the member descriptor as returned by {@link AbstractReflection#getField(Object, String)} or * {@link AbstractReflection#getMethod(Object, String, Object...)} for faster member lookups. Called by {@link AstInterpreter} the * first time this node is evaluated. Subsequent evaluations can use the cached descriptor, avoiding a costly reflective * lookup. **/ public void setCachedMember (Object cachedMember) { this.cachedMember = cachedMember; } @SuppressWarnings("rawtypes") @Override public Object evaluate (ExpressionTemplate template, ExpressionTemplateContext context) throws IOException { Object object = getObject().evaluate(template, context); if (object == null) { return null; } // special case for array.length if (object.getClass().isArray() && "length".equals(getName().getText())) { return Array.getLength(object); } // special case for map, allows to do map.key instead of map[key] if (object instanceof Map) { Map map = (Map)object; return map.get(getName().getText()); } Object field = getCachedMember(); if (field != null) { try { return AbstractReflection.getInstance().getFieldValue(object, field); } catch (Throwable t) { // fall through } } String text = getName().getText(); field = AbstractReflection.getInstance().getField(object, text); if (field == null) { String methodName = null; if(text.length() > 1){ methodName = text.substring(0,1).toUpperCase() + text.substring(1); }else{ methodName = text.toUpperCase(); } MemberAccess access = new MemberAccess(this.object, new Span("get" + methodName)); MethodCall methodCall = new MethodCall(getName(),access, Collections.emptyList()); try { return methodCall.evaluate(template, context); } catch (TemplateException e) { if(ExceptionUtils.indexOfThrowable(e, InvocationTargetException.class) > -1){ ExpressionError.error(String.format("在%s中调用方法get%s发生异常" ,object.getClass() ,methodName), getSpan(),e); return null; } access = new MemberAccess(this.object, new Span("is" + methodName)); methodCall = new MethodCall(getName(),access, Collections.emptyList()); try { return methodCall.evaluate(template, context); } catch (TemplateException e1) { if(ExceptionUtils.indexOfThrowable(e1, InvocationTargetException.class) > -1){ ExpressionError.error(String.format("在%s中调用方法is%s发生异常" ,object.getClass() ,methodName), getSpan(),e); return null; } ExpressionError.error(String.format("在%s中找不到属性%s或者方法get%s、方法is%s" ,object.getClass() ,getName().getText() ,methodName ,methodName), getSpan()); } } } setCachedMember(field); return AbstractReflection.getInstance().getFieldValue(object, field); } } public static class LambdaAccess extends Expression { private final List elements = new ArrayList<>(); private final Expression function; private MemberAccess arrayLike; public LambdaAccess (Span span, Expression function, List elements) { super(span); this.elements.addAll(elements); this.function = function; } public LambdaAccess (Span span, Expression function, Expression... elements) { super(span); this.elements.addAll(Arrays.asList(elements)); this.function = function; } public List getElements() { return elements; } /** Returns an expression that is used as the key or index to fetch a map or array element. **/ public Expression getFunction() { return function; } @SuppressWarnings("rawtypes") @Override public Object evaluate (ExpressionTemplate template, ExpressionTemplateContext context) throws IOException { if (ArrayLikeLambdaExecutor.SUPPORT_METHOD.contains(arrayLike.getName().getText())) { return oneArgumentParser(template, context); } else { ExpressionError.error("只支持 "+ String.join(",", ArrayLikeLambdaExecutor.SUPPORT_METHOD) +"。不支持的lambda函数: " + arrayLike.getName().getText(), arrayLike.getSpan()); } return null; } private Object oneArgumentParser(ExpressionTemplate template, ExpressionTemplateContext context) { Expression object = arrayLike.getObject(); if (object instanceof VariableAccess) { VariableAccess arrLike = (VariableAccess) object; String parName = arrLike.getVariableName().getText(); Object arrLikeObj = context.get(parName); if (arrLikeObj.getClass().isArray()) { try { // Integer size = (Integer) arrLikeObj.getClass().getDeclaredField("length").get(arrLikeObj); int size = Array.getLength(arrLikeObj); List list = new ArrayList<>(size); for (int i = 0; i < size; i++) { list.add(Array.get(arrLikeObj, i)); } arrLikeObj = list; } catch (Exception e) { e.printStackTrace(); } } else if (arrLikeObj instanceof Iterator) { Iterator it = (Iterator) arrLikeObj; List list = new ArrayList<>(); it.forEachRemaining(list::add); arrLikeObj = list; } else if (arrLikeObj instanceof Enumeration) { Enumeration en = (Enumeration) arrLikeObj; arrLikeObj = Collections.list(en); } if (arrLikeObj instanceof Collection) { Collection coll = (Collection) arrLikeObj; AtomicInteger ai = new AtomicInteger(); return new ArrayLikeLambdaExecutor.MultipleArgumentsLambda(elements, lambdaArgumentsValues -> { try { context.push(); for (int i = 0; i < elements.size() && i < lambdaArgumentsValues.length; i++) { Expression expression = elements.get(i); context.setOnCurrentScope(expression.getSpan().getText(), lambdaArgumentsValues[i]); } return function.evaluate(template, context); } catch (IOException e) { e.printStackTrace(); throw new RuntimeException(e); } finally { context.pop(); } }); } } return null; } public void setArrayLike(MemberAccess arrayLike) { this.arrayLike = arrayLike; } public MemberAccess getArrayLike() { return arrayLike; } } /** Represents a call to a top-level function. A function may either be a {@link FunctionalInterface} stored in a * {@link ExpressionTemplateContext}, or a {@link Macro} defined in a template. */ public static class FunctionCall extends Expression { private final Expression function; private final List arguments; private Object cachedFunction; private final ThreadLocal cachedArguments; public FunctionCall (Span span, Expression function, List arguments) { super(span); this.function = function; this.arguments = arguments; this.cachedArguments = new ThreadLocal(); } /** Return the expression that must evaluate to a {@link FunctionalInterface} or a {@link Macro}. **/ public Expression getFunction () { return function; } /** Returns the list of expressions to be passed to the function as arguments. **/ public List getArguments () { return arguments; } /** Returns the cached "function" descriptor as returned by {@link AbstractReflection#getMethod(Object, String, Object...)} or the * {@link Macro}. See {@link #setCachedFunction(Object)}. **/ public Object getCachedFunction () { return cachedFunction; } /** Sets the "function" descriptor as returned by {@link AbstractReflection#getMethod(Object, String, Object...)} for faster * lookups, or the {@link Macro} to be called. Called by {@link AstInterpreter} the first time this node is evaluated. * Subsequent evaluations can use the cached descriptor, avoiding a costly reflective lookup. **/ public void setCachedFunction (Object cachedFunction) { this.cachedFunction = cachedFunction; } /** Returns a scratch buffer to store arguments in when calling the function in {@link AstInterpreter}. Avoids generating * garbage. **/ public Object[] getCachedArguments () { Object[] args = cachedArguments.get(); if (args == null) { args = new Object[arguments.size()]; cachedArguments.set(args); } return args; } /** Must be invoked when this node is done evaluating so we don't leak memory **/ public void clearCachedArguments () { Object[] args = getCachedArguments(); for (int i = 0; i < args.length; i++) { args[i] = null; } } @Override public Object evaluate (ExpressionTemplate template, ExpressionTemplateContext context) throws IOException { try { Object[] argumentValues = getCachedArguments(); List arguments = getArguments(); for (int i = 0, n = argumentValues.length; i < n; i++) { Expression expr = arguments.get(i); argumentValues[i] = expr.evaluate(template, context); } // This is a special case to handle template level macros. If a call to a macro is // made, evaluating the function expression will result in an exception, as the // function name can't be found in the context. Instead we need to manually check // if the function expression is a VariableAccess and if so, if it can be found // in the context. Object function = null; if (getFunction() instanceof VariableAccess) { VariableAccess varAccess = (VariableAccess)getFunction(); function = context.get(varAccess.getVariableName().getText()); } else { function = getFunction().evaluate(template, context); } if (function != null) { Object method = getCachedFunction(); if (method != null) { try { return AbstractReflection.getInstance().callMethod(function, method, argumentValues); } catch (Throwable t) { // fall through } } method = AbstractReflection.getInstance().getMethod(function, null, argumentValues); if (method == null) { ExpressionError.error("Couldn't find function.", getSpan()); } setCachedFunction(method); try { return AbstractReflection.getInstance().callMethod(function, method, argumentValues); } catch (Throwable t) { ExpressionError.error(t.getMessage(), getSpan(), t); return null; // never reached } } else { ExpressionError.error("Couldn't find function " + getFunction(), getSpan()); return null; // never reached } } finally { clearCachedArguments(); } } } /** Represents a call to a method of the form object.method(a, b, c). **/ public static class MethodCall extends Expression { private final MemberAccess method; private final List arguments; private Object cachedMethod; private final ThreadLocal cachedArguments; private boolean cachedMethodStatic; public MethodCall (Span span, MemberAccess method, List arguments) { super(span); this.method = method; this.arguments = arguments; this.cachedArguments = new ThreadLocal(); } /** Returns the object on which to call the method. **/ public Expression getObject () { return method.getObject(); } /** Returns the method to call. **/ public MemberAccess getMethod () { return method; } /** Returns the list of expressions to be passed to the function as arguments. **/ public List getArguments () { return arguments; } /** Returns the cached member descriptor as returned by {@link AbstractReflection#getMethod(Object, String, Object...)}. See * {@link #setCachedMember(Object)}. **/ public Object getCachedMethod () { return cachedMethod; } /** Sets the method descriptor as returned by {@link AbstractReflection#getMethod(Object, String, Object...)} for faster lookups. * Called by {@link AstInterpreter} the first time this node is evaluated. Subsequent evaluations can use the cached * descriptor, avoiding a costly reflective lookup. **/ public void setCachedMethod (Object cachedMethod) { this.cachedMethod = cachedMethod; } /** Returns a scratch buffer to store arguments in when calling the function in {@link AstInterpreter}. Avoids generating * garbage. **/ public Object[] getCachedArguments () { Object[] args = cachedArguments.get(); if (args == null) { args = new Object[arguments.size()]; cachedArguments.set(args); } return args; } /** Must be invoked when this node is done evaluating so we don't leak memory **/ public void clearCachedArguments () { Object[] args = getCachedArguments(); for (int i = 0; i < args.length; i++) { args[i] = null; } } @Override public Object evaluate (ExpressionTemplate template, ExpressionTemplateContext context) throws IOException { try { Object object = getObject().evaluate(template, context); if (object == null) { return null; } Object[] argumentValues = getCachedArguments(); List arguments = getArguments(); for (int i = 0, n = argumentValues.length; i < n; i++) { Expression expr = arguments.get(i); argumentValues[i] = expr.evaluate(template, context); } // Otherwise try to find a corresponding method or field pointing to a lambda. Object method = getCachedMethod(); if (method != null) { try { if (isCachedMethodStatic()) { return AbstractReflection.getInstance().callMethod(null, method, object, argumentValues); } return AbstractReflection.getInstance().callMethod(object, method, argumentValues); } catch (Throwable t) { t.printStackTrace(); // fall through } } method = AbstractReflection.getInstance().getMethod(object, getMethod().getName().getText(), argumentValues); if (method != null) { // found the method on the object, call it setCachedMethod(method); try { return AbstractReflection.getInstance().callMethod(object, method, argumentValues); } catch (Throwable t) { ExpressionError.error(t.getMessage(), getSpan(), t); return null; // never reached } } method = AbstractReflection.getInstance().getExtensionMethod(object, getMethod().getName().getText(), argumentValues); if(method != null){ try { int argumentLength = argumentValues == null ? 0 : argumentValues.length; Object[] parameters = new Object[argumentLength + 1]; if(argumentLength > 0){ for (int i = 0; i < argumentLength; i++) { parameters[i + 1] = argumentValues[i]; } } parameters[0] = object; if(object.getClass().isArray()){ Object[] objs = new Object[Array.getLength(object)]; for (int i = 0,len = objs.length; i < len; i++) { Array.set(objs, i, Array.get(object, i)); } parameters[0] = objs; } return AbstractReflection.getInstance().callMethod(object, method, parameters); } catch (Throwable t) { ExpressionError.error(t.getMessage(), getSpan(), t); // fall through return null; } }else { // didn't find the method on the object, try to find a field pointing to a lambda Object field = AbstractReflection.getInstance().getField(object, getMethod().getName().getText()); if (field == null){ ExpressionError.error("在'" + object.getClass() + "'中找不到方法 " + getMethod().getName().getText() + "(" + StringUtils.join(JavaReflection.getStringTypes(argumentValues),",") + ")", getSpan()); } Object function = AbstractReflection.getInstance().getFieldValue(object, field); method = AbstractReflection.getInstance().getMethod(function, null, argumentValues); if (method == null){ ExpressionError.error("在'" + object.getClass() + "'中找不到方法 " + getMethod().getName().getText() + "("+ StringUtils.join(JavaReflection.getStringTypes(argumentValues),",") +")", getSpan()); } try { return AbstractReflection.getInstance().callMethod(function, method, argumentValues); } catch (Throwable t) { ExpressionError.error(t.getMessage(), getSpan(), t); return null; // never reached } } } finally { clearCachedArguments(); } } public void setCachedMethodStatic(boolean cachedMethodStatic) { this.cachedMethodStatic = cachedMethodStatic; } public boolean isCachedMethodStatic() { return cachedMethodStatic; } } /** Represents a map literal of the form { key: value, key2: value, ... } which can be nested. */ public static class MapLiteral extends Expression { private final List keys; private final List values; public MapLiteral (Span span, List keys, List values) { super(span); this.keys = keys; this.values = values; } public List getKeys () { return keys; } public List getValues () { return values; } @Override public Object evaluate (ExpressionTemplate template, ExpressionTemplateContext context) throws IOException { Map map = new HashMap<>(); for (int i = 0, n = keys.size(); i < n; i++) { Object value = values.get(i).evaluate(template, context); Token tokenKey = keys.get(i); String key = tokenKey.getSpan().getText(); if(tokenKey.getType() == TokenType.StringLiteral){ key = (String) new StringLiteral(tokenKey.getSpan()).evaluate(template, context); }else if(key != null && key.startsWith("$")){ Object objKey = context.get(key.substring(1)); if(objKey != null){ key = objKey.toString(); }else{ key = null; } } map.put(key, value); } return map; } } /** Represents a list literal of the form [ value, value2, value3, ...] which can be nested. */ public static class ListLiteral extends Expression { public final List values; public ListLiteral (Span span, List values) { super(span); this.values = values; } public List getValues () { return values; } @Override public Object evaluate (ExpressionTemplate template, ExpressionTemplateContext context) throws IOException { List list = new ArrayList<>(); for (int i = 0, n = values.size(); i < n; i++) { list.add(values.get(i).evaluate(template, context)); } return list; } } }