org.mvel2.compiler.ExpressionCompiler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of tbel Show documentation
Show all versions of tbel Show documentation
TBEL is a powerful expression language for ThingsBoard platform user-defined functions.
Original implementation is based on MVEL.
/**
* MVEL 2.0
* Copyright (C) 2007 The Codehaus
* Mike Brock, Dhanji Prasanna, John Graham, Mark Proctor
*
* 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
*
* http://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 org.mvel2.compiler;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.mvel2.CompileException;
import org.mvel2.ErrorDetail;
import org.mvel2.MVEL;
import org.mvel2.Operator;
import org.mvel2.ParserContext;
import org.mvel2.ast.ASTNode;
import org.mvel2.ast.Assignment;
import org.mvel2.ast.DeepOperativeAssignmentNode;
import org.mvel2.ast.LiteralNode;
import org.mvel2.ast.NewObjectNode;
import org.mvel2.ast.OperatorNode;
import org.mvel2.ast.Substatement;
import org.mvel2.ast.Union;
import org.mvel2.util.ASTLinkedList;
import org.mvel2.util.CompilerTools;
import org.mvel2.util.ErrorUtil;
import org.mvel2.util.ExecutionStack;
import org.mvel2.util.ParseTools;
import org.mvel2.util.StringAppender;
import static org.mvel2.DataConversion.canConvert;
import static org.mvel2.DataConversion.convert;
import static org.mvel2.Operator.PTABLE;
import static org.mvel2.ast.ASTNode.COMPILE_IMMEDIATE;
import static org.mvel2.ast.ASTNode.OPT_SUBTR;
import static org.mvel2.util.CompilerTools.finalizePayload;
import static org.mvel2.util.CompilerTools.signNumber;
import static org.mvel2.util.ParseTools.subCompileExpression;
import static org.mvel2.util.ParseTools.unboxPrimitive;
import static org.mvel2.util.ParseTools.validateStatements;
/**
* This is the main MVEL compiler.
*/
public class ExpressionCompiler extends AbstractParser {
private Class returnType;
private boolean verifyOnly = false;
private boolean verifying = true;
private boolean secondPassOptimization = false;
public CompiledExpression compile() {
try {
this.debugSymbols = pCtx.isDebugSymbols();
return (CompiledExpression) validateStatements(this.expr, _compile());
}
finally {
if (pCtx.isFatalError()) {
StringAppender err = new StringAppender();
Iterator iter = pCtx.getErrorList().iterator();
ErrorDetail e;
while (iter.hasNext()) {
e = iter.next();
e = ErrorUtil.rewriteIfNeeded(e, expr, cursor);
if (e.getExpr() != expr) {
iter.remove();
}
else {
err.append("\n - ").append("(").append(e.getLineNumber()).append(",").append(e.getColumn()).append(")")
.append(" ").append(e.getMessage());
}
}
//noinspection ThrowFromFinallyBlock
throw new CompileException("Failed to compileShared: " + pCtx.getErrorList().size()
+ " compilation error(s): " + err.toString(), pCtx.getErrorList(), expr, cursor, pCtx);
}
}
}
/**
* Initiate an in-context compileShared. This method should really only be called by the internal API.
*
* @return compiled expression object
*/
public CompiledExpression _compile() {
ASTNode tk;
ASTNode tkOp;
ASTNode tkOp2;
ASTNode tkLA;
ASTNode tkLA2;
int op, lastOp = -1;
cursor = start;
ASTLinkedList astBuild = new ASTLinkedList();
stk = new ExecutionStack();
dStack = new ExecutionStack();
compileMode = true;
boolean firstLA;
try {
if (verifying) {
pCtx.initializeTables();
}
fields |= COMPILE_IMMEDIATE;
main_loop: while ((tk = nextToken()) != null) {
/**
* If this is a debug symbol, just add it and continue.
*/
if (tk.fields == -1) {
astBuild.addTokenNode(tk);
continue;
}
/**
* Record the type of the current node..
*/
returnType = tk.getEgressType();
if (tk instanceof Substatement) {
String key = new String(expr, tk.getStart(), tk.getOffset());
Map cec = pCtx.getCompiledExpressionCache();
Map rtc = pCtx.getReturnTypeCache();
CompiledExpression compiled = cec.get(key);
Class rt = rtc.get(key);
if (compiled == null) {
ExpressionCompiler subCompiler = new ExpressionCompiler(expr, tk.getStart(), tk.getOffset(), pCtx);
compiled = subCompiler._compile();
rt = subCompiler.getReturnType();
cec.put(key, compiled);
rtc.put(key, rt);
}
tk.setAccessor(compiled);
returnType = rt;
}
/**
* This kludge of code is to handle compileShared-time literal reduction. We need to avoid
* reducing for certain literals like, 'this', ternary and ternary else.
*/
if (!verifyOnly && tk.isLiteral()) {
if (literalOnly == -1) literalOnly = 1;
if ((tkOp = nextTokenSkipSymbols()) != null && tkOp.isOperator()
&& !tkOp.isOperator(Operator.TERNARY) && !tkOp.isOperator(Operator.TERNARY_ELSE)) {
/**
* If the next token is ALSO a literal, then we have a candidate for a compileShared-time literal
* reduction.
*/
if ((tkLA = nextTokenSkipSymbols()) != null && tkLA.isLiteral()
&& tkOp.getOperator() < 34 && ((lastOp == -1
|| (lastOp < PTABLE.length && PTABLE[lastOp] < PTABLE[tkOp.getOperator()])))) {
stk.push(tk.getLiteralValue(), tkLA.getLiteralValue(), op = tkOp.getOperator());
/**
* Reduce the token now.
*/
if (isArithmeticOperator(op)) {
if (!compileReduce(op, astBuild)) continue;
}
else {
reduce();
}
firstLA = true;
/**
* Now we need to check to see if this is a continuing reduction.
*/
while ((tkOp2 = nextTokenSkipSymbols()) != null) {
if (isBooleanOperator(tkOp2.getOperator())) {
astBuild.addTokenNode(new LiteralNode(stk.pop(), pCtx), verify(pCtx, tkOp2));
break;
}
else if ((tkLA2 = nextTokenSkipSymbols()) != null) {
if (tkLA2.isLiteral()) {
stk.push(tkLA2.getLiteralValue(), op = tkOp2.getOperator());
if (isArithmeticOperator(op)) {
if (!compileReduce(op, astBuild)) continue main_loop;
}
else {
reduce();
}
}
else {
/**
* A reducable line of literals has ended. We must now terminate here and
* leave the rest to be determined at runtime.
*/
if (!stk.isEmpty()) {
astBuild.addTokenNode(new LiteralNode(getStackValueResult(), pCtx));
}
astBuild.addTokenNode(new OperatorNode(tkOp2.getOperator(), expr, st, pCtx), verify(pCtx, tkLA2));
break;
}
firstLA = false;
literalOnly = 0;
}
else {
if (firstLA) {
/**
* There are more tokens, but we can't reduce anymore. So
* we create a reduced token for what we've got.
*/
astBuild.addTokenNode(new LiteralNode(getStackValueResult(), pCtx));
}
else {
/**
* We have reduced additional tokens, but we can't reduce
* anymore.
*/
astBuild.addTokenNode(new LiteralNode(getStackValueResult(), pCtx), tkOp2);
if (tkLA2 != null) astBuild.addTokenNode(verify(pCtx, tkLA2));
}
break;
}
}
/**
* If there are no more tokens left to parse, we check to see if
* we've been doing any reducing, and if so we create the token
* now.
*/
if (!stk.isEmpty())
astBuild.addTokenNode(new LiteralNode(getStackValueResult(), pCtx));
continue;
}
else {
astBuild.addTokenNode(verify(pCtx, tk), verify(pCtx, tkOp));
if (tkLA != null) astBuild.addTokenNode(verify(pCtx, tkLA));
continue;
}
}
else if (tkOp != null && !tkOp.isOperator() && !(tk.getLiteralValue() instanceof Class)) {
throw new CompileException("unexpected token: " + tkOp.getName(), expr, tkOp.getStart());
}
else {
literalOnly = 0;
astBuild.addTokenNode(verify(pCtx, tk));
if (tkOp != null) astBuild.addTokenNode(verify(pCtx, tkOp));
continue;
}
}
else {
if (tk.isOperator()) {
lastOp = tk.getOperator();
}
else {
literalOnly = 0;
}
}
astBuild.addTokenNode(verify(pCtx, tk));
}
astBuild.finish();
if (verifying && !verifyOnly) {
pCtx.processTables();
}
if (!stk.isEmpty()) {
throw new CompileException("COMPILE ERROR: non-empty stack after compileShared.", expr, cursor);
}
if (!verifyOnly) {
try {
return new CompiledExpression(finalizePayload(astBuild, secondPassOptimization, pCtx), pCtx.getSourceFile(), returnType, pCtx.getParserConfiguration(), literalOnly == 1);
} catch (RuntimeException e) {
throw new CompileException(e.getMessage(), expr, st, e);
}
}
else {
try {
returnType = CompilerTools.getReturnType(astBuild, pCtx.isStrongTyping());
} catch (RuntimeException e) {
throw new CompileException(e.getMessage(), expr, st, e);
}
return null;
}
}
catch (NullPointerException e) {
throw new CompileException("not a statement, or badly formed structure", expr, st, e);
}
catch (CompileException e) {
throw ErrorUtil.rewriteIfNeeded(e, expr, st);
}
catch (Throwable e) {
if (e instanceof RuntimeException) throw (RuntimeException) e;
else {
throw new CompileException(e.getMessage(), expr, st, e);
}
}
}
private Object getStackValueResult() {
return (fields & OPT_SUBTR) == 0 ? stk.pop() : signNumber(stk.pop());
}
private boolean compileReduce(int opCode, ASTLinkedList astBuild) {
switch (arithmeticFunctionReduction(opCode)) {
case OP_TERMINATE:
/**
* The reduction failed because we encountered a non-literal,
* so we must now back out and cleanup.
*/
stk.xswap_op();
astBuild.addTokenNode(new LiteralNode(stk.pop(), pCtx));
astBuild.addTokenNode(
(OperatorNode) splitAccumulator.pop(),
verify(pCtx, (ASTNode) splitAccumulator.pop())
);
return false;
case OP_OVERFLOW:
/**
* Back out completely, pull everything back off the stack and add the instructions
* to the output payload as they are.
*/
LiteralNode rightValue = new LiteralNode(stk.pop(), pCtx);
OperatorNode operator = new OperatorNode((Integer) stk.pop(), expr, st, pCtx);
astBuild.addTokenNode(new LiteralNode(stk.pop(), pCtx), operator);
astBuild.addTokenNode(rightValue, (OperatorNode) splitAccumulator.pop());
astBuild.addTokenNode(verify(pCtx, (ASTNode) splitAccumulator.pop()));
return false;
case OP_NOT_LITERAL:
ASTNode tkLA2 = (ASTNode) stk.pop();
Integer tkOp2 = (Integer) stk.pop();
astBuild.addTokenNode(new LiteralNode(getStackValueResult(), pCtx));
astBuild.addTokenNode(new OperatorNode(tkOp2, expr, st, pCtx), verify(pCtx, tkLA2));
return false;
}
return true;
}
private static boolean isBooleanOperator(int operator) {
return operator == Operator.AND || operator == Operator.OR || operator == Operator.TERNARY || operator == Operator.TERNARY_ELSE;
}
protected ASTNode verify(ParserContext pCtx, ASTNode tk) {
if (tk.isOperator() && (tk.getOperator().equals(Operator.AND) || tk.getOperator().equals(Operator.OR))) {
secondPassOptimization = true;
}
if (tk.isDiscard() || tk.isOperator()) {
return tk;
}
else if (tk.isLiteral()) {
/**
* Convert literal values from the default ASTNode to the more-efficient LiteralNode.
*/
if ((fields & COMPILE_IMMEDIATE) != 0 && tk.getClass() == ASTNode.class) {
return new LiteralNode(tk.getLiteralValue(), pCtx);
}
else {
return tk;
}
}
if (verifying) {
if (tk.isIdentifier()) {
PropertyVerifier propVerifier = new PropertyVerifier(expr, tk.getStart(), tk.getOffset(), pCtx);
if (tk instanceof Union) {
propVerifier.setCtx(((Union) tk).getLeftEgressType());
tk.setEgressType(returnType = propVerifier.analyze());
}
else {
tk.setEgressType(returnType = propVerifier.analyze());
if (propVerifier.isFqcn()) {
tk.setAsFQCNReference();
}
if (propVerifier.isClassLiteral()) {
return new LiteralNode(returnType, pCtx);
}
if (propVerifier.isInput()) {
pCtx.addInput(tk.getAbsoluteName(), propVerifier.isDeepProperty() ? Object.class : returnType);
}
if (!propVerifier.isMethodCall() && !returnType.isEnum() && !pCtx.isOptimizerNotified() &&
pCtx.isStrongTyping() && !pCtx.isVariableVisible(tk.getAbsoluteName()) && !tk.isFQCN()) {
throw new CompileException("no such identifier: " + tk.getAbsoluteName(), expr, tk.getStart());
}
}
}
else if (tk.isAssignment()) {
Assignment a = (Assignment) tk;
if (a.getAssignmentVar() != null) {
// pCtx.makeVisible(a.getAssignmentVar());
PropertyVerifier propVerifier = new PropertyVerifier(a.getAssignmentVar(), pCtx);
tk.setEgressType(returnType = propVerifier.analyze( ));
if (!a.isNewDeclaration() && propVerifier.isResolvedExternally()) {
pCtx.addInput(tk.getAbsoluteName(), returnType);
}
ExecutableStatement c = (ExecutableStatement) subCompileExpression(expr, tk.getStart(),
tk.getOffset(), pCtx);
if (pCtx.isStrictTypeEnforcement()) {
/**
* If we're using strict type enforcement, we need to see if this coercion can be done now,
* or fail epicly.
*/
if (!returnType.isAssignableFrom(c.getKnownEgressType()) && c.isLiteralOnly()) {
if (canConvert(c.getKnownEgressType(), returnType)) {
/**
* We convert the literal to the proper type.
*/
try {
// Don't convert to a literal for DeepOperativeAssignmentNode. Coercion will be done by BinaryOperation
if (!(a instanceof DeepOperativeAssignmentNode)) {
a.setValueStatement(new ExecutableLiteral(convert(c.getValue(null, null), returnType)));
}
return tk;
}
catch (Exception e) {
// fall through.
}
}
else if (returnType.isPrimitive()
&& unboxPrimitive(c.getKnownEgressType()).equals(returnType)) {
/**
* We ignore boxed primitive cases, since MVEL does not recognize primitives.
*/
return tk;
}
throw new CompileException(
"cannot assign type " + c.getKnownEgressType().getName()
+ " to " + returnType.getName(), expr, st);
}
}
}
}
else if (tk instanceof NewObjectNode) {
// this is a bit of a hack for now.
NewObjectNode n = (NewObjectNode) tk;
List parms = ParseTools.parseMethodOrConstructor(tk.getNameAsArray());
if (parms != null) {
for (char[] p : parms) {
MVEL.analyze(p, pCtx);
}
}
}
returnType = tk.getEgressType();
}
if (!tk.isLiteral() && tk.getClass() == ASTNode.class && (tk.getFields() & ASTNode.ARRAY_TYPE_LITERAL) == 0) {
if (pCtx.isStrongTyping()) tk.strongTyping();
tk.storePctx();
tk.storeInLiteralRegister(pCtx);
}
return tk;
}
public ExpressionCompiler(String expression) {
setExpression(expression);
}
public ExpressionCompiler(String expression, boolean verifying) {
setExpression(expression);
this.verifying = verifying;
}
public ExpressionCompiler(char[] expression) {
setExpression(expression);
}
public ExpressionCompiler(String expression, ParserContext ctx) {
setExpression(expression);
this.pCtx = ctx;
}
public ExpressionCompiler(char[] expression, int start, int offset) {
this.expr = expression;
this.start = start;
this.end = start + offset;
this.end = trimLeft(this.end);
this.length = this.end - start;
}
public ExpressionCompiler(String expression, int start, int offset, ParserContext ctx) {
this.expr = expression.toCharArray();
this.start = start;
this.end = start + offset;
this.end = trimLeft(this.end);
this.length = this.end - start;
this.pCtx = ctx;
}
public ExpressionCompiler(char[] expression, int start, int offset, ParserContext ctx) {
this.expr = expression;
this.start = start;
this.end = start + offset;
this.end = trimLeft(this.end);
this.length = this.end - start;
this.pCtx = ctx;
}
public ExpressionCompiler(char[] expression, ParserContext ctx) {
setExpression(expression);
this.pCtx = ctx;
}
public boolean isVerifying() {
return verifying;
}
public void setVerifying(boolean verifying) {
this.verifying = verifying;
}
public boolean isVerifyOnly() {
return verifyOnly;
}
public void setVerifyOnly(boolean verifyOnly) {
this.verifyOnly = verifyOnly;
}
public Class getReturnType() {
return returnType;
}
public void setReturnType(Class returnType) {
this.returnType = returnType;
}
public ParserContext getParserContextState() {
return pCtx;
}
public boolean isLiteralOnly() {
return literalOnly == 1;
}
}