io.marioslab.basis.template.parsing.Ast Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of template Show documentation
Show all versions of template Show documentation
An expressive templating engine for Java and the JVM
The newest version!
package io.marioslab.basis.template.parsing;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import io.marioslab.basis.template.Error;
import io.marioslab.basis.template.Error.TemplateException;
import io.marioslab.basis.template.Template;
import io.marioslab.basis.template.TemplateContext;
import io.marioslab.basis.template.TemplateLoader.Source;
import io.marioslab.basis.template.interpreter.AstInterpreter;
import io.marioslab.basis.template.interpreter.Reflection;
import io.marioslab.basis.template.parsing.Ast.Return.ReturnValue;
import io.marioslab.basis.template.parsing.Parser.Macros;
/** 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 (Template template, TemplateContext context, OutputStream out) 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 byte[] bytes;
public Text (Span text) {
super(text);
try {
String unescapedValue = text.getText();
StringBuilder builder = new StringBuilder();
CharacterStream stream = new CharacterStream(new Source(text.getSource().getPath(), unescapedValue));
while (stream.hasMore()) {
if (stream.match("\\{", true))
builder.append('{');
else if (stream.match("\\}", true))
builder.append('}');
else
builder.append(stream.consume());
}
bytes = builder.toString().getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
Error.error("Couldn't convert text to UTF-8 string.", text);
throw new RuntimeException(""); // never reached
}
}
/** Returns the UTF-8 representation of this text node. **/
public byte[] getBytes () {
return bytes;
}
@Override
public Object evaluate (Template template, TemplateContext context, OutputStream out) throws IOException {
out.write(getBytes());
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;
Error.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 (Template template, TemplateContext context, OutputStream out) throws IOException {
Object operand = getOperand().evaluate(template, context, out);
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 {
Error.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)) Error.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;
Error.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();
Error.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 {
Error.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 {
Error.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 {
Error.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 {
Error.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 {
Error.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 {
Error.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 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 {
Error.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 {
Error.error("Operands for greater/equal operator must be numbers" + left + ", " + right + ".", getSpan());
return null; // never reached
}
}
private Object evaluateAnd (Object left, Template template, TemplateContext context, OutputStream out) throws IOException {
if (!(left instanceof Boolean)) Error.error("Left operand must be a boolean, got " + left + ".", getLeftOperand().getSpan());
if (!(Boolean)left) return false;
Object right = getRightOperand().evaluate(template, context, out);
if (!(right instanceof Boolean)) Error.error("Right operand must be a boolean, got " + right + ".", getRightOperand().getSpan());
return (Boolean)left && (Boolean)right;
}
private Object evaluateOr (Object left, Template template, TemplateContext context, OutputStream out) throws IOException {
if (!(left instanceof Boolean)) Error.error("Left operand must be a boolean, got " + left + ".", getLeftOperand().getSpan());
if ((Boolean)left) return true;
Object right = getRightOperand().evaluate(template, context, out);
if (!(right instanceof Boolean)) Error.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)) Error.error("Left operand must be a boolean, got " + left + ".", getLeftOperand().getSpan());
if (!(right instanceof Boolean)) Error.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 (Template template, TemplateContext context, OutputStream out) throws IOException {
if (getOperator() == BinaryOperator.Assignment) {
if (!(getLeftOperand() instanceof VariableAccess)) Error.error("Can only assign to top-level variables in context.", getLeftOperand().getSpan());
Object value = getRightOperand().evaluate(template, context, out);
context.set(((VariableAccess)getLeftOperand()).getVariableName().getText(), value);
return null;
}
Object left = getLeftOperand().evaluate(template, context, out);
Object right = getOperator() == BinaryOperator.And || getOperator() == BinaryOperator.Or ? null : getRightOperand().evaluate(template, context, out);
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, out);
case Or:
return evaluateOr(left, template, context, out);
case Xor:
return evaluateXor(left, right);
default:
Error.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 (Template template, TemplateContext context, OutputStream out) throws IOException {
Object condition = getCondition().evaluate(template, context, out);
if (!(condition instanceof Boolean)) Error.error("Condition of ternary operator must be a boolean, got " + condition + ".", getSpan());
return ((Boolean)condition) ? getTrueExpression().evaluate(template, context, out) : getFalseExpression().evaluate(template, context, out);
}
}
/** A null literal, with the single value null
**/
public static class NullLiteral extends Expression {
public NullLiteral (Span span) {
super(span);
}
@Override
public Object evaluate (Template template, TemplateContext context, OutputStream out) 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 (Template template, TemplateContext context, OutputStream out) 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 (Template template, TemplateContext context, OutputStream out) 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 (Template template, TemplateContext context, OutputStream out) 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 (Template template, TemplateContext context, OutputStream out) 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 (Template template, TemplateContext context, OutputStream out) 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 (Template template, TemplateContext context, OutputStream out) 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 (Template template, TemplateContext context, OutputStream out) 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 {
Error.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 (Template template, TemplateContext context, OutputStream out) 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(new Source(literal.getSource().getPath(), 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 (Template template, TemplateContext context, OutputStream out) 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 TemplateContext}. **/
public static class VariableAccess extends Expression {
public VariableAccess (Span name) {
super(name);
}
public Span getVariableName () {
return getSpan();
}
@Override
public Object evaluate (Template template, TemplateContext context, OutputStream out) throws IOException {
Object value = context.get(getSpan().getText());
if (value == null) Error.error("Couldn't find variable '" + getSpan().getText() + "' in context.", 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 (Template template, TemplateContext context, OutputStream out) throws IOException {
Object mapOrArray = getMapOrArray().evaluate(template, context, out);
if (mapOrArray == null) Error.error("Couldn't find map or array in context.", getSpan());
Object keyOrIndex = getKeyOrIndex().evaluate(template, context, out);
if (keyOrIndex == null) Error.error("Couldn't evaluate key or index.", getKeyOrIndex().getSpan());
if (mapOrArray instanceof Map) {
return ((Map)mapOrArray).get(keyOrIndex);
} else if (mapOrArray instanceof List) {
if (!(keyOrIndex instanceof Number)) {
Error.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)) {
Error.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
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 Reflection#getField(Object, String)} or
* {@link Reflection#getMethod(Object, String, Object...)}. See {@link #setCachedMember(Object)}. **/
public Object getCachedMember () {
return cachedMember;
}
/** Sets the member descriptor as returned by {@link Reflection#getField(Object, String)} or
* {@link Reflection#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 (Template template, TemplateContext context, OutputStream out) throws IOException {
Object object = getObject().evaluate(template, context, out);
if (object == null) Error.error("Couldn't find object in context.", getSpan());
// special case for array.length
if (object.getClass().isArray() && getName().getText().equals("length")) {
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 Reflection.getInstance().getFieldValue(object, field);
} catch (Throwable t) {
// fall through
}
}
field = Reflection.getInstance().getField(object, getName().getText());
if (field == null) {
Error.error("Couldn't find field '" + getName().getText() + "' for object of type '" + object.getClass().getSimpleName() + "'.", getSpan());
}
setCachedMember(field);
return Reflection.getInstance().getFieldValue(object, field);
}
}
/** Represents a call to a top-level function. A function may either be a {@link FunctionalInterface} stored in a
* {@link TemplateContext}, 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
© 2015 - 2025 Weber Informatics LLC | Privacy Policy