SingleEvaluatedExpressionParser.java
/*
* Copyright 2017-2022 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micronaut.expressions.parser;
import io.micronaut.core.annotation.Internal;
import io.micronaut.expressions.parser.ast.ExpressionNode;
import io.micronaut.expressions.parser.ast.access.BeanContextAccess;
import io.micronaut.expressions.parser.ast.access.ContextElementAccess;
import io.micronaut.expressions.parser.ast.access.ContextMethodCall;
import io.micronaut.expressions.parser.ast.access.ElementMethodCall;
import io.micronaut.expressions.parser.ast.access.EnvironmentAccess;
import io.micronaut.expressions.parser.ast.access.PropertyAccess;
import io.micronaut.expressions.parser.ast.access.SubscriptOperator;
import io.micronaut.expressions.parser.ast.access.ThisAccess;
import io.micronaut.expressions.parser.ast.conditional.ElvisOperator;
import io.micronaut.expressions.parser.ast.conditional.TernaryExpression;
import io.micronaut.expressions.parser.ast.literal.BoolLiteral;
import io.micronaut.expressions.parser.ast.literal.DoubleLiteral;
import io.micronaut.expressions.parser.ast.literal.FloatLiteral;
import io.micronaut.expressions.parser.ast.literal.IntLiteral;
import io.micronaut.expressions.parser.ast.literal.LongLiteral;
import io.micronaut.expressions.parser.ast.literal.NullLiteral;
import io.micronaut.expressions.parser.ast.literal.StringLiteral;
import io.micronaut.expressions.parser.ast.operator.binary.AddOperator;
import io.micronaut.expressions.parser.ast.operator.binary.AndOperator;
import io.micronaut.expressions.parser.ast.operator.binary.EqOperator;
import io.micronaut.expressions.parser.ast.operator.binary.InstanceofOperator;
import io.micronaut.expressions.parser.ast.operator.binary.MatchesOperator;
import io.micronaut.expressions.parser.ast.operator.binary.MathOperator;
import io.micronaut.expressions.parser.ast.operator.binary.NeqOperator;
import io.micronaut.expressions.parser.ast.operator.binary.OrOperator;
import io.micronaut.expressions.parser.ast.operator.binary.PowOperator;
import io.micronaut.expressions.parser.ast.operator.binary.RelationalOperator;
import io.micronaut.expressions.parser.ast.operator.unary.EmptyOperator;
import io.micronaut.expressions.parser.ast.operator.unary.NegOperator;
import io.micronaut.expressions.parser.ast.operator.unary.NotOperator;
import io.micronaut.expressions.parser.ast.operator.unary.PosOperator;
import io.micronaut.expressions.parser.ast.types.TypeIdentifier;
import io.micronaut.expressions.parser.exception.ExpressionParsingException;
import io.micronaut.expressions.parser.token.Token;
import io.micronaut.expressions.parser.token.TokenType;
import io.micronaut.expressions.parser.token.Tokenizer;
import io.micronaut.sourcegen.model.ExpressionDef.ComparisonOperation.OpType;
import java.util.ArrayList;
import java.util.List;
import static io.micronaut.expressions.parser.token.TokenType.AND;
import static io.micronaut.expressions.parser.token.TokenType.BEAN_CONTEXT;
import static io.micronaut.expressions.parser.token.TokenType.BOOL;
import static io.micronaut.expressions.parser.token.TokenType.COLON;
import static io.micronaut.expressions.parser.token.TokenType.COMMA;
import static io.micronaut.expressions.parser.token.TokenType.DECREMENT;
import static io.micronaut.expressions.parser.token.TokenType.DIV;
import static io.micronaut.expressions.parser.token.TokenType.DOT;
import static io.micronaut.expressions.parser.token.TokenType.DOUBLE;
import static io.micronaut.expressions.parser.token.TokenType.ELVIS;
import static io.micronaut.expressions.parser.token.TokenType.EMPTY;
import static io.micronaut.expressions.parser.token.TokenType.ENVIRONMENT;
import static io.micronaut.expressions.parser.token.TokenType.EQ;
import static io.micronaut.expressions.parser.token.TokenType.EXPRESSION_CONTEXT_REF;
import static io.micronaut.expressions.parser.token.TokenType.FLOAT;
import static io.micronaut.expressions.parser.token.TokenType.GT;
import static io.micronaut.expressions.parser.token.TokenType.GTE;
import static io.micronaut.expressions.parser.token.TokenType.IDENTIFIER;
import static io.micronaut.expressions.parser.token.TokenType.INCREMENT;
import static io.micronaut.expressions.parser.token.TokenType.INSTANCEOF;
import static io.micronaut.expressions.parser.token.TokenType.INT;
import static io.micronaut.expressions.parser.token.TokenType.LONG;
import static io.micronaut.expressions.parser.token.TokenType.LT;
import static io.micronaut.expressions.parser.token.TokenType.LTE;
import static io.micronaut.expressions.parser.token.TokenType.L_PAREN;
import static io.micronaut.expressions.parser.token.TokenType.L_SQUARE;
import static io.micronaut.expressions.parser.token.TokenType.MATCHES;
import static io.micronaut.expressions.parser.token.TokenType.MINUS;
import static io.micronaut.expressions.parser.token.TokenType.MOD;
import static io.micronaut.expressions.parser.token.TokenType.MUL;
import static io.micronaut.expressions.parser.token.TokenType.NE;
import static io.micronaut.expressions.parser.token.TokenType.NOT;
import static io.micronaut.expressions.parser.token.TokenType.NULL;
import static io.micronaut.expressions.parser.token.TokenType.OR;
import static io.micronaut.expressions.parser.token.TokenType.PLUS;
import static io.micronaut.expressions.parser.token.TokenType.POW;
import static io.micronaut.expressions.parser.token.TokenType.QMARK;
import static io.micronaut.expressions.parser.token.TokenType.R_PAREN;
import static io.micronaut.expressions.parser.token.TokenType.R_SQUARE;
import static io.micronaut.expressions.parser.token.TokenType.SAFE_NAV;
import static io.micronaut.expressions.parser.token.TokenType.STRING;
import static io.micronaut.expressions.parser.token.TokenType.THIS;
import static io.micronaut.expressions.parser.token.TokenType.TYPE_IDENTIFIER;
import static io.micronaut.sourcegen.model.ExpressionDef.MathBinaryOperation.OpType.DIVISION;
import static io.micronaut.sourcegen.model.ExpressionDef.MathBinaryOperation.OpType.MODULUS;
import static io.micronaut.sourcegen.model.ExpressionDef.MathBinaryOperation.OpType.MULTIPLICATION;
import static io.micronaut.sourcegen.model.ExpressionDef.MathBinaryOperation.OpType.SUBTRACTION;
/**
* Parser for building AST for single evaluated expression.
* A single expression is parsed as a whole,
* it cannot contain multiple expressions.
*
* @author Sergey Gavrilov
* @since 4.0.0
*/
@Internal
public final class SingleEvaluatedExpressionParser implements EvaluatedExpressionParser {
private final Tokenizer tokenizer;
private Token lookahead;
/**
* Instantiates a parser for single passed expression.
* Expression string must not contain expression template wrapper like #{...}
*
* @param expression expression to parse
*/
public SingleEvaluatedExpressionParser(String expression) {
this.tokenizer = new Tokenizer(expression);
this.lookahead = tokenizer.getNextToken();
}
@Override
public ExpressionNode parse() throws ExpressionParsingException {
try {
final ExpressionNode expressionNode = expression();
if (lookahead != null) {
throw new ExpressionParsingException("Unexpected token: " + lookahead.value());
}
return expressionNode;
} catch (NullPointerException ex) {
throw new ExpressionParsingException("Unexpected end of input");
}
}
// Expression
// : TernaryExpression
// ;
private ExpressionNode expression() {
return ternaryExpression();
}
// TernaryExpression
// : OrExpression
// | OrExpression '?' Expression ':' Expression
// ;
private ExpressionNode ternaryExpression() {
ExpressionNode orExpression = orExpression();
if (lookahead != null) {
if (lookahead.type() == QMARK) {
eat(QMARK);
ExpressionNode trueExpr = expression();
eat(COLON);
ExpressionNode falseExpr = expression();
return new TernaryExpression(orExpression, trueExpr, falseExpr);
} else if (lookahead.type() == ELVIS) {
eat(ELVIS);
ExpressionNode falseExpr = expression();
return new ElvisOperator(orExpression, falseExpr);
}
}
return orExpression;
}
// OrExpression
// : AndExpression
// | OrExpression '||' AndExpression -> AndExpression '||' AndExpression '||' AndExpression
// ;
private ExpressionNode orExpression() {
ExpressionNode leftNode = andExpression();
while (lookahead != null && lookahead.type() == OR) {
eat(OR);
leftNode = new OrOperator(leftNode, andExpression());
}
return leftNode;
}
// AndExpression
// : EqualityExpression
// | AndExpression '&&' EqualityExpression
// ;
private ExpressionNode andExpression() {
ExpressionNode leftNode = equalityExpression();
while (lookahead != null && lookahead.type() == AND) {
eat(AND);
leftNode = new AndOperator(leftNode, equalityExpression());
}
return leftNode;
}
// EqualityExpression
// : RelationalExpression
// | EqualityExpression '==' RelationalExpression
// | EqualityExpression '!=' RelationalExpression
// ;
private ExpressionNode equalityExpression() {
ExpressionNode leftNode = relationalExpression();
while (lookahead != null && lookahead.type().isOneOf(EQ, NE)) {
TokenType tokenType = lookahead.type();
eat(tokenType);
if (tokenType == EQ) {
leftNode = new EqOperator(leftNode, relationalExpression());
} else if (tokenType == NE) {
leftNode = new NeqOperator(leftNode, relationalExpression());
}
}
return leftNode;
}
// RelationalExpression
// : AdditiveExpression
// | RelationalExpression RelOperator AdditiveExpression
// | RelationalExpression 'instanceof' TypeIdentifier
// | RelationalExpression 'matches' StringLiteral
// ;
private ExpressionNode relationalExpression() {
ExpressionNode leftNode = additiveExpression();
while (lookahead != null && (lookahead.type()
.isOneOf(GT, GTE, LT, LTE, INSTANCEOF, MATCHES))) {
TokenType tokenType = lookahead.type();
eat(lookahead.type());
leftNode = switch (tokenType) {
case GT -> new RelationalOperator(leftNode, additiveExpression(), OpType.GREATER_THAN);
case LT -> new RelationalOperator(leftNode, additiveExpression(), OpType.LESS_THAN);
case GTE -> new RelationalOperator(leftNode, additiveExpression(), OpType.GREATER_THAN_OR_EQUAL);
case LTE -> new RelationalOperator(leftNode, additiveExpression(), OpType.LESS_THAN_OR_EQUAL);
case INSTANCEOF -> new InstanceofOperator(leftNode, typeIdentifier(true));
case MATCHES -> new MatchesOperator(leftNode, stringLiteral());
default -> leftNode;
};
}
return leftNode;
}
// AdditiveExpression
// : PowExpression
// | AdditiveExpression '+' PowExpression
// | AdditiveExpression '-' PowExpression
// ;
private ExpressionNode additiveExpression() {
ExpressionNode leftNode = multiplicativeExpression();
while (lookahead != null && lookahead.type().isOneOf(PLUS, MINUS)) {
TokenType tokenType = lookahead.type();
eat(tokenType);
if (tokenType == PLUS) {
leftNode = new AddOperator(leftNode, multiplicativeExpression());
} else if (tokenType == MINUS) {
leftNode = new MathOperator(leftNode, multiplicativeExpression(), SUBTRACTION);
}
}
return leftNode;
}
// MultiplicativeExpression
// : PowExpression
// | MultiplicativeExpression '*' PowExpression
// | MultiplicativeExpression '/' PowExpression
// | MultiplicativeExpression '%' PowExpression
// ;
private ExpressionNode multiplicativeExpression() {
ExpressionNode leftNode = powExpression();
while (lookahead != null && lookahead.type().isOneOf(MUL, DIV, MOD)) {
TokenType tokenType = lookahead.type();
eat(tokenType);
if (tokenType == MUL) {
leftNode = new MathOperator(leftNode, powExpression(), MULTIPLICATION);
} else if (tokenType == DIV) {
leftNode = new MathOperator(leftNode, powExpression(), DIVISION);
} else if (tokenType == MOD) {
leftNode = new MathOperator(leftNode, powExpression(), MODULUS);
}
}
return leftNode;
}
// PowExpression
// : UnaryExpression
// | PowExpression '^' UnaryExpression
// ;
private ExpressionNode powExpression() {
ExpressionNode leftNode = unaryExpression();
while (lookahead != null && lookahead.type() == POW) {
eat(POW);
leftNode = new PowOperator(leftNode, unaryExpression());
}
return leftNode;
}
// UnaryExpression
// : '+' UnaryExpression
// | '-' UnaryExpression
// | '!' UnaryExpression
// | '++' UnaryExpression
// | '--' UnaryExpression
// | PostfixExpression
// ;
private ExpressionNode unaryExpression() {
TokenType tokenType = lookahead.type();
if (tokenType == PLUS) {
eat(PLUS);
return new PosOperator(unaryExpression());
} else if (tokenType == MINUS) {
eat(MINUS);
return new NegOperator(unaryExpression());
} else if (tokenType == NOT) {
eat(NOT);
return new NotOperator(unaryExpression());
} else if (tokenType == EMPTY) {
eat(EMPTY);
return new EmptyOperator(unaryExpression());
} else if (tokenType == INCREMENT) {
throw new ExpressionParsingException("Prefix increment operation is not supported");
} else if (tokenType == DECREMENT) {
throw new ExpressionParsingException("Prefix decrement operation is not supported");
} else {
return postfixExpression();
}
}
// PostfixExpression
// : PrimaryExpression
// | PostfixExpression '.' MethodOrPropertyAccess
// | PostfixExpression '?.' MethodOrPropertyAccess with safe navigation
// | PostfixExpression SubscriptOperator
// | PostfixExpression '++'
// | PostfixExpression '--'
// ;
private ExpressionNode postfixExpression() {
ExpressionNode leftNode = primaryExpression();
while (lookahead != null && (lookahead.type()
.isOneOf(DOT, SAFE_NAV, L_SQUARE, INCREMENT, DECREMENT))) {
TokenType tokenType = lookahead.type();
if (tokenType == INCREMENT) {
throw new ExpressionParsingException("Postfix increment operation is not " +
"supported");
} else if (tokenType == DECREMENT) {
throw new ExpressionParsingException("Postfix decrement operation is not " +
"supported");
} else if (tokenType == DOT) {
eat(DOT);
leftNode = methodOrPropertyAccess(leftNode, false);
} else if (tokenType == SAFE_NAV) {
eat(SAFE_NAV);
leftNode = methodOrPropertyAccess(leftNode, true);
} else if (tokenType == L_SQUARE) {
leftNode = subscriptOperator(leftNode);
} else {
throw new ExpressionParsingException("Unexpected token: " + lookahead.value());
}
}
return leftNode;
}
// PrimaryExpression
// : EvaluationContextAccess
// | BeanContextAccess
// | EnvironmentAccess
// | ThisAccess
// | TypeIdentifier
// | ParenthesizedExpression
// | Literal
// ;
private ExpressionNode primaryExpression() {
return switch (lookahead.type()) {
case EXPRESSION_CONTEXT_REF -> evaluationContextAccess(true);
case IDENTIFIER -> evaluationContextAccess(false);
case BEAN_CONTEXT -> beanContextAccess();
case ENVIRONMENT -> environmentAccess();
case THIS -> thisAccess();
case TYPE_IDENTIFIER -> typeIdentifier(true);
case L_PAREN -> parenthesizedExpression();
case STRING, INT, LONG, DOUBLE, FLOAT, BOOL, NULL -> literal();
default -> throw new ExpressionParsingException("Unexpected token: " + lookahead.value());
};
}
// ThisAccess
// : 'this'
// ;
private ExpressionNode thisAccess() {
eat(THIS);
return new ThisAccess();
}
// EvaluationContextAccess
// : '#' Identifier
// | '#' Identifier MethodArguments
// | Identifier
// | Identifier MethodArguments
// ;
private ExpressionNode evaluationContextAccess(boolean prefixed) {
if (prefixed) {
eat(EXPRESSION_CONTEXT_REF);
}
String identifier = eat(IDENTIFIER).value();
if (lookahead != null && lookahead.type() == L_PAREN) {
List<ExpressionNode> methodArguments = methodArguments();
return new ContextMethodCall(identifier, methodArguments);
}
return new ContextElementAccess(identifier);
}
// BeanContextAccess
// : 'ctx' '[' TypeIdentifier ']'
// ;
private ExpressionNode beanContextAccess() {
eat(BEAN_CONTEXT);
eat(L_SQUARE);
TypeIdentifier typeIdentifier;
if (lookahead != null) {
typeIdentifier = lookahead.type() == TYPE_IDENTIFIER
? typeIdentifier(true)
: typeIdentifier(false);
} else {
throw new ExpressionParsingException("Bean context access must be followed by type reference");
}
eat(R_SQUARE);
return new BeanContextAccess(typeIdentifier);
}
// EnvironmentAccess
// : 'env' '[' Expression ']'
// ;
private ExpressionNode environmentAccess() {
eat(ENVIRONMENT);
eat(L_SQUARE);
ExpressionNode propertyName = expression();
eat(R_SQUARE);
return new EnvironmentAccess(propertyName);
}
// MethodOrFieldAccess
// : SimpleIdentifier
// | SimpleIdentifier MethodArguments
// ;
private ExpressionNode methodOrPropertyAccess(ExpressionNode callee, boolean nullSafe) {
String identifier = eat(IDENTIFIER).value();
if (lookahead != null && lookahead.type() == L_PAREN) {
List<ExpressionNode> methodArguments = methodArguments();
return new ElementMethodCall(callee, identifier, methodArguments, nullSafe);
}
return new PropertyAccess(callee, identifier, nullSafe);
}
// SubscriptOperator
// '[' Expression ']'
private ExpressionNode subscriptOperator(ExpressionNode callee) {
eat(L_SQUARE);
ExpressionNode indexExpression = expression();
SubscriptOperator subscriptOperator = new SubscriptOperator(
callee,
indexExpression
);
eat(R_SQUARE);
return subscriptOperator;
}
// MethodArguments:
// '(' MethodArgumentsList ')'
// ;
private List<ExpressionNode> methodArguments() {
eat(L_PAREN);
List<ExpressionNode> arguments = new ArrayList<>();
if (lookahead.type() != R_PAREN) {
arguments = methodArgumentsList();
}
eat(R_PAREN);
return arguments;
}
// MethodArgumentsList
// : Expression
// | MethodArgumentsList ',' Expression
// ;
private List<ExpressionNode> methodArgumentsList() {
List<ExpressionNode> arguments = new ArrayList<>();
if (lookahead.type() != R_PAREN) {
ExpressionNode firstArgument = expression();
arguments.add(firstArgument);
while (lookahead.type() != R_PAREN) {
eat(COMMA);
arguments.add(expression());
}
}
return arguments;
}
// TypeReference
// : 'T(' ChainedIdentifier')'
// ;
private TypeIdentifier typeIdentifier(boolean wrapped) {
if (wrapped) {
eat(TYPE_IDENTIFIER);
}
List<String> parts = new ArrayList<>();
parts.add(eat(IDENTIFIER).value());
while (lookahead != null && lookahead.type() == DOT) {
eat(DOT);
parts.add(eat(IDENTIFIER).value());
}
if (wrapped) {
eat(R_PAREN);
}
return new TypeIdentifier(String.join(".", parts));
}
// ParenthesizedExpression
// : '(' Expression ')'
// ;
private ExpressionNode parenthesizedExpression() {
eat(L_PAREN);
ExpressionNode parenthesizedExpression = expression();
eat(R_PAREN);
return parenthesizedExpression;
}
// Literal
// : StringLiteral
// | IntLiteral
// | LongLiteral
// | DecimalLiteral
// | FloatLiteral
// | BoolLiteral
// ;
private ExpressionNode literal() {
return switch (lookahead.type()) {
case DOUBLE -> doubleLiteral();
case FLOAT -> floatLiteral();
case INT -> intLiteral();
case STRING -> stringLiteral();
case LONG -> longLiteral();
case BOOL -> boolLiteral();
case NULL -> nullLiteral();
default -> throw new ExpressionParsingException("Unknown literal type: " + lookahead.type());
};
}
private StringLiteral stringLiteral() {
Token token = eat(STRING);
String value = token.value();
// removing surrounding quotes
return new StringLiteral(token.value().substring(1, value.length() - 1));
}
private DoubleLiteral doubleLiteral() {
Token token = eat(DOUBLE);
return new DoubleLiteral(Double.parseDouble(token.value()));
}
private FloatLiteral floatLiteral() {
Token token = eat(FLOAT);
return new FloatLiteral(Float.parseFloat(token.value()));
}
private IntLiteral intLiteral() {
Token token = eat(INT);
return new IntLiteral(Integer.decode(token.value()));
}
private LongLiteral longLiteral() {
Token token = eat(LONG);
return new LongLiteral(Long.decode(token.value().replaceAll("([lL])", "")));
}
private BoolLiteral boolLiteral() {
Token token = eat(BOOL);
return new BoolLiteral(Boolean.parseBoolean(token.value()));
}
private NullLiteral nullLiteral() {
eat(NULL);
return new NullLiteral();
}
private Token eat(TokenType tokenType) {
if (lookahead == null) {
throw new ExpressionParsingException("Unexpected end of input. Expected: '" + tokenType + "'");
}
Token token = lookahead;
if (token.type() != tokenType) {
throw new ExpressionParsingException("Unexpected token: " + token.value() + ". Expected: '" + tokenType + "'");
}
lookahead = tokenizer.getNextToken();
return token;
}
}