first
Some checks failed
Reduce Adoc / reduce (push) Failing after 57s
Java Unit Test with Maven / test (push) Failing after 1m42s

This commit is contained in:
2025-12-29 13:59:13 +08:00
commit 2dfed7464e
555 changed files with 35895 additions and 0 deletions

436
src/main/antlr4/QLParser.g4 Normal file
View File

@@ -0,0 +1,436 @@
parser grammar QLParser;
options {
tokenVocab = QLexer;
}
@header {
package com.alibaba.qlexpress4.aparser;
import com.alibaba.qlexpress4.aparser.ParserOperatorManager.OpType;
import static com.alibaba.qlexpress4.aparser.ParserOperatorManager.OpType.*;
import static com.alibaba.qlexpress4.QLPrecedences.*;
import static com.alibaba.qlexpress4.aparser.InterpolationMode.*;
}
@members {
protected boolean isOpType(String lexeme, OpType opType) {
return false;
}
protected Integer precedence(String lexeme) {
return 0;
}
protected InterpolationMode getInterpolationMode() {
return SCRIPT;
}
protected boolean isStrictNewLines() {
return true;
}
}
// grammar
program
: (newlines? importDeclaration)* newlines? blockStatements? EOF
;
blockStatements
: blockStatement+
;
newlines : NEWLINE+;
nextStatement
: {_input.LA(1) == Token.EOF || _input.LA(1) == QLexer.RBRACE}? | ';' | {isStrictNewLines()}? NEWLINE;
blockStatement
: localVariableDeclaration ';' # localVariableDeclarationStatement
| THROW expression nextStatement # throwStatement
| WHILE '(' newlines? expression newlines? ')' '{' newlines? blockStatements? newlines? '}' # whileStatement
| FOR '(' newlines? forInit (forCondition=expression)? ';' newlines? (forUpdate=expression)? newlines? ')' '{' newlines? blockStatements? newlines? '}' # traditionalForStatement
| FOR '(' newlines? declType? varId ':' expression newlines? ')' '{' newlines? blockStatements? newlines? '}' # forEachStatement
| FUNCTION varId '(' newlines? formalOrInferredParameterList? newlines? ')' LBRACE newlines? blockStatements? newlines? RBRACE # functionStatement
| MACRO varId LBRACE newlines? blockStatements? newlines? RBRACE # macroStatement
| (BREAK | CONTINUE) nextStatement # breakContinueStatement
| RETURN expression? nextStatement # returnStatement
| (';' | NEWLINE) # emptyStatement
| expression nextStatement # expressionStatement
;
localVariableDeclaration
: declType variableDeclaratorList
;
forInit
: localVariableDeclaration ';'
| expression ';'
| ';'
;
variableDeclaratorList
: variableDeclarator (newlines? ',' newlines? variableDeclarator)*
;
variableDeclarator
: variableDeclaratorId (EQ newlines? variableInitializer)?
;
variableDeclaratorId
: varId dims?
;
variableInitializer
: expression
| arrayInitializer
;
arrayInitializer
: LBRACE newlines? variableInitializerList? newlines? RBRACE
;
variableInitializerList
: variableInitializer (newlines? ',' newlines? variableInitializer)* ','?
;
// decl type
declType
: primitiveType dims?
| clsType dims?
;
declTypeNoArr
: primitiveType
| clsType
;
primitiveType
: 'byte'
| 'short'
| 'int'
| 'long'
| 'float'
| 'double'
| 'boolean'
| 'char'
;
referenceType
: clsType dims?
| primitiveType dims
;
dims
: LBRACK RBRACK (LBRACK RBRACK)*
;
clsTypeNoTypeArguments
: varId ('.' varId)*
;
clsType
: varId ('.' varId)* typeArguments?
;
typeArguments
: LT newlines? typeArgumentList? newlines? (GT | RIGHSHIFT | URSHIFT)?
| NOEQ
;
typeArgumentList
: typeArgument (newlines? ',' newlines? typeArgument)*
;
typeArgument
: referenceType
| wildcard
;
wildcard
: '?' wildcardBounds?
;
wildcardBounds
: 'extends' referenceType
| 'super' referenceType
;
// expression
expression
: leftHandSide assignOperator newlines? expression
| ternaryExpr
;
leftHandSide
: varId (newlines? pathPart)*
;
ternaryExpr
: condition=baseExpr[1] (QUESTION newlines? thenExpr=baseExpr[0] COLON newlines? elseExpr=expression)?
;
baseExpr [int p]
: primary ({_input.LA(1) != Token.EOF && (!isStrictNewLines() || _input.LA(1) != QLexer.NEWLINE) &&
isOpType(_input.LT(1).getText(), MIDDLE) && precedence(_input.LT(1).getText()) >= $p}? leftAsso)*
;
leftAsso
: binaryop newlines? baseExpr[precedence(_localctx.binaryop().getStart().getText()) + 1];
binaryop
: opId | varId
;
// primary
primary
: (prefixExpress)? primaryNoFixPathable (newlines? pathPart)* (suffixExpress)?
| primaryNoFixNonPathable
;
prefixExpress
: {_input.LT(1).getType() != Token.EOF && isOpType(_input.LT(1).getText(), PREFIX)}? opId
;
suffixExpress
: {_input.LT(1).getType() != Token.EOF && isOpType(_input.LT(1).getText(), SUFFIX)}? opId
;
primaryNoFixPathable
: literal # constExpr
| '(' newlines? declType newlines? ')' primary # castExpr
| '(' newlines? expression newlines? ')' # groupExpr
| NEW varId ('.' varId)* typeArguments? '(' newlines? argumentList? newlines? ')' # newObjExpr
| NEW declTypeNoArr dimExprs # newEmptyArrExpr
| NEW declTypeNoArr dims arrayInitializer # newInitArrExpr
| varId # varIdExpr
| primitiveType # typeExpr
| '[' newlines? listItems? newlines? ']' # listExpr
| LBRACE newlines? mapEntries newlines? RBRACE # mapExpr
| LBRACE newlines? blockStatements? newlines? RBRACE # blockExpr
| SELECTOR_START SelectorVariable_VANME # contextSelectExpr
;
primaryNoFixNonPathable
: qlIf # ifExpr
| TRY LBRACE newlines? blockStatements? newlines? RBRACE tryCatches? (newlines? tryFinally)? # tryCatchExpr
| lambdaParameters ARROW newlines? ( LBRACE newlines? blockStatements? newlines? RBRACE | expression) # lambdaExpr
;
qlIf : IF '(' newlines? condition=expression newlines? ')' newlines? THEN? newlines? thenBody (newlines? ELSE newlines? elseBody)?;
thenBody
: LBRACE newlines? blockStatements? newlines? RBRACE
| expression
| blockStatement
;
elseBody
: LBRACE newlines? blockStatements? newlines? RBRACE
// if ... else ... if ...
| qlIf
| expression
| blockStatement
;
listItems
: expression (newlines? ',' newlines? expression)* ','?
;
dimExprs
: ('[' newlines? expression newlines? ']')+
;
tryCatches
: tryCatch (newlines? tryCatch)*
;
tryCatch
: 'catch' '(' catchParams ')' LBRACE newlines? blockStatements? newlines? RBRACE
;
catchParams
: (declType ('|' declType)*)? varId
;
tryFinally
: FINALLY LBRACE newlines? blockStatements? newlines? RBRACE
;
mapEntries
: ':'
| mapEntry (',' newlines? mapEntry)* ','?
;
mapEntry
: mapKey newlines? ':' newlines? mapValue
;
mapValue
: {_input.LT(-2).getText().equals("'@class'")}? QuoteStringLiteral # clsValue
| expression # eValue
;
mapKey
: idMapKey # idKey
| doubleQuoteStringLiteral # stringKey
| QuoteStringLiteral # quoteStringKey
;
idMapKey
: varId
| FOR
| IF
| ELSE
| WHILE
| BREAK
| CONTINUE
| RETURN
| FUNCTION
| MACRO
| IMPORT
| STATIC
| NEW
| BYTE
| SHORT
| INT
| LONG
| FLOAT
| DOUBLE
| CHAR
| BOOL
| NULL
| TRUE
| FALSE
| EXTENDS
| SUPER
| TRY
| CATCH
| FINALLY
| THROW
| CLASS
| THIS
;
pathPart
: '.' varId '(' newlines? argumentList? newlines? ')' # methodInvoke
| OPTIONAL_CHAINING varId '(' newlines? argumentList? newlines? ')' # optionalMethodInvoke
| SPREAD_CHAINING varId '(' newlines? argumentList? newlines? ')' # spreadMethodInvoke
| '.' fieldId # fieldAccess
| OPTIONAL_CHAINING fieldId # optionalFieldAccess
| SPREAD_CHAINING fieldId # spreadFieldAccess
| DCOLON varId # methodAccess
| '(' newlines? argumentList? newlines? ')' # callExpr
| '[' newlines? indexValueExpr? newlines? ']' # indexExpr
| {isOpType(_input.LT(1).getText(), MIDDLE) && precedence(_input.LT(1).getText()) == GROUP}? opId newlines? varId # customPath
;
fieldId
: varId
| CLASS
| QuoteStringLiteral
;
indexValueExpr
: expression # singleIndex
| start=expression? newlines? ':' newlines? end=expression? # sliceIndex
;
argumentList
: expression (newlines? ',' newlines? expression)*
;
literal
: IntegerLiteral
| FloatingPointLiteral
| IntegerOrFloatingLiteral
| boolenLiteral
| QuoteStringLiteral
| doubleQuoteStringLiteral
| NULL
;
doubleQuoteStringLiteral
: {getInterpolationMode() == DISABLE}? DOUBLE_QUOTE StaticStringCharacters? DOUBLE_QUOTE
| DOUBLE_QUOTE (DyStrText | stringExpression)* DOUBLE_QUOTE
;
stringExpression
: {getInterpolationMode() == SCRIPT}? DyStrExprStart newlines? expression newlines? RBRACE
| {getInterpolationMode() == VARIABLE}? DyStrExprStart SelectorVariable_VANME
;
boolenLiteral
: TRUE
| FALSE
;
lambdaParameters
: varId
| '(' formalOrInferredParameterList? ')'
;
formalOrInferredParameterList
: formalOrInferredParameter (newlines? ',' newlines? formalOrInferredParameter)*
;
formalOrInferredParameter
: declType? varId
;
// import (not support import static now)
importDeclaration
// import xxx
: IMPORT varId ('.' varId)* ';' # importCls
// import .*
| IMPORT varId ('.' varId)* (DOT MUL | DOTMUL) ';' # importPack
;
// id
assignOperator
: EQ
| RIGHSHIFT_ASSGIN
| URSHIFT_ASSGIN
| LSHIFT_ASSGIN
| ADD_ASSIGN
| SUB_ASSIGN
| AND_ASSIGN
| OR_ASSIGN
| MUL_ASSIGN
| MOD_ASSIGN
| DIV_ASSIGN
| XOR_ASSIGN
;
opId
: GT
| LT
| GE
| LE
| BANG
| TILDE
| ADD
| SUB
| MUL
| DIV
| INC
| DEC
| DOTMUL
| NOEQ
| RIGHSHIFT
| URSHIFT
| LEFTSHIFT
| BIT_AND
| BIT_OR
| MOD
| CARET
| assignOperator
| OPID
;
varId
: ID
| FUNCTION
;

1196
src/main/antlr4/QLexer.g4 Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,180 @@
FOR=1
IF=2
ELSE=3
WHILE=4
BREAK=5
CONTINUE=6
RETURN=7
FUNCTION=8
MACRO=9
IMPORT=10
STATIC=11
NEW=12
BYTE=13
SHORT=14
INT=15
LONG=16
FLOAT=17
DOUBLE=18
CHAR=19
BOOL=20
NULL=21
TRUE=22
FALSE=23
EXTENDS=24
SUPER=25
TRY=26
CATCH=27
FINALLY=28
THROW=29
THEN=30
CLASS=31
THIS=32
QuoteStringLiteral=33
IntegerLiteral=34
FloatingPointLiteral=35
IntegerOrFloatingLiteral=36
LPAREN=37
RPAREN=38
LBRACE=39
RBRACE=40
LBRACK=41
RBRACK=42
DOT=43
ARROW=44
SEMI=45
COMMA=46
QUESTION=47
COLON=48
DCOLON=49
GT=50
LT=51
EQ=52
NOEQ=53
RIGHSHIFT_ASSGIN=54
RIGHSHIFT=55
OPTIONAL_CHAINING=56
SPREAD_CHAINING=57
URSHIFT_ASSGIN=58
URSHIFT=59
LSHIFT_ASSGIN=60
LEFTSHIFT=61
GE=62
LE=63
DOTMUL=64
CARET=65
ADD_ASSIGN=66
SUB_ASSIGN=67
AND_ASSIGN=68
OR_ASSIGN=69
MUL_ASSIGN=70
MOD_ASSIGN=71
DIV_ASSIGN=72
XOR_ASSIGN=73
BANG=74
TILDE=75
ADD=76
SUB=77
MUL=78
DIV=79
BIT_AND=80
BIT_OR=81
MOD=82
INC=83
DEC=84
NEWLINE=85
WS=86
COMMENT=87
LINE_COMMENT=88
OPID=89
SELECTOR_START=90
ID=91
DOUBLE_QUOTE=92
CATCH_ALL=93
StaticStringCharacters=94
DyStrExprStart=95
DyStrText=96
SelectorVariable_VANME=97
StrExpr_WS=98
'for'=1
'if'=2
'else'=3
'while'=4
'break'=5
'continue'=6
'return'=7
'function'=8
'macro'=9
'import'=10
'static'=11
'new'=12
'byte'=13
'short'=14
'int'=15
'long'=16
'float'=17
'double'=18
'char'=19
'boolean'=20
'null'=21
'true'=22
'false'=23
'extends'=24
'super'=25
'try'=26
'catch'=27
'finally'=28
'throw'=29
'then'=30
'class'=31
'this'=32
'('=37
')'=38
'{'=39
'}'=40
'['=41
']'=42
'.'=43
'->'=44
';'=45
','=46
'?'=47
':'=48
'::'=49
'>'=50
'<'=51
'='=52
'<>'=53
'>>='=54
'>>'=55
'?.'=56
'*.'=57
'>>>='=58
'>>>'=59
'<<='=60
'<<'=61
'>='=62
'<='=63
'.*'=64
'^'=65
'+='=66
'-='=67
'&='=68
'|='=69
'*='=70
'%='=71
'/='=72
'^='=73
'!'=74
'~'=75
'+'=76
'-'=77
'*'=78
'/'=79
'&'=80
'|'=81
'%'=82
'++'=83
'--'=84
'"'=92
'${'=95

View File

@@ -0,0 +1,69 @@
package com.alibaba.qlexpress4;
import com.alibaba.qlexpress4.operator.OperatorCheckStrategy;
/**
* Script validation configuration class
* Used to configure restriction rules during script validation
*
* @author QLExpress Team
*/
public class CheckOptions {
/**
* Operator check strategy for script validation
* default OperatorCheckStrategy.allowAll()
*/
private final OperatorCheckStrategy operatorCheckStrategy;
/**
* Whether to disable function calls in the script
* default false
*/
private final boolean disableFunctionCalls;
/**
* Default validation options
*/
public static final CheckOptions DEFAULT_OPTIONS = CheckOptions.builder().build();
private CheckOptions(OperatorCheckStrategy operatorCheckStrategy, boolean disableFunctionCalls) {
this.operatorCheckStrategy = operatorCheckStrategy;
this.disableFunctionCalls = disableFunctionCalls;
}
public OperatorCheckStrategy getCheckStrategy() {
return operatorCheckStrategy;
}
public boolean isDisableFunctionCalls() {
return disableFunctionCalls;
}
public static Builder builder() {
return new Builder();
}
public static class Builder {
private OperatorCheckStrategy operatorCheckStrategy = OperatorCheckStrategy.allowAll();
private boolean disableFunctionCalls = false;
private Builder() {
}
public Builder operatorCheckStrategy(OperatorCheckStrategy operatorCheckStrategy) {
this.operatorCheckStrategy = operatorCheckStrategy;
return this;
}
public Builder disableFunctionCalls(boolean disableFunctionCalls) {
this.disableFunctionCalls = disableFunctionCalls;
return this;
}
public CheckOptions build() {
return new CheckOptions(operatorCheckStrategy, disableFunctionCalls);
}
}
}

View File

@@ -0,0 +1,11 @@
package com.alibaba.qlexpress4;
/**
* Author: DQinYuan
*/
@FunctionalInterface
public interface ClassSupplier {
Class<?> loadCls(String clsQualifiedName);
}

View File

@@ -0,0 +1,39 @@
package com.alibaba.qlexpress4;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
/**
* Author: DQinYuan
*/
public class DefaultClassSupplier implements ClassSupplier {
private static final DefaultClassSupplier INSTANCE = new DefaultClassSupplier();
public static DefaultClassSupplier getInstance() {
return INSTANCE;
}
private final Map<String, Optional<Class<?>>> cache = new ConcurrentHashMap<>();
/**
* @param clsQualifiedName qualified name of class
* @return loaded class
*/
@Override
public Class<?> loadCls(String clsQualifiedName) {
Optional<Class<?>> clsOp = cache.computeIfAbsent(clsQualifiedName, this::loadClsInner);
return clsOp.orElse(null);
}
private Optional<Class<?>> loadClsInner(String clsQualifiedName) {
try {
Class<?> aClass = Class.forName(clsQualifiedName);
return Optional.of(aClass);
}
catch (ClassNotFoundException | NoClassDefFoundError e) {
return Optional.empty();
}
}
}

View File

@@ -0,0 +1,751 @@
package com.alibaba.qlexpress4;
import com.alibaba.qlexpress4.aparser.CheckVisitor;
import com.alibaba.qlexpress4.aparser.GeneratorScope;
import com.alibaba.qlexpress4.aparser.ImportManager;
import com.alibaba.qlexpress4.aparser.MacroDefine;
import com.alibaba.qlexpress4.aparser.OutFunctionVisitor;
import com.alibaba.qlexpress4.aparser.OutVarAttrsVisitor;
import com.alibaba.qlexpress4.aparser.OutVarNamesVisitor;
import com.alibaba.qlexpress4.aparser.QCompileCache;
import com.alibaba.qlexpress4.aparser.QLParser;
import com.alibaba.qlexpress4.aparser.QvmInstructionVisitor;
import com.alibaba.qlexpress4.aparser.SyntaxTreeFactory;
import com.alibaba.qlexpress4.aparser.TraceExpressionVisitor;
import com.alibaba.qlexpress4.aparser.compiletimefunction.CompileTimeFunction;
import com.alibaba.qlexpress4.api.BatchAddFunctionResult;
import com.alibaba.qlexpress4.api.QLFunctionalVarargs;
import com.alibaba.qlexpress4.exception.PureErrReporter;
import com.alibaba.qlexpress4.exception.QLException;
import com.alibaba.qlexpress4.exception.QLSyntaxException;
import com.alibaba.qlexpress4.runtime.DelegateQContext;
import com.alibaba.qlexpress4.runtime.QLambda;
import com.alibaba.qlexpress4.runtime.QLambdaDefinitionInner;
import com.alibaba.qlexpress4.runtime.QLambdaTrace;
import com.alibaba.qlexpress4.runtime.QvmGlobalScope;
import com.alibaba.qlexpress4.runtime.QvmRuntime;
import com.alibaba.qlexpress4.runtime.ReflectLoader;
import com.alibaba.qlexpress4.runtime.Value;
import com.alibaba.qlexpress4.runtime.context.ExpressContext;
import com.alibaba.qlexpress4.runtime.context.MapExpressContext;
import com.alibaba.qlexpress4.runtime.context.ObjectFieldExpressContext;
import com.alibaba.qlexpress4.runtime.context.QLAliasContext;
import com.alibaba.qlexpress4.runtime.function.CustomFunction;
import com.alibaba.qlexpress4.runtime.function.ExtensionFunction;
import com.alibaba.qlexpress4.runtime.function.QMethodFunction;
import com.alibaba.qlexpress4.runtime.instruction.QLInstruction;
import com.alibaba.qlexpress4.runtime.operator.CustomBinaryOperator;
import com.alibaba.qlexpress4.runtime.operator.OperatorManager;
import com.alibaba.qlexpress4.runtime.trace.ExpressionTrace;
import com.alibaba.qlexpress4.runtime.trace.QTraces;
import com.alibaba.qlexpress4.runtime.trace.TracePointTree;
import com.alibaba.qlexpress4.utils.BasicUtil;
import com.alibaba.qlexpress4.utils.QLFunctionUtil;
import org.antlr.v4.runtime.dfa.DFA;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
* Author: DQinYuan
*/
public class Express4Runner {
private final OperatorManager operatorManager = new OperatorManager();
private final Map<String, Future<QCompileCache>> compileCache = new ConcurrentHashMap<>();
private final Map<String, CustomFunction> userDefineFunction = new ConcurrentHashMap<>();
private final Map<String, CompileTimeFunction> compileTimeFunctions = new ConcurrentHashMap<>();
private final GeneratorScope globalScope = new GeneratorScope(null, "global", new ConcurrentHashMap<>());
private final ReflectLoader reflectLoader;
private final InitOptions initOptions;
public Express4Runner(InitOptions initOptions) {
this.initOptions = initOptions;
this.reflectLoader = new ReflectLoader(initOptions.getSecurityStrategy(), initOptions.isAllowPrivateAccess());
SyntaxTreeFactory.warmUp();
}
public CustomFunction getFunction(String functionName) {
return userDefineFunction.get(functionName);
}
public CompileTimeFunction getCompileTimeFunction(String functionName) {
return compileTimeFunctions.get(functionName);
}
/**
* Execute the script with variables set in the context; the map key corresponds to the
* variable name referenced in the script.
*
* @param script the script content to execute
* @param context variables for execution, keyed by variable name
* @param qlOptions execution options (e.g. interpolation, debug)
* @return result of script execution and related traces
* @throws QLException if a script or runtime error occurs
*/
public QLResult execute(String script, Map<String, Object> context, QLOptions qlOptions)
throws QLException {
return execute(script, new MapExpressContext(context), qlOptions);
}
/**
* Execute a template string by wrapping it as a dynamic string literal.
* Template does not support newlines in this mode.
*
* @param template the template text to evaluate as a dynamic string
* @param context variables available to the template
* @param qlOptions execution options
* @return result of template evaluation
* @throws QLException if compilation or execution fails
*/
public QLResult executeTemplate(String template, Map<String, Object> context, QLOptions qlOptions)
throws QLException {
String script = wrapAsDynamicString(template);
return execute(script, context, qlOptions);
}
private String wrapAsDynamicString(String template) {
if (template == null) {
return "\"\"";
}
String escaped = template.replace("\"", "\\\"");
return "\"" + escaped + "\"";
}
/**
* Execute the script with variables resolved from the fields of the given context object.
* The variable name in the script corresponds to the field name on the object.
*
* @param script the script content to execute
* @param context the object whose public fields/properties are exposed as variables
* @param qlOptions execution options
* @return result of script execution
* @throws QLException if a script or runtime error occurs
*/
public QLResult execute(String script, Object context, QLOptions qlOptions)
throws QLException {
return execute(script, new ObjectFieldExpressContext(context, this), qlOptions);
}
/**
* Execute the script using objects annotated with {@code @QLAlias}.
* The {@code QLAlias.value} serves as the variable name for each object.
* Objects without the annotation are ignored.
*
* @param script the script content to execute
* @param qlOptions execution options
* @param objects objects annotated with {@code @QLAlias}
* @return result of script execution
* @throws QLException if a script or runtime error occurs
*/
public QLResult executeWithAliasObjects(String script, QLOptions qlOptions, Object... objects) {
return execute(script, new QLAliasContext(objects), qlOptions);
}
public QLResult execute(String script, ExpressContext context, QLOptions qlOptions) {
QLambdaTrace mainLambdaTrace;
if (initOptions.isDebug()) {
long start = System.currentTimeMillis();
mainLambdaTrace = parseToLambda(script, context, qlOptions);
initOptions.getDebugInfoConsumer()
.accept("Compile consume time: " + (System.currentTimeMillis() - start) + " ms");
}
else {
mainLambdaTrace = parseToLambda(script, context, qlOptions);
}
QLambda mainLambda = mainLambdaTrace.getqLambda();
try {
Object result;
if (initOptions.isDebug()) {
long start = System.currentTimeMillis();
result = mainLambda.call().getResult().get();
initOptions.getDebugInfoConsumer()
.accept("Execute consume time: " + (System.currentTimeMillis() - start) + " ms");
}
else {
result = mainLambda.call().getResult().get();
}
return new QLResult(result, mainLambdaTrace.getTraces().getExpressionTraces());
}
catch (QLException e) {
throw e;
}
catch (Throwable nuKnown) {
// should not run here
throw new RuntimeException(nuKnown);
}
}
private QTraces convertPoints2QTraces(List<TracePointTree> expressionTracePoints) {
Map<Integer, ExpressionTrace> traceMap = new HashMap<>();
List<ExpressionTrace> expressionTraces = expressionTracePoints.stream()
.map(tracePoint -> convertPoint2Trace(tracePoint, traceMap))
.collect(Collectors.toList());
return new QTraces(expressionTraces, traceMap);
}
private ExpressionTrace convertPoint2Trace(TracePointTree tree, Map<Integer, ExpressionTrace> traceMap) {
if (tree.getChildren().isEmpty()) {
ExpressionTrace result = new ExpressionTrace(tree.getType(), tree.getToken(), Collections.emptyList(),
tree.getLine(), tree.getCol(), tree.getPosition());
traceMap.put(result.getPosition(), result);
return result;
}
List<ExpressionTrace> mergedChildren =
tree.getChildren().stream().map(child -> convertPoint2Trace(child, traceMap)).collect(Collectors.toList());
ExpressionTrace result = new ExpressionTrace(tree.getType(), tree.getToken(), mergedChildren, tree.getLine(),
tree.getCol(), tree.getPosition());
traceMap.put(result.getPosition(), result);
return result;
}
/**
* Get external variables (those that must be provided via context) referenced by the script.
*
* @param script the script content
* @return names of external variables referenced in the script
*/
public Set<String> getOutVarNames(String script) {
QLParser.ProgramContext programContext = parseToSyntaxTree(script);
OutVarNamesVisitor outVarNamesVisitor = new OutVarNamesVisitor(inheritDefaultImport());
programContext.accept(outVarNamesVisitor);
return outVarNamesVisitor.getOutVars();
}
/**
* Get external variable attribute access paths referenced by the script.
*
* @param script the script content
* @return attribute chains accessed on external variables
*/
public Set<List<String>> getOutVarAttrs(String script) {
QLParser.ProgramContext programContext = parseToSyntaxTree(script);
OutVarAttrsVisitor outVarAttrsVisitor = new OutVarAttrsVisitor(inheritDefaultImport());
programContext.accept(outVarAttrsVisitor);
return outVarAttrsVisitor.getOutVarAttrs();
}
/**
* Get external functions (those that must be provided via context) referenced by the script.
*
* @param script the script content
* @return names of external functions referenced in the script
*/
public Set<String> getOutFunctions(String script) {
QLParser.ProgramContext programContext = parseToSyntaxTree(script);
OutFunctionVisitor outFunctionVisitor = new OutFunctionVisitor();
programContext.accept(outFunctionVisitor);
return outFunctionVisitor.getOutFunctions();
}
/**
* Get the expression trace trees for the script without executing it.
*
* @param script the script content
* @return trace trees for each expression
*/
public List<TracePointTree> getExpressionTracePoints(String script) {
QLParser.ProgramContext programContext = parseToSyntaxTree(script);
TraceExpressionVisitor traceExpressionVisitor = new TraceExpressionVisitor();
programContext.accept(traceExpressionVisitor);
return traceExpressionVisitor.getExpressionTracePoints();
}
/**
* add user defined global macro to QLExpress engine
* @param name macro name
* @param macroScript script for macro
* @return true if add macro successfully. fail if macro name already exists.
*/
public boolean addMacro(String name, String macroScript) {
return globalScope.defineMacroIfAbsent(name, parseMacroDefine(name, macroScript));
}
/**
* add or replace user defined global macro to QLExpress engine
* @param name macro name
* @param macroScript script for macro
*/
public void addOrReplaceMacro(String name, String macroScript) {
globalScope.defineMacro(name, parseMacroDefine(name, macroScript));
}
private MacroDefine parseMacroDefine(String name, String macroScript) {
QLParser.ProgramContext macroProgram = parseToSyntaxTree(macroScript);
QvmInstructionVisitor macroVisitor = new QvmInstructionVisitor(macroScript, inheritDefaultImport(),
new GeneratorScope("MACRO_" + name, globalScope), operatorManager, QvmInstructionVisitor.Context.MACRO,
compileTimeFunctions, initOptions);
macroProgram.accept(macroVisitor);
List<QLInstruction> macroInstructions = macroVisitor.getInstructions();
List<QLParser.BlockStatementContext> blockStatementContexts = macroProgram.blockStatements().blockStatement();
boolean lastStmtExpress = !blockStatementContexts.isEmpty() && blockStatementContexts
.get(blockStatementContexts.size() - 1) instanceof QLParser.ExpressionStatementContext;
return new MacroDefine(macroInstructions, lastStmtExpress);
}
/**
* add user defined function to QLExpress engine
* @param name function name
* @param function function definition
* @return true if add function successfully. fail if function name already exists.
*/
public boolean addFunction(String name, CustomFunction function) {
CustomFunction preFunction = userDefineFunction.putIfAbsent(name, function);
return preFunction == null;
}
public <T, R> boolean addFunction(String name, Function<T, R> function) {
return addFunction(name, (qContext, parameters) -> {
T t = parameters.size() > 0 ? (T)parameters.get(0).get() : null;
return function.apply(t);
});
}
public boolean addVarArgsFunction(String name, QLFunctionalVarargs functionalVarargs) {
return addFunction(name, (qContext, parameters) -> {
Object[] paramArr = new Object[parameters.size()];
for (int i = 0; i < paramArr.length; i++) {
paramArr[i] = parameters.get(i).get();
}
return functionalVarargs.call(paramArr);
});
}
public <T> boolean addFunction(String name, Predicate<T> predicate) {
return addFunction(name, (qContext, parameters) -> {
T t = parameters.size() > 0 ? (T)parameters.get(0).get() : null;
return predicate.test(t);
});
}
public boolean addFunction(String name, Runnable runnable) {
return addFunction(name, (qContext, parameters) -> {
runnable.run();
return null;
});
}
public <T> boolean addFunction(String name, Consumer<T> consumer) {
return addFunction(name, (qContext, parameters) -> {
T t = parameters.size() > 0 ? (T)parameters.get(0).get() : null;
consumer.accept(t);
return null;
});
}
/**
* Add a user-defined function backed by a specific Java service instance method.
*
* @param name function name exposed in QLExpress scripts
* @param serviceObject target service instance, must not be {@code null}
* @param methodName Java method name on the service instance
* @param parameterClassTypes parameter type signature of the Java method; use an empty array for no-arg methods
* @return true if the function was added successfully; false if a function with the same name already exists
* @throws IllegalArgumentException if {@code serviceObject} or {@code methodName} is null, or if no matching
* public method is found on the service type
*/
public boolean addFunctionOfServiceMethod(String name, Object serviceObject, String methodName,
Class<?>[] parameterClassTypes) {
if (serviceObject == null) {
throw new IllegalArgumentException("serviceObject must not be null");
}
if (methodName == null) {
throw new IllegalArgumentException("methodName must not be null");
}
Class<?>[] parameterTypes = parameterClassTypes == null ? new Class<?>[0] : parameterClassTypes;
Method method;
try {
method = serviceObject.getClass().getMethod(methodName, parameterTypes);
}
catch (NoSuchMethodException e) {
throw new IllegalArgumentException("No such public method '" + methodName + "' with parameter types "
+ java.util.Arrays.toString(parameterTypes) + " on service object class '"
+ serviceObject.getClass().getName() + "'", e);
}
return addFunction(name, new QMethodFunction(serviceObject, method));
}
/**
* execute `scriptWithFunctionDefine` and add functions defined in script
* @param scriptWithFunctionDefine script with function define
* @param context context when execute script
* @param qlOptions qlOptions when execute script
* @return succ and fail functions. fail if function name already exists
*/
public BatchAddFunctionResult addFunctionsDefinedInScript(String scriptWithFunctionDefine, ExpressContext context,
QLOptions qlOptions) {
BatchAddFunctionResult batchResult = new BatchAddFunctionResult();
QLambdaTrace mainLambdaTrace = parseToLambda(scriptWithFunctionDefine, context, qlOptions);
try {
Map<String, CustomFunction> functionTableInScript = mainLambdaTrace.getqLambda().getFunctionDefined();
for (Map.Entry<String, CustomFunction> entry : functionTableInScript.entrySet()) {
boolean addResult = addFunction(entry.getKey(), entry.getValue());
(addResult ? batchResult.getSucc() : batchResult.getFail()).add(entry.getKey());
}
return batchResult;
}
catch (QLException e) {
throw e;
}
catch (Throwable e) {
// should not run here
throw new RuntimeException(e);
}
}
/**
* add object member method with annotation {@link com.alibaba.qlexpress4.annotation.QLFunction} as function
* @param object object with member method with annotation {@link com.alibaba.qlexpress4.annotation.QLFunction}
* @return succ and fail functions. fail if function name already exists or method is not public
*/
public BatchAddFunctionResult addObjFunction(Object object) {
return addFunctionByAnnotation(object.getClass(), object);
}
/**
* add class static method with annotation {@link com.alibaba.qlexpress4.annotation.QLFunction} as function
* @param clazz class with static method with annotation {@link com.alibaba.qlexpress4.annotation.QLFunction}
* @return succ and fail functions. fail if function name already exists or method is not public
*/
public BatchAddFunctionResult addStaticFunction(Class<?> clazz) {
return addFunctionByAnnotation(clazz, null);
}
private BatchAddFunctionResult addFunctionByAnnotation(Class<?> clazz, Object object) {
BatchAddFunctionResult result = new BatchAddFunctionResult();
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
if (!BasicUtil.isPublic(method)) {
result.getFail().add(method.getName());
continue;
}
if (QLFunctionUtil.containsQLFunctionForMethod(method)) {
for (String functionName : QLFunctionUtil.getQLFunctionValue(method)) {
boolean addResult = addFunction(functionName, new QMethodFunction(object, method));
(addResult ? result.getSucc() : result.getFail()).add(method.getName());
}
}
}
return result;
}
/**
* add compile time function
* @param name function name
* @param compileTimeFunction definition
* @return true if successful
*/
public boolean addCompileTimeFunction(String name, CompileTimeFunction compileTimeFunction) {
return compileTimeFunctions.putIfAbsent(name, compileTimeFunction) == null;
}
/**
* add extension function
* @param extensionFunction definition of extansion function
*/
public void addExtendFunction(ExtensionFunction extensionFunction) {
this.reflectLoader.addExtendFunction(extensionFunction);
}
/**
* add an extension function with variable arguments.
* @param name the name of the extension function
* @param bindingClass the receiver type (class)
* @param functionalVarargs custom logic
*/
public void addExtendFunction(String name, Class<?> bindingClass, QLFunctionalVarargs functionalVarargs) {
this.reflectLoader.addExtendFunction(new ExtensionFunction() {
@Override
public Class<?>[] getParameterTypes() {
return new Class[] {Object[].class};
}
@Override
public boolean isVarArgs() {
return true;
}
@Override
public String getName() {
return name;
}
@Override
public Class<?> getDeclaringClass() {
return bindingClass;
}
@Override
public Object invoke(Object obj, Object[] args)
throws InvocationTargetException, IllegalAccessException {
Object[] varArgs = (Object[])args[0];
Object[] extArgs = new Object[varArgs.length + 1];
extArgs[0] = obj;
System.arraycopy(varArgs, 0, extArgs, 1, varArgs.length);
return functionalVarargs.call(extArgs);
}
});
}
public QLParser.ProgramContext parseToSyntaxTree(String script) {
return SyntaxTreeFactory.buildTree(script,
operatorManager,
initOptions.isDebug(),
false,
initOptions.getDebugInfoConsumer(),
initOptions.getInterpolationMode(),
initOptions.getSelectorStart(),
initOptions.getSelectorEnd(),
initOptions.isStrictNewLines());
}
public void check(String script, CheckOptions checkOptions)
throws QLSyntaxException {
// 1. Parse syntax tree (reuse existing parseToSyntaxTree logic)
QLParser.ProgramContext programContext = parseToSyntaxTree(script);
// 2. Create CheckVisitor and pass validation configuration and script content
CheckVisitor checkVisitor = new CheckVisitor(checkOptions, script);
// 3. Traverse syntax tree and perform operator validation during traversal
programContext.accept(checkVisitor);
}
public void check(String script)
throws QLSyntaxException {
check(script, CheckOptions.DEFAULT_OPTIONS);
}
public QLambdaTrace parseToLambda(String script, ExpressContext context, QLOptions qlOptions) {
QCompileCache mainLambdaDefine =
qlOptions.isCache() ? parseToDefinitionWithCache(script) : parseDefinition(script);
if (initOptions.isDebug()) {
initOptions.getDebugInfoConsumer().accept("\nInstructions:");
mainLambdaDefine.getQLambdaDefinition().println(0, initOptions.getDebugInfoConsumer());
}
QTraces qTraces = initOptions.isTraceExpression() && qlOptions.isTraceExpression()
? convertPoints2QTraces(mainLambdaDefine.getExpressionTracePoints())
: new QTraces(null, null);
QvmRuntime qvmRuntime =
new QvmRuntime(qTraces, qlOptions.getAttachments(), reflectLoader, System.currentTimeMillis());
QvmGlobalScope globalScope = new QvmGlobalScope(context, userDefineFunction, qlOptions);
QLambda qLambda = mainLambdaDefine.getQLambdaDefinition()
.toLambda(new DelegateQContext(qvmRuntime, globalScope), qlOptions, true);
return new QLambdaTrace(qLambda, qTraces);
}
/**
* parse script with cache
* @param script script to parse
* @return QLambdaDefinition and TracePointTrees
*/
public QCompileCache parseToDefinitionWithCache(String script) {
try {
return getParseFuture(script).get();
}
catch (Exception e) {
Throwable compileException = e.getCause();
throw compileException instanceof QLSyntaxException ? (QLSyntaxException)compileException
: new RuntimeException(compileException);
}
}
public Value loadField(Object object, String fieldName) {
return reflectLoader.loadField(object, fieldName, true, PureErrReporter.INSTANCE);
}
private static final String PARSER_CLASS_NAME = "com.alibaba.qlexpress4.aparser.QLParser";
private static final String DFA_FIELD_NAME = "_decisionToDFA";
/**
* Clears the DFA (Deterministic Finite Automaton) cache used by the ANTLR parser.
* <p>
* <b>WARNING:</b> Calling this method will cause a significant compilation performance degradation.
* It is NOT recommended for normal use cases.
* </p>
*
* <h3>Use Cases:</h3>
* <ul>
* <li><b>Memory-sensitive applications:</b> When memory usage is a critical concern and you can
* tolerate slower compilation times</li>
* <li><b>Infrequently changing scripts:</b> When scripts are relatively stable and not frequently
* recompiled</li>
* </ul>
*
* <h3>Best Practice:</h3>
* <p>
* Call this method immediately after parsing and caching your expression, and ensure all subsequent
* executions use the cached version to avoid recompilation. Example:
* </p>
* <pre>{@code
* /*
* * When the expression changes, parse it and add it to the expression cache;
* * after parsing is complete, call clearDFACache.
* *\/
* Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
* runner.parseToDefinitionWithCache(complexDataProcessingExpress);
* runner.clearDFACache();
*
* /*
* * All subsequent runs of this script must enable the cache option to ensure that re-compilation does not occur.
* *\/
* for (int i = 0; i < 3; i++) {
* runner.execute(complexDataProcessingExpress, Collections.emptyMap(), QLOptions
* .builder().cache(true).build());
* }
* }</pre>
*/
public void clearDFACache() {
DFA[] decisionToDFA = getDecisionToDFA();
for (int d = 0; d < decisionToDFA.length; d++) {
decisionToDFA[d] = new DFA(QLParser._ATN.getDecisionState(d), d);
}
}
private DFA[] getDecisionToDFA() {
try {
Class<?> parserClass = Class.forName(PARSER_CLASS_NAME);
Field dfaField = parserClass.getDeclaredField(DFA_FIELD_NAME);
dfaField.setAccessible(true);
return (DFA[])dfaField.get(null);
}
catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
/**
* Clear the compilation cache.
* This method clears the cache that stores compiled scripts for performance optimization.
* When the cache is cleared, subsequent script executions will need to recompile the scripts,
* which may temporarily impact performance until the cache is rebuilt.
*/
public void clearCompileCache() {
compileCache.clear();
}
private Future<QCompileCache> getParseFuture(String script) {
Future<QCompileCache> parseFuture = compileCache.get(script);
if (parseFuture != null) {
return parseFuture;
}
FutureTask<QCompileCache> parseTask = new FutureTask<>(() -> parseDefinition(script));
Future<QCompileCache> preTask = compileCache.putIfAbsent(script, parseTask);
if (preTask == null) {
parseTask.run();
return parseTask;
}
return preTask;
}
private QCompileCache parseDefinition(String script) {
QLParser.ProgramContext program = parseToSyntaxTree(script);
QvmInstructionVisitor qvmInstructionVisitor = new QvmInstructionVisitor(script, inheritDefaultImport(),
globalScope, operatorManager, compileTimeFunctions, initOptions);
program.accept(qvmInstructionVisitor);
QLambdaDefinitionInner qLambdaDefinition = new QLambdaDefinitionInner("main",
qvmInstructionVisitor.getInstructions(), Collections.emptyList(), qvmInstructionVisitor.getMaxStackSize());
if (initOptions.isTraceExpression()) {
TraceExpressionVisitor traceExpressionVisitor = new TraceExpressionVisitor();
program.accept(traceExpressionVisitor);
List<TracePointTree> tracePoints = traceExpressionVisitor.getExpressionTracePoints();
return new QCompileCache(qLambdaDefinition, tracePoints);
}
else {
return new QCompileCache(qLambdaDefinition, Collections.emptyList());
}
}
private ImportManager inheritDefaultImport() {
return new ImportManager(initOptions.getClassSupplier(), initOptions.getDefaultImport());
}
public <T, U, R> boolean addOperatorBiFunction(String operator, BiFunction<T, U, R> biFunction) {
return operatorManager.addBinaryOperator(operator,
(left, right) -> biFunction.apply((T)left.get(), (U)right.get()),
QLPrecedences.MULTI);
}
public boolean addOperator(String operator, QLFunctionalVarargs functionalVarargs) {
return addOperator(operator, (left, right) -> functionalVarargs.call(left.get(), right.get()));
}
/**
* add operator with multi precedences
* @param operator operator name
* @param customBinaryOperator operator implement
* @return true if add operator successfully; false if operator already exist
*/
public boolean addOperator(String operator, CustomBinaryOperator customBinaryOperator) {
return operatorManager.addBinaryOperator(operator, customBinaryOperator, QLPrecedences.MULTI);
}
/**
* add operator
* @param operator operator name
* @param customBinaryOperator operator implement
* @param precedence precedence, see {@link QLPrecedences}
* @return true if add operator successfully; false if operator already exist
*/
public boolean addOperator(String operator, CustomBinaryOperator customBinaryOperator, int precedence) {
return operatorManager.addBinaryOperator(operator, customBinaryOperator, precedence);
}
/**
* @param operator operator name
* @param customBinaryOperator operator implement
* @return true if replace operator successfully; false if default operator not exists
*/
public boolean replaceDefaultOperator(String operator, CustomBinaryOperator customBinaryOperator) {
return operatorManager.replaceDefaultOperator(operator, customBinaryOperator);
}
/**
* add alias for keyWord, operator and function
* @param alias must be a valid id
* @param originToken key word in qlexpress
* @return true if add alias successfully
*/
public boolean addAlias(String alias, String originToken) {
boolean addKeyWordAliasResult = operatorManager.addKeyWordAlias(alias, originToken);
boolean addOperatorAliasResult = operatorManager.addOperatorAlias(alias, originToken);
boolean addFunctionAliasResult = addFunctionAlias(alias, originToken);
return addKeyWordAliasResult || addOperatorAliasResult || addFunctionAliasResult;
}
private boolean addFunctionAlias(String alias, String originToken) {
CustomFunction customFunction = userDefineFunction.get(originToken);
if (customFunction != null) {
return userDefineFunction.putIfAbsent(alias, customFunction) == null;
}
return false;
}
}

View File

@@ -0,0 +1,245 @@
package com.alibaba.qlexpress4;
import com.alibaba.qlexpress4.aparser.ImportManager;
import com.alibaba.qlexpress4.aparser.InterpolationMode;
import com.alibaba.qlexpress4.security.QLSecurityStrategy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
/**
* Author: TaoKan
*/
public class InitOptions {
public static InitOptions DEFAULT_OPTIONS = InitOptions.builder().build();
private final ClassSupplier classSupplier;
/**
* default import java packages for script
* default
* ImportManager.importPack("java.lang"),
* ImportManager.importPack("java.util"),
* ImportManager.importPack("java.math"),
* ImportManager.importPack("java.util.stream")
* ImportManager.importPack("java.util.function")
*/
private final List<ImportManager.QLImport> defaultImport;
/**
* enable debug mode
* default false
*/
private final boolean debug;
/**
* consume all debug info, valid when debug is true
* default is print in standard output, can not be null
*/
private final Consumer<String> debugInfoConsumer;
/**
* qlexpress security strategy
* default is isolation, not allow any access to java
*/
private final QLSecurityStrategy securityStrategy;
/**
* allow access private field and method
* default false
*/
private final boolean allowPrivateAccess;
/**
* How to manage string interpolation, for instance, "a ${t-c} b"
* default SCRIPT
*/
private final InterpolationMode interpolationMode;
/**
* track the execution process of all expressions and return the path to the `execute` caller.
* default false
*/
private final boolean traceExpression;
/**
* selector start token, must be one of: "${", "$[", "#{", or "#[".
* default is "${".
*/
private final String selectorStart;
/**
* selector end token, must be 1 or more characters
* default is "}"
*/
private final String selectorEnd;
/**
* Strictly require a line break between any two statements (since semicolons can be omitted in QLExpress4).
* default is true
*/
private final boolean strictNewLines;
private InitOptions(ClassSupplier classSupplier, List<ImportManager.QLImport> defaultImport, boolean debug,
Consumer<String> debugInfoConsumer, QLSecurityStrategy securityStrategy, boolean allowPrivateAccess,
InterpolationMode interpolationMode, boolean traceExpression, String selectorStart, String selectorEnd,
boolean strictNewLines) {
this.classSupplier = classSupplier;
this.defaultImport = defaultImport;
this.debug = debug;
this.debugInfoConsumer = debugInfoConsumer;
this.securityStrategy = securityStrategy;
this.allowPrivateAccess = allowPrivateAccess;
this.interpolationMode = interpolationMode;
this.traceExpression = traceExpression;
this.selectorStart = selectorStart;
this.selectorEnd = selectorEnd;
this.strictNewLines = strictNewLines;
}
public static InitOptions.Builder builder() {
return new Builder();
}
public List<ImportManager.QLImport> getDefaultImport() {
return defaultImport;
}
public ClassSupplier getClassSupplier() {
return classSupplier;
}
public boolean isDebug() {
return debug;
}
public Consumer<String> getDebugInfoConsumer() {
return debugInfoConsumer;
}
public QLSecurityStrategy getSecurityStrategy() {
return securityStrategy;
}
public boolean isAllowPrivateAccess() {
return allowPrivateAccess;
}
public InterpolationMode getInterpolationMode() {
return interpolationMode;
}
public boolean isTraceExpression() {
return traceExpression;
}
public String getSelectorStart() {
return selectorStart;
}
public String getSelectorEnd() {
return selectorEnd;
}
public boolean isStrictNewLines() {
return strictNewLines;
}
public static class Builder {
private ClassSupplier classSupplier = DefaultClassSupplier.getInstance();
private final List<ImportManager.QLImport> defaultImport =
new ArrayList<>(Arrays.asList(ImportManager.importPack("java.lang"),
ImportManager.importPack("java.util"),
ImportManager.importPack("java.math"),
ImportManager.importPack("java.util.stream"),
ImportManager.importPack("java.util.function")));
private boolean debug = false;
private Consumer<String> debugInfoConsumer = System.out::println;
private QLSecurityStrategy securityStrategy = QLSecurityStrategy.isolation();
private boolean allowPrivateAccess = false;
private InterpolationMode interpolationMode = InterpolationMode.SCRIPT;
private boolean traceExpression = false;
private String selectorStart = "${";
private String selectorEnd = "}";
private boolean strictNewLines = true;
public Builder classSupplier(ClassSupplier classSupplier) {
this.classSupplier = classSupplier;
return this;
}
public Builder addDefaultImport(List<ImportManager.QLImport> defaultImport) {
this.defaultImport.addAll(defaultImport);
return this;
}
public Builder debug(boolean debug) {
this.debug = debug;
return this;
}
public Builder debugInfoConsumer(Consumer<String> debugInfoConsumer) {
this.debugInfoConsumer = debugInfoConsumer;
return this;
}
public Builder securityStrategy(QLSecurityStrategy securityStrategy) {
this.securityStrategy = securityStrategy;
return this;
}
public Builder allowPrivateAccess(boolean allowPrivateAccess) {
this.allowPrivateAccess = allowPrivateAccess;
return this;
}
public Builder interpolationMode(InterpolationMode interpolationMode) {
this.interpolationMode = interpolationMode;
return this;
}
public Builder traceExpression(boolean traceExpression) {
this.traceExpression = traceExpression;
return this;
}
public Builder selectorStart(String selectorStart) {
if (!Arrays.asList("${", "$[", "#{", "#[").contains(selectorStart)) {
throw new IllegalArgumentException("Custom selector start must in '${' | '$[' | '#{' | '#['");
}
this.selectorStart = selectorStart;
return this;
}
public Builder selectorEnd(String selectorEnd) {
if (selectorEnd == null || selectorEnd.isEmpty()) {
throw new IllegalArgumentException("Custom selector end must be 1 or more characters");
}
this.selectorEnd = selectorEnd;
return this;
}
public Builder strictNewLines(boolean strictNewLines) {
this.strictNewLines = strictNewLines;
return this;
}
public InitOptions build() {
return new InitOptions(classSupplier, defaultImport, debug, debugInfoConsumer, securityStrategy,
allowPrivateAccess, interpolationMode, traceExpression, selectorStart, selectorEnd, strictNewLines);
}
}
}

View File

@@ -0,0 +1,205 @@
package com.alibaba.qlexpress4;
import java.util.Collections;
import java.util.Map;
/**
* Author: DQinYuan
* date 2022/1/12 2:35 下午
*/
public class QLOptions {
public static QLOptions DEFAULT_OPTIONS = QLOptions.builder().build();
/**
* precise evaluate based on BigDecimal
* default false
*/
private final boolean precise;
/**
* define global symbol in user context
* default false
*/
private final boolean polluteUserContext;
/**
* script timeout millisecond, default is -1, namely time unlimited
* <= 0, time unlimited
* default -1
*/
private final long timeoutMillis;
/**
* attachments will be carried to user defined function/operator/macro
* only used to pass data, not as variable value
*
* default empty map
*/
private final Map<String, Object> attachments;
/**
* allow cache compile result of script
*
* default false
*/
private final boolean cache;
/**
* avoid null pointer
* default false
*/
private final boolean avoidNullPointer;
/**
* max length of arrays allowed to be created
* -1 means no limit
* default -1
*/
private final int maxArrLength;
/**
* Track the execution process of all expressions and return the path to the `execute` caller.
* To enable expression tracing, please ensure that the InitOptions.traceExpression is alse set to true.
* default false
*/
private final boolean traceExpression;
/**
* disable short circuit in logic operator
* default false
*/
private final boolean shortCircuitDisable;
private QLOptions(boolean precise, boolean polluteUserContext, long timeoutMillis, Map<String, Object> attachments,
boolean cache, boolean avoidNullPointer, int maxArrLength, boolean traceExpression,
boolean shortCircuitDisable) {
this.precise = precise;
this.polluteUserContext = polluteUserContext;
this.timeoutMillis = timeoutMillis;
this.attachments = attachments;
this.cache = cache;
this.avoidNullPointer = avoidNullPointer;
this.maxArrLength = maxArrLength;
this.traceExpression = traceExpression;
this.shortCircuitDisable = shortCircuitDisable;
}
public static Builder builder() {
return new Builder();
}
public boolean isPrecise() {
return precise;
}
public boolean isPolluteUserContext() {
return polluteUserContext;
}
public long getTimeoutMillis() {
return timeoutMillis;
}
public Map<String, Object> getAttachments() {
return attachments;
}
public boolean isCache() {
return cache;
}
public boolean isAvoidNullPointer() {
return avoidNullPointer;
}
public int getMaxArrLength() {
return maxArrLength;
}
/**
* @param newArrLen new arr length in runtime
* @return true if less or equal to max arr len
*/
public boolean checkArrLen(int newArrLen) {
return maxArrLength == -1 || newArrLen <= maxArrLength;
}
public boolean isTraceExpression() {
return traceExpression;
}
public boolean isShortCircuitDisable() {
return shortCircuitDisable;
}
public static class Builder {
private boolean precise = false;
private boolean polluteUserContext = false;
private long timeoutMillis = -1;
private Map<String, Object> attachments = Collections.emptyMap();
private boolean cache = false;
private boolean avoidNullPointer = false;
private int maxArrLength = -1;
private boolean traceExpression = false;
private boolean shortCircuitDisable = false;
public Builder precise(boolean precise) {
this.precise = precise;
return this;
}
public Builder polluteUserContext(boolean polluteUserContext) {
this.polluteUserContext = polluteUserContext;
return this;
}
public Builder timeoutMillis(long timeoutMillis) {
this.timeoutMillis = timeoutMillis;
return this;
}
public Builder attachments(Map<String, Object> attachments) {
this.attachments = attachments;
return this;
}
public Builder cache(boolean cache) {
this.cache = cache;
return this;
}
public Builder avoidNullPointer(boolean avoidNullPointer) {
this.avoidNullPointer = avoidNullPointer;
return this;
}
public Builder maxArrLength(int maxArrLength) {
this.maxArrLength = maxArrLength;
return this;
}
public Builder traceExpression(boolean traceExpression) {
this.traceExpression = traceExpression;
return this;
}
public Builder shortCircuitDisable(boolean shortCircuitDisable) {
this.shortCircuitDisable = shortCircuitDisable;
return this;
}
public QLOptions build() {
return new QLOptions(precise, polluteUserContext, timeoutMillis, attachments, cache, avoidNullPointer,
maxArrLength, traceExpression, shortCircuitDisable);
}
}
}

View File

@@ -0,0 +1,89 @@
package com.alibaba.qlexpress4;
/**
* Author: DQinYuan
* date 2022/1/12 2:31 下午
*/
public class QLPrecedences {
/*
* = += -= &= |= *= /= %= <<= >>=
*/
public static final int ASSIGN = 0;
/*
* ?:
*/
public static final int TERNARY = 1;
/*
* || or
*/
public static final int OR = 2;
/*
* && and
*/
public static final int AND = 3;
/*
* |
*/
public static final int BIT_OR = 4;
/*
* ^
*/
public static final int XOR = 5;
/*
* &
*/
public static final int BIT_AND = 6;
/*
* == !=
*/
public static final int EQUAL = 7;
/*
* < <= > >= instanceof
*/
public static final int COMPARE = 8;
/*
* << >> >>>
*/
public static final int BIT_MOVE = 9;
/*
* in like
*/
public static final int IN_LIKE = 10;
/*
* + -
*/
public static final int ADD = 11;
/*
* * / %
*/
public static final int MULTI = 12;
/*
* ! ++ -- ~ + -
*/
public static final int UNARY = 13;
/*
* ++ -- in suffix, like i++
*/
public static final int UNARY_SUFFIX = 14;
/*
* . ()
*/
public static final int GROUP = 15;
}

View File

@@ -0,0 +1,25 @@
package com.alibaba.qlexpress4;
import com.alibaba.qlexpress4.runtime.trace.ExpressionTrace;
import java.util.List;
public class QLResult {
private final Object result;
private final List<ExpressionTrace> expressionTraces;
public QLResult(Object result, List<ExpressionTrace> expressionTraces) {
this.result = result;
this.expressionTraces = expressionTraces;
}
public Object getResult() {
return result;
}
public List<ExpressionTrace> getExpressionTraces() {
return expressionTraces;
}
}

View File

@@ -0,0 +1,15 @@
package com.alibaba.qlexpress4.annotation;
import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;
@Inherited
@Target({TYPE, FIELD, METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface QLAlias {
/**
* @return aliases
*/
String[] value();
}

View File

@@ -0,0 +1,20 @@
package com.alibaba.qlexpress4.annotation;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.METHOD;
/**
* Author: TaoKan
*/
@Inherited
@Target({METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface QLFunction {
/**
* @return function names
*/
String[] value();
}

View File

@@ -0,0 +1,53 @@
package com.alibaba.qlexpress4.aparser;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.TokenFactory;
import org.antlr.v4.runtime.TokenSource;
public class AliasTokenSource implements TokenSource {
private final TokenSource tokenSource;
private final ParserOperatorManager operatorManager;
AliasTokenSource(TokenSource tokenSource, ParserOperatorManager operatorManager) {
this.tokenSource = tokenSource;
this.operatorManager = operatorManager;
}
@Override
public Token nextToken() {
return SyntaxTreeFactory.preHandleToken(tokenSource.nextToken(), operatorManager);
}
@Override
public int getLine() {
return tokenSource.getLine();
}
@Override
public int getCharPositionInLine() {
return tokenSource.getCharPositionInLine();
}
@Override
public CharStream getInputStream() {
return tokenSource.getInputStream();
}
@Override
public String getSourceName() {
return tokenSource.getSourceName();
}
@Override
public void setTokenFactory(TokenFactory<?> factory) {
tokenSource.setTokenFactory(factory);
}
@Override
public TokenFactory<?> getTokenFactory() {
return tokenSource.getTokenFactory();
}
}

View File

@@ -0,0 +1,95 @@
package com.alibaba.qlexpress4.aparser;
import org.antlr.v4.runtime.BufferedTokenStream;
import org.antlr.v4.runtime.RuleContext;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.TokenSource;
import org.antlr.v4.runtime.TokenStream;
import org.antlr.v4.runtime.misc.Interval;
public class AliasTokenStream implements TokenStream {
private final BufferedTokenStream stream;
private final ParserOperatorManager operatorManager;
public AliasTokenStream(BufferedTokenStream stream, ParserOperatorManager operatorManager) {
this.stream = stream;
this.operatorManager = operatorManager;
}
@Override
public Token LT(int k) {
return SyntaxTreeFactory.preHandleToken(stream.LT(k), operatorManager);
}
@Override
public Token get(int index) {
return SyntaxTreeFactory.preHandleToken(stream.get(index), operatorManager);
}
@Override
public TokenSource getTokenSource() {
return new AliasTokenSource(stream.getTokenSource(), operatorManager);
}
@Override
public String getText(Interval interval) {
return stream.getText(interval);
}
@Override
public String getText() {
return stream.getText();
}
@Override
public String getText(RuleContext ctx) {
return stream.getText(ctx);
}
@Override
public String getText(Token start, Token stop) {
return stream.getText(start, stop);
}
@Override
public void consume() {
stream.consume();
}
@Override
public int LA(int i) {
return SyntaxTreeFactory.preHandleToken(LT(i), operatorManager).getType();
}
@Override
public int mark() {
return stream.mark();
}
@Override
public void release(int marker) {
stream.release(marker);
}
@Override
public int index() {
return stream.index();
}
@Override
public void seek(int index) {
stream.seek(index);
}
@Override
public int size() {
return stream.size();
}
@Override
public String getSourceName() {
return stream.getSourceName();
}
}

View File

@@ -0,0 +1,43 @@
package com.alibaba.qlexpress4.aparser;
public class BuiltInTypesSet {
public static final String BYTE = "byte";
public static final String SHORT = "short";
public static final String INT = "int";
public static final String LONG = "long";
public static final String FLOAT = "float";
public static final String DOUBLE = "double";
public static final String BOOLEAN = "boolean";
public static final String CHAR = "char";
public static Class<?> getCls(String lexeme) {
switch (lexeme) {
case BYTE:
return Byte.class;
case SHORT:
return Short.class;
case INT:
return Integer.class;
case LONG:
return Long.class;
case FLOAT:
return Float.class;
case DOUBLE:
return Double.class;
case BOOLEAN:
return Boolean.class;
case CHAR:
return Character.class;
default:
return null;
}
}
}

View File

@@ -0,0 +1,142 @@
package com.alibaba.qlexpress4.aparser;
import com.alibaba.qlexpress4.CheckOptions;
import com.alibaba.qlexpress4.exception.QLErrorCodes;
import com.alibaba.qlexpress4.exception.QLException;
import com.alibaba.qlexpress4.exception.QLSyntaxException;
import com.alibaba.qlexpress4.operator.OperatorCheckStrategy;
import org.antlr.v4.runtime.Token;
/**
* @author zhoutao
*/
public class CheckVisitor extends QLParserBaseVisitor<Void> {
/**
* Operator restriction strategy
*/
private final OperatorCheckStrategy operatorCheckStrategy;
/**
* Whether to disable function calls
*/
private final boolean disableFunctionCalls;
/**
* Script content for error reporting
*/
private final String script;
public CheckVisitor(CheckOptions checkOptions) {
this(checkOptions, "");
}
public CheckVisitor(CheckOptions checkOptions, String script) {
this.operatorCheckStrategy = checkOptions.getCheckStrategy();
this.disableFunctionCalls = checkOptions.isDisableFunctionCalls();
this.script = script;
}
private void checkOperator(String operatorString, Token token)
throws QLSyntaxException {
if (null != operatorCheckStrategy && !operatorCheckStrategy.isAllowed(operatorString)) {
String reason = String.format(QLErrorCodes.OPERATOR_NOT_ALLOWED.getErrorMsg(),
operatorString,
operatorCheckStrategy.getOperators());
throw QLException.reportScannerErr(script,
token.getStartIndex(),
token.getLine(),
token.getCharPositionInLine(),
operatorString,
QLErrorCodes.OPERATOR_NOT_ALLOWED.name(),
reason);
}
}
private void checkFunctionCall(Token token)
throws QLSyntaxException {
if (disableFunctionCalls) {
String reason = "Function calls are not allowed in this context";
throw QLException.reportScannerErr(script,
token.getStartIndex(),
token.getLine(),
token.getCharPositionInLine(),
token.getText(),
"FUNCTION_CALL_NOT_ALLOWED",
reason);
}
}
@Override
public Void visitLeftAsso(QLParser.LeftAssoContext ctx) {
// Get operator
QLParser.BinaryopContext binaryopContext = ctx.binaryop();
if (binaryopContext != null) {
String operator = binaryopContext.getText();
checkOperator(operator, binaryopContext.getStart()); // Validate here, may throw exception
}
// Continue traversing child nodes
return super.visitLeftAsso(ctx);
}
@Override
public Void visitPrefixExpress(QLParser.PrefixExpressContext ctx) {
// Get prefix operator
if (ctx.opId() != null) {
String operator = ctx.opId().getText();
checkOperator(operator, ctx.opId().getStart()); // Validate here, may throw exception
}
return super.visitPrefixExpress(ctx);
}
@Override
public Void visitSuffixExpress(QLParser.SuffixExpressContext ctx) {
// Get suffix operator
if (ctx.opId() != null) {
String operator = ctx.opId().getText();
checkOperator(operator, ctx.opId().getStart());
}
return super.visitSuffixExpress(ctx);
}
@Override
public Void visitExpression(QLParser.ExpressionContext ctx) {
// Check assignment operator
if (ctx.assignOperator() != null) {
checkOperator(ctx.assignOperator().getText(), ctx.assignOperator().getStart());
}
return super.visitExpression(ctx);
}
@Override
public Void visitCallExpr(QLParser.CallExprContext ctx) {
// Check if function calls are disabled
checkFunctionCall(ctx.getStart());
return super.visitCallExpr(ctx);
}
@Override
public Void visitMethodInvoke(QLParser.MethodInvokeContext ctx) {
// Check if function calls are disabled
checkFunctionCall(ctx.getStart());
return super.visitMethodInvoke(ctx);
}
@Override
public Void visitOptionalMethodInvoke(QLParser.OptionalMethodInvokeContext ctx) {
// Check if function calls are disabled
checkFunctionCall(ctx.getStart());
return super.visitOptionalMethodInvoke(ctx);
}
@Override
public Void visitSpreadMethodInvoke(QLParser.SpreadMethodInvokeContext ctx) {
// Check if function calls are disabled
checkFunctionCall(ctx.getStart());
return super.visitSpreadMethodInvoke(ctx);
}
}

View File

@@ -0,0 +1,12 @@
package com.alibaba.qlexpress4.aparser;
public interface ExistStack {
ExistStack push();
ExistStack pop();
boolean exist(String varName);
void add(String varName);
}

View File

@@ -0,0 +1,50 @@
package com.alibaba.qlexpress4.aparser;
import java.util.HashMap;
import java.util.Map;
/**
* Author: DQinYuan
*/
public class GeneratorScope {
private final GeneratorScope parent;
private final String name;
private final Map<String, MacroDefine> macroDefineMap;
public GeneratorScope(GeneratorScope parent, String name, Map<String, MacroDefine> macroDefineMap) {
this.parent = parent;
this.name = name;
this.macroDefineMap = macroDefineMap;
}
public GeneratorScope(String name, GeneratorScope parent) {
this.parent = parent;
this.name = name;
this.macroDefineMap = new HashMap<>();
}
/**
* @param name macro name
* @param macroDefine macro definition
* @return true if define macro successfully. fail if macro name already exists
*/
public boolean defineMacroIfAbsent(String name, MacroDefine macroDefine) {
return macroDefineMap.putIfAbsent(name, macroDefine) == null;
}
public void defineMacro(String name, MacroDefine macroDefine) {
macroDefineMap.put(name, macroDefine);
}
public MacroDefine getMacroInstructions(String macroName) {
MacroDefine qlInstructions = macroDefineMap.get(macroName);
return qlInstructions != null ? qlInstructions : parent != null ? parent.getMacroInstructions(macroName) : null;
}
public String getName() {
return name;
}
}

View File

@@ -0,0 +1,231 @@
package com.alibaba.qlexpress4.aparser;
import com.alibaba.qlexpress4.ClassSupplier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Author: DQinYuan
*/
public class ImportManager {
private final ClassSupplier classSupplier;
private final List<QLImport> importedPacks;
private final Map<String, Class<?>> importedClses;
public ImportManager(ClassSupplier classSupplier, List<QLImport> imports) {
this.classSupplier = classSupplier;
this.importedPacks = new ArrayList<>();
this.importedClses = new HashMap<>();
imports.forEach(this::addImport);
}
public ImportManager(ClassSupplier classSupplier, List<QLImport> importedPacks,
Map<String, Class<?>> importedClses) {
this.classSupplier = classSupplier;
this.importedPacks = importedPacks;
this.importedClses = importedClses;
}
public boolean addImport(QLImport anImport) {
switch (anImport.getScope()) {
case PACK:
case InnerCls:
importedPacks.add(anImport);
return true;
case CLS:
Class<?> importCls = classSupplier.loadCls(anImport.getTarget());
if (importCls == null) {
return false;
}
String[] split = anImport.getTarget().split("\\.");
importedClses.put(split[split.length - 1], importCls);
return true;
default:
return false;
}
}
public Class<?> loadQualified(String qualifiedCls) {
return classSupplier.loadCls(qualifiedCls);
}
public LoadPartQualifiedResult loadPartQualified(List<String> fieldIds) {
Class<?> qualifiedCls = null;
List<String> qualifiedPath = null;
String innerClsId = null;
final byte initState = 0;
final byte continueState = 1;
final byte loadClsState = 2;
final byte loadInnerClsState = 3;
final byte preLoadInnerClsState = 4;
byte state = initState;
nextField: for (int i = 0; i < fieldIds.size(); i++) {
String fieldId = fieldIds.get(i);
switch (state) {
case initState:
// load from imported class
Class<?> aCls = importedClses.get(fieldId);
if (aCls != null) {
qualifiedCls = aCls;
state = preLoadInnerClsState;
continue;
}
// load from imported packs
if (!Character.isLowerCase(fieldId.charAt(0))) {
for (QLImport importedPack : importedPacks) {
switch (importedPack.getScope()) {
case PACK:
Class<?> packCls = classSupplier.loadCls(importedPack.getTarget() + "." + fieldId);
if (packCls != null) {
qualifiedCls = packCls;
state = preLoadInnerClsState;
continue nextField;
}
break;
case InnerCls:
Class<?> innerCls = classSupplier.loadCls(importedPack.getTarget() + "$" + fieldId);
if (innerCls != null) {
qualifiedCls = innerCls;
state = preLoadInnerClsState;
continue nextField;
}
break;
}
}
return new LoadPartQualifiedResult(null, 0);
}
state = continueState;
qualifiedPath = new ArrayList<>();
qualifiedPath.add(fieldId);
break;
case preLoadInnerClsState:
if (!Character.isLowerCase(fieldId.charAt(0))) {
state = loadInnerClsState;
innerClsId = fieldId;
}
else {
return new LoadPartQualifiedResult(qualifiedCls, i);
}
break;
case continueState:
qualifiedPath.add(fieldId);
if (!Character.isLowerCase(fieldId.charAt(0))) {
state = loadClsState;
}
break;
case loadClsState:
qualifiedCls = classSupplier.loadCls(String.join(".", qualifiedPath));
if (qualifiedCls == null) {
return new LoadPartQualifiedResult(null, 0);
}
if (!Character.isLowerCase(fieldId.charAt(0))) {
qualifiedPath = null;
innerClsId = fieldId;
state = loadInnerClsState;
}
else {
return new LoadPartQualifiedResult(qualifiedCls, i);
}
break;
case loadInnerClsState:
Class<?> innerCls = classSupplier.loadCls(qualifiedCls.getName() + "$" + innerClsId);
if (innerCls == null) {
return new LoadPartQualifiedResult(qualifiedCls, i - 1);
}
if (!Character.isLowerCase(fieldId.charAt(0))) {
qualifiedCls = innerCls;
innerClsId = fieldId;
}
else {
return new LoadPartQualifiedResult(innerCls, i);
}
break;
}
}
switch (state) {
case continueState:
return new LoadPartQualifiedResult(null, 0);
case loadClsState:
qualifiedCls = classSupplier.loadCls(String.join(".", qualifiedPath));
return qualifiedCls == null ? new LoadPartQualifiedResult(null, fieldIds.size())
: new LoadPartQualifiedResult(qualifiedCls, fieldIds.size());
case preLoadInnerClsState:
return new LoadPartQualifiedResult(qualifiedCls, fieldIds.size());
case loadInnerClsState:
Class<?> innerCls = classSupplier.loadCls(qualifiedCls.getName() + "$" + innerClsId);
return innerCls == null ? new LoadPartQualifiedResult(qualifiedCls, fieldIds.size() - 1)
: new LoadPartQualifiedResult(innerCls, fieldIds.size());
default:
return new LoadPartQualifiedResult(null, 0);
}
}
public static class LoadPartQualifiedResult {
private final Class<?> cls;
/**
* first no class path field index
*/
private final int restIndex;
public LoadPartQualifiedResult(Class<?> cls, int restIndex) {
this.cls = cls;
this.restIndex = restIndex;
}
public Class<?> getCls() {
return cls;
}
public int getRestIndex() {
return restIndex;
}
}
enum ImportScope {
// import java.lang.*;
PACK,
// import a.b.Cls.*
InnerCls,
// import java.lang.String;
CLS
}
public static QLImport importInnerCls(String clsPath) {
return new QLImport(ImportScope.InnerCls, clsPath);
}
public static QLImport importPack(String packPath) {
return new QLImport(ImportScope.PACK, packPath);
}
public static QLImport importCls(String clsPath) {
return new QLImport(ImportScope.CLS, clsPath);
}
public static class QLImport {
private final ImportScope scope;
private final String target;
public QLImport(ImportScope scope, String target) {
this.scope = scope;
this.target = target;
}
public ImportScope getScope() {
return scope;
}
public String getTarget() {
return target;
}
}
}

View File

@@ -0,0 +1,19 @@
package com.alibaba.qlexpress4.aparser;
/**
* How to manage string interpolation, for instance, "a ${t-c} b"
*/
public enum InterpolationMode {
/**
* Implement interpolation using a QLExpress script.
*/
SCRIPT,
/**
* Implement interpolation using a variable name in context.
*/
VARIABLE,
/**
* Disable interpolation, `${xxx}` will be rendered verbatim.
*/
DISABLE
}

View File

@@ -0,0 +1,28 @@
package com.alibaba.qlexpress4.aparser;
import com.alibaba.qlexpress4.runtime.instruction.QLInstruction;
import java.util.List;
/**
* Author: DQinYuan
*/
public class MacroDefine {
private final List<QLInstruction> instructions;
private final boolean lastStmtExpress;
public MacroDefine(List<QLInstruction> instructions, boolean lastStmtExpress) {
this.instructions = instructions;
this.lastStmtExpress = lastStmtExpress;
}
public List<QLInstruction> getMacroInstructions() {
return instructions;
}
public boolean isLastStmtExpress() {
return lastStmtExpress;
}
}

View File

@@ -0,0 +1,17 @@
package com.alibaba.qlexpress4.aparser;
import com.alibaba.qlexpress4.runtime.operator.BinaryOperator;
import com.alibaba.qlexpress4.runtime.operator.unary.UnaryOperator;
/**
* Author: DQinYuan
*/
public interface OperatorFactory {
BinaryOperator getBinaryOperator(String operatorLexeme);
UnaryOperator getPrefixUnaryOperator(String operatorLexeme);
UnaryOperator getSuffixUnaryOperator(String operatorLexeme);
}

View File

@@ -0,0 +1,112 @@
package com.alibaba.qlexpress4.aparser;
import com.alibaba.qlexpress4.exception.PureErrReporter;
import com.alibaba.qlexpress4.runtime.instruction.PopInstruction;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
public class OutFunctionVisitor extends ScopeStackVisitor {
private final Set<String> outFunctions = new HashSet<>();
public OutFunctionVisitor() {
super(new ExistFunctionStack(null));
}
private static class ExistFunctionStack implements ExistStack {
private final ExistFunctionStack parent;
private final Set<String> existVars = new HashSet<>();
private ExistFunctionStack(ExistFunctionStack parent) {
this.parent = parent;
}
public void add(String varName) {
existVars.add(varName);
}
public boolean exist(String varName) {
if (existVars.contains(varName)) {
return true;
}
return parent != null && parent.exist(varName);
}
public ExistFunctionStack push() {
return new ExistFunctionStack(this);
}
public ExistFunctionStack pop() {
return parent;
}
}
@Override
public Void visitBlockStatements(QLParser.BlockStatementsContext ctx) {
List<QLParser.BlockStatementContext> nonEmptyChildren = ctx.blockStatement()
.stream()
.filter(bs -> !(bs instanceof QLParser.EmptyStatementContext))
.collect(Collectors.toList());
// process all function definitions to support forward references
for (QLParser.BlockStatementContext child : nonEmptyChildren) {
if (child instanceof QLParser.FunctionStatementContext) {
child.accept(this);
}
}
for (QLParser.BlockStatementContext child : nonEmptyChildren) {
if (!(child instanceof QLParser.FunctionStatementContext)) {
child.accept(this);
}
}
return null;
}
@Override
public Void visitPrimary(QLParser.PrimaryContext ctx) {
QLParser.PrimaryNoFixPathableContext primaryNoFixPathableContext = ctx.primaryNoFixPathable();
if (primaryNoFixPathableContext != null) {
List<QLParser.PathPartContext> pathPartContexts = ctx.pathPart();
if (primaryNoFixPathableContext instanceof QLParser.VarIdExprContext && !pathPartContexts.isEmpty()
&& pathPartContexts.get(0) instanceof QLParser.CallExprContext) {
// function call
String functionName = primaryNoFixPathableContext.getText();
if (!getStack().exist(functionName)) {
outFunctions.add(functionName);
}
}
}
return super.visitPrimary(ctx);
}
@Override
public Void visitFunctionStatement(QLParser.FunctionStatementContext ctx) {
String functionName = ctx.varId().getText();
getStack().add(functionName);
QLParser.FormalOrInferredParameterListContext paramList = ctx.formalOrInferredParameterList();
if (paramList != null) {
paramList.accept(this);
}
QLParser.BlockStatementsContext functionBlockStatements = ctx.blockStatements();
if (functionBlockStatements != null) {
push();
// recur scene
getStack().add(functionName);
functionBlockStatements.accept(this);
pop();
}
return null;
}
public Set<String> getOutFunctions() {
return outFunctions;
}
}

View File

@@ -0,0 +1,199 @@
package com.alibaba.qlexpress4.aparser;
import com.alibaba.qlexpress4.utils.QLStringUtils;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.tree.TerminalNode;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
public class OutVarAttrsVisitor extends ScopeStackVisitor {
private final Set<List<String>> outVarAttrs = new HashSet<>();
private final ImportManager importManager;
public OutVarAttrsVisitor(ImportManager importManager) {
super(new ExistVarStack(null));
this.importManager = importManager;
}
private static class ExistVarStack implements ExistStack {
private final ExistVarStack parent;
private final Set<String> existVars = new HashSet<>();
private ExistVarStack(ExistVarStack parent) {
this.parent = parent;
}
public void add(String varName) {
existVars.add(varName);
}
public boolean exist(String varName) {
if (existVars.contains(varName)) {
return true;
}
return parent != null && parent.exist(varName);
}
public ExistVarStack push() {
return new ExistVarStack(this);
}
public ExistVarStack pop() {
return parent;
}
}
// handle import
@Override
public Void visitImportCls(QLParser.ImportClsContext ctx) {
String importClsPath = ctx.varId()
.stream()
.map(QLParser.VarIdContext::getStart)
.map(Token::getText)
.collect(Collectors.joining("."));
importManager.addImport(ImportManager.importCls(importClsPath));
return null;
}
@Override
public Void visitImportPack(QLParser.ImportPackContext ctx) {
List<QLParser.VarIdContext> importPackPathTokens = ctx.varId();
boolean isInnerCls =
!Character.isLowerCase(importPackPathTokens.get(importPackPathTokens.size() - 1).getText().charAt(0));
String importPath = importPackPathTokens.stream()
.map(QLParser.VarIdContext::getStart)
.map(Token::getText)
.collect(Collectors.joining("."));
importManager
.addImport(isInnerCls ? ImportManager.importInnerCls(importPath) : ImportManager.importPack(importPath));
return null;
}
// collect exist variable name
/**
* @param ctx int a = 10;
* @return a
*/
@Override
public Void visitVariableDeclaratorId(QLParser.VariableDeclaratorIdContext ctx) {
QLParser.VarIdContext varIdContext = ctx.varId();
getStack().add(varIdContext.getText());
return null;
}
@Override
public Void visitLeftHandSide(QLParser.LeftHandSideContext ctx) {
List<QLParser.PathPartContext> pathPartContexts = ctx.pathPart();
String leftVarName = ctx.varId().getText();
if (pathPartContexts.isEmpty()) {
getStack().add(leftVarName);
}
else if (!getStack().exist(leftVarName)) {
addAttrs(leftVarName, pathPartContexts);
}
return null;
}
// exclude function call
@Override
public Void visitPrimary(QLParser.PrimaryContext ctx) {
QLParser.PrimaryNoFixPathableContext primaryNoFixPathableContext = ctx.primaryNoFixPathable();
if (primaryNoFixPathableContext != null) {
List<QLParser.PathPartContext> pathPartContexts = ctx.pathPart();
if (primaryNoFixPathableContext instanceof QLParser.VarIdExprContext && !pathPartContexts.isEmpty()
&& pathPartContexts.get(0) instanceof QLParser.CallExprContext) {
// function call
for (QLParser.PathPartContext pathPartContext : pathPartContexts) {
pathPartContext.accept(this);
}
return null;
}
if (primaryNoFixPathableContext instanceof QLParser.VarIdExprContext) {
int restIndex = parseOutVarAttrInPath(((QLParser.VarIdExprContext)primaryNoFixPathableContext).varId(),
pathPartContexts);
for (int i = restIndex; i < pathPartContexts.size(); i++) {
pathPartContexts.get(i).accept(this);
}
return null;
}
}
return super.visitPrimary(ctx);
}
private int parseOutVarAttrInPath(QLParser.VarIdContext idContext,
List<QLParser.PathPartContext> pathPartContexts) {
List<String> headPartIds = new ArrayList<>();
String primaryId = idContext.getText();
headPartIds.add(primaryId);
for (QLParser.PathPartContext pathPartContext : pathPartContexts) {
if (pathPartContext instanceof QLParser.FieldAccessContext) {
headPartIds.add(parseFieldId(((QLParser.FieldAccessContext)pathPartContext).fieldId()));
}
else {
break;
}
}
ImportManager.LoadPartQualifiedResult loadPartQualifiedResult = importManager.loadPartQualified(headPartIds);
if (loadPartQualifiedResult.getCls() != null) {
return loadPartQualifiedResult.getRestIndex() - 1;
}
else {
return getStack().exist(primaryId) ? 0 : addAttrs(primaryId, pathPartContexts);
}
}
private String parseFieldId(QLParser.FieldIdContext ctx) {
TerminalNode quoteStringLiteral = ctx.QuoteStringLiteral();
if (quoteStringLiteral != null) {
return QLStringUtils.parseStringEscape(quoteStringLiteral.getText());
}
return ctx.getStart().getText();
}
private String getFieldId(QLParser.PathPartContext pathPartContext) {
if (pathPartContext instanceof QLParser.FieldAccessContext) {
return parseFieldId(((QLParser.FieldAccessContext)pathPartContext).fieldId());
}
else if (pathPartContext instanceof QLParser.OptionalFieldAccessContext) {
return parseFieldId(((QLParser.OptionalFieldAccessContext)pathPartContext).fieldId());
}
else if (pathPartContext instanceof QLParser.SpreadFieldAccessContext) {
return parseFieldId(((QLParser.SpreadFieldAccessContext)pathPartContext).fieldId());
}
else {
return null;
}
}
private int addAttrs(String primaryId, List<QLParser.PathPartContext> pathPartContexts) {
List<String> attrs = new ArrayList<>();
attrs.add(primaryId);
int i = 0;
for (; i < pathPartContexts.size(); i++) {
String fieldId = getFieldId(pathPartContexts.get(i));
if (fieldId == null) {
break;
}
attrs.add(fieldId);
}
outVarAttrs.add(attrs);
return i;
}
public Set<List<String>> getOutVarAttrs() {
return outVarAttrs;
}
}

View File

@@ -0,0 +1,194 @@
package com.alibaba.qlexpress4.aparser;
import com.alibaba.qlexpress4.utils.QLStringUtils;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.tree.TerminalNode;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Author: DQinYuan
*/
public class OutVarNamesVisitor extends ScopeStackVisitor {
private final Set<String> outVars = new HashSet<>();
private final ImportManager importManager;
public OutVarNamesVisitor(ImportManager importManager) {
super(new ExistVarStack(null));
this.importManager = importManager;
}
private static class ExistVarStack implements ExistStack {
private final ExistVarStack parent;
private final Set<String> existVars = new HashSet<>();
private ExistVarStack(ExistVarStack parent) {
this.parent = parent;
}
public void add(String varName) {
existVars.add(varName);
}
public boolean exist(String varName) {
if (existVars.contains(varName)) {
return true;
}
return parent != null && parent.exist(varName);
}
public ExistVarStack push() {
return new ExistVarStack(this);
}
public ExistVarStack pop() {
return parent;
}
}
// handle import
@Override
public Void visitImportCls(QLParser.ImportClsContext ctx) {
String importClsPath = ctx.varId()
.stream()
.map(QLParser.VarIdContext::getStart)
.map(Token::getText)
.collect(Collectors.joining("."));
importManager.addImport(ImportManager.importCls(importClsPath));
return null;
}
@Override
public Void visitImportPack(QLParser.ImportPackContext ctx) {
List<QLParser.VarIdContext> importPackPathTokens = ctx.varId();
boolean isInnerCls =
!Character.isLowerCase(importPackPathTokens.get(importPackPathTokens.size() - 1).getText().charAt(0));
String importPath = importPackPathTokens.stream()
.map(QLParser.VarIdContext::getStart)
.map(Token::getText)
.collect(Collectors.joining("."));
importManager
.addImport(isInnerCls ? ImportManager.importInnerCls(importPath) : ImportManager.importPack(importPath));
return null;
}
// collect exist variable name
/**
* @param ctx int a = 10;
* @return a
*/
@Override
public Void visitVariableDeclaratorId(QLParser.VariableDeclaratorIdContext ctx) {
QLParser.VarIdContext varIdContext = ctx.varId();
getStack().add(varIdContext.getText());
return null;
}
@Override
public Void visitLeftHandSide(QLParser.LeftHandSideContext ctx) {
List<QLParser.PathPartContext> pathPartContexts = ctx.pathPart();
String leftVarName = ctx.varId().getText();
if (pathPartContexts.isEmpty()) {
getStack().add(leftVarName);
}
else if (!getStack().exist(leftVarName)) {
outVars.add(leftVarName);
}
return null;
}
// selector
@Override
public Void visitContextSelectExpr(QLParser.ContextSelectExprContext ctx) {
String variableName = ctx.SelectorVariable_VANME().getText().trim();
if (!getStack().exist(variableName)) {
outVars.add(variableName);
}
return null;
}
// exclude function call
@Override
public Void visitPrimary(QLParser.PrimaryContext ctx) {
QLParser.PrimaryNoFixPathableContext primaryNoFixPathableContext = ctx.primaryNoFixPathable();
if (primaryNoFixPathableContext != null) {
List<QLParser.PathPartContext> pathPartContexts = ctx.pathPart();
if (primaryNoFixPathableContext instanceof QLParser.VarIdExprContext && !pathPartContexts.isEmpty()
&& pathPartContexts.get(0) instanceof QLParser.CallExprContext) {
// function call
for (QLParser.PathPartContext pathPartContext : pathPartContexts) {
pathPartContext.accept(this);
}
return null;
}
if (primaryNoFixPathableContext instanceof QLParser.VarIdExprContext) {
int restIndex = parseVarIdInPath(((QLParser.VarIdExprContext)primaryNoFixPathableContext).varId(),
pathPartContexts);
for (int i = restIndex; i < pathPartContexts.size(); i++) {
pathPartContexts.get(i).accept(this);
}
return null;
}
}
return super.visitPrimary(ctx);
}
private int parseVarIdInPath(QLParser.VarIdContext idContext, List<QLParser.PathPartContext> pathPartContexts) {
List<String> headPartIds = new ArrayList<>();
String primaryId = idContext.getText();
headPartIds.add(primaryId);
for (QLParser.PathPartContext pathPartContext : pathPartContexts) {
if (pathPartContext instanceof QLParser.FieldAccessContext) {
headPartIds.add(parseFieldId(((QLParser.FieldAccessContext)pathPartContext).fieldId()));
}
else {
break;
}
}
ImportManager.LoadPartQualifiedResult loadPartQualifiedResult = importManager.loadPartQualified(headPartIds);
if (loadPartQualifiedResult.getCls() != null) {
return loadPartQualifiedResult.getRestIndex() - 1;
}
else {
if (!getStack().exist(primaryId)) {
outVars.add(primaryId);
}
return 0;
}
}
private String parseFieldId(QLParser.FieldIdContext ctx) {
TerminalNode quoteStringLiteral = ctx.QuoteStringLiteral();
if (quoteStringLiteral != null) {
return QLStringUtils.parseStringEscape(quoteStringLiteral.getText());
}
return ctx.getStart().getText();
}
// collect out variables name
@Override
public Void visitVarIdExpr(QLParser.VarIdExprContext ctx) {
String varName = ctx.varId().getText();
if (!getStack().exist(varName)) {
outVars.add(varName);
}
return null;
}
public Set<String> getOutVars() {
return outVars;
}
}

View File

@@ -0,0 +1,34 @@
package com.alibaba.qlexpress4.aparser;
/**
* Author: DQinYuan
*/
public interface ParserOperatorManager {
enum OpType {
PREFIX, SUFFIX, MIDDLE
}
/**
* determine whether lexeme is opType or not
* @param lexeme lexeme
* @param opType type of operator
* @return true if lexeme is opType
*/
boolean isOpType(String lexeme, OpType opType);
/**
* get binary operator precedence
* @param lexeme lexeme
* @return null if lexeme not a operator
*/
Integer precedence(String lexeme);
/**
* Get the aliased token type of lexeme.
*
* @param lexeme the source text of the token
* @return alias token type (or {@code null} if none)
*/
Integer getAlias(String lexeme);
}

View File

@@ -0,0 +1,26 @@
package com.alibaba.qlexpress4.aparser;
import com.alibaba.qlexpress4.runtime.QLambdaDefinition;
import com.alibaba.qlexpress4.runtime.trace.TracePointTree;
import java.util.List;
public class QCompileCache {
private final QLambdaDefinition qLambdaDefinition;
private final List<TracePointTree> expressionTracePoints;
public QCompileCache(QLambdaDefinition qLambdaDefinition, List<TracePointTree> expressionTracePoints) {
this.qLambdaDefinition = qLambdaDefinition;
this.expressionTracePoints = expressionTracePoints;
}
public QLambdaDefinition getQLambdaDefinition() {
return qLambdaDefinition;
}
public List<TracePointTree> getExpressionTracePoints() {
return expressionTracePoints;
}
}

View File

@@ -0,0 +1,50 @@
package com.alibaba.qlexpress4.aparser;
import com.alibaba.qlexpress4.exception.QLErrorCodes;
import com.alibaba.qlexpress4.exception.QLException;
import org.antlr.v4.runtime.BaseErrorListener;
import org.antlr.v4.runtime.Parser;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.Recognizer;
import org.antlr.v4.runtime.Token;
public class QLErrorListener extends BaseErrorListener {
private final String script;
public QLErrorListener(String script) {
this.script = script;
}
@Override
public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line, int charPositionInLine,
String msg, RecognitionException e) {
Token currentToken = (Token)offendingSymbol;
String tokenText = currentToken.getType() == Token.EOF ? "<EOF>" : currentToken.getText();
String preHandledScript = currentToken.getType() == Token.EOF ? script + "<EOF>" : script;
String preHandledMsg = errMsg(((Parser)recognizer).getContext(), currentToken, msg);
throw QLException.reportScannerErr(preHandledScript,
currentToken.getStartIndex(),
currentToken.getLine(),
currentToken.getCharPositionInLine(),
tokenText,
QLErrorCodes.SYNTAX_ERROR.name(),
preHandledMsg);
}
private String errMsg(ParserRuleContext ruleContext, Token currentToken, String msg) {
if ("'".equals(currentToken.getText()) || "\"".equals(currentToken.getText())
|| ruleContext.getRuleIndex() == QLParser.RULE_doubleQuoteStringLiteral) {
return "unterminated string literal";
}
if ("import".equals(currentToken.getText())) {
return "Import statement is not at the beginning of the file.";
}
if (ruleContext.getRuleIndex() == QLParser.RULE_importDeclaration && "static".equals(currentToken.getText())) {
return "'import static' not supported temporarily";
}
return msg;
}
}

View File

@@ -0,0 +1,61 @@
package com.alibaba.qlexpress4.aparser;
import org.antlr.v4.runtime.DefaultErrorStrategy;
import org.antlr.v4.runtime.InputMismatchException;
import org.antlr.v4.runtime.Parser;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.Vocabulary;
import org.antlr.v4.runtime.misc.Interval;
import org.antlr.v4.runtime.misc.IntervalSet;
import java.util.ArrayList;
import java.util.List;
/**
* Author: DQinYuan
*/
public class QLErrorStrategy extends DefaultErrorStrategy {
@Override
protected void reportInputMismatch(Parser recognizer, InputMismatchException e) {
String msg = "mismatched input " + getTokenErrorDisplay(e.getOffendingToken()) + " expecting "
+ intervalSetString(e.getExpectedTokens(), recognizer.getVocabulary());
recognizer.notifyErrorListeners(e.getOffendingToken(), msg, e);
}
private static String intervalSetString(IntervalSet expectedTokens, Vocabulary vocabulary) {
if (expectedTokens.getIntervals() == null || expectedTokens.getIntervals().isEmpty()) {
return "{}";
}
List<String> eleNames = new ArrayList<>();
for (Interval I : expectedTokens.getIntervals()) {
int a = I.a;
int b = I.b;
for (int i = a; i <= b; i++) {
if (i != QLexer.NEWLINE) {
eleNames.add(elementName(vocabulary, i));
}
}
}
if (eleNames.isEmpty()) {
return "{}";
}
if (eleNames.size() == 1) {
return eleNames.get(0);
}
return String.join(",", eleNames);
}
private static String elementName(Vocabulary vocabulary, int a) {
if (a == Token.EOF) {
return "<EOF>";
}
else if (a == Token.EPSILON) {
return "<EPSILON>";
}
else {
return vocabulary.getDisplayName(a);
}
}
}

View File

@@ -0,0 +1,103 @@
package com.alibaba.qlexpress4.aparser;
import com.alibaba.qlexpress4.exception.QLErrorCodes;
import com.alibaba.qlexpress4.exception.QLException;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.Token;
public class QLExtendLexer extends QLexer {
private final String script;
private final InterpolationMode interpolationMode;
private final String selectorStart;
private final String selectorEnd;
private final boolean strictNewLines;
public QLExtendLexer(CharStream input, String script, InterpolationMode interpolationMode, String selectorStart,
String selectorEnd, boolean strictNewLines) {
super(input);
this.script = script;
this.interpolationMode = interpolationMode;
this.selectorStart = selectorStart;
this.selectorEnd = selectorEnd;
this.strictNewLines = strictNewLines;
}
@Override
public Token nextToken() {
Token token = super.nextToken();
// In non-strict mode, skip NEWLINE tokens
if (!strictNewLines && token.getType() == QLexer.NEWLINE) {
// Skip NEWLINEs by recursively calling nextToken()
return nextToken();
}
return token;
}
@Override
protected InterpolationMode getInterpolationMode() {
return interpolationMode;
}
@Override
protected String getSelectorStart() {
return selectorStart;
}
@Override
protected void consumeSelectorVariable() {
StringBuilder t = new StringBuilder();
int selectorEndLength = selectorEnd.length();
char lastCharOfSelector = selectorEnd.charAt(selectorEndLength - 1);
t.ensureCapacity(selectorEndLength * 2);
while (true) {
int curChInt = _input.LA(1);
if (curChInt == Token.EOF || curChInt == '\n') {
// mismatch
throwScannerException(t.toString(), "unterminated selector");
}
char curCh = (char)curChInt;
t.append(curCh);
_input.consume();
if (curCh == lastCharOfSelector && t.length() >= selectorEndLength) {
if (checkEndsWith(t, selectorEnd)) {
// match
String text = t.toString();
setText(text.substring(0, text.length() - selectorEndLength));
popMode();
break;
}
}
}
}
@Override
protected void throwScannerException(String lexeme, String reason) {
throw QLException.reportScannerErr(script,
this._tokenStartCharIndex,
this._tokenStartLine,
this._tokenStartCharPositionInLine,
lexeme,
QLErrorCodes.SYNTAX_ERROR.name(),
reason);
}
private boolean checkEndsWith(StringBuilder sb, String suffix) {
int suffixLength = suffix.length();
int sbLength = sb.length();
for (int i = 0; i < suffixLength; i++) {
if (sb.charAt(sbLength - suffixLength + i) != suffix.charAt(i)) {
return false;
}
}
return true;
}
}

View File

@@ -0,0 +1,40 @@
package com.alibaba.qlexpress4.aparser;
import com.alibaba.qlexpress4.aparser.ParserOperatorManager.OpType;
public class QLExtendParser extends QLParser {
private final ParserOperatorManager opM;
private final InterpolationMode interpolationMode;
private final boolean strictNewLines;
public QLExtendParser(AliasTokenStream input, ParserOperatorManager opM, InterpolationMode interpolationMode,
boolean strictNewLines) {
super(input);
this.opM = opM;
this.interpolationMode = interpolationMode;
this.strictNewLines = strictNewLines;
}
@Override
protected boolean isOpType(String lexeme, OpType opType) {
return opM.isOpType(lexeme, opType);
}
@Override
protected Integer precedence(String lexeme) {
return opM.precedence(lexeme);
}
@Override
protected InterpolationMode getInterpolationMode() {
return interpolationMode;
}
@Override
protected boolean isStrictNewLines() {
return strictNewLines;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,99 @@
package com.alibaba.qlexpress4.aparser;
public abstract class ScopeStackVisitor extends QLParserBaseVisitor<Void> {
private ExistStack existStack;
protected ScopeStackVisitor(ExistStack existStack) {
this.existStack = existStack;
}
// scope
@Override
public Void visitBlockExpr(QLParser.BlockExprContext ctx) {
push();
super.visitBlockExpr(ctx);
pop();
return null;
}
@Override
public Void visitQlIf(QLParser.QlIfContext qlIfContext) {
qlIfContext.condition.accept(this);
push();
qlIfContext.thenBody().accept(this);
pop();
QLParser.ElseBodyContext elseBodyContext = qlIfContext.elseBody();
if (elseBodyContext != null) {
push();
elseBodyContext.accept(this);
pop();
}
return null;
}
@Override
public Void visitTryCatchExpr(QLParser.TryCatchExprContext ctx) {
QLParser.BlockStatementsContext blockStatementsContext = ctx.blockStatements();
if (blockStatementsContext != null) {
push();
blockStatementsContext.accept(this);
pop();
}
QLParser.TryCatchesContext tryCatchesContext = ctx.tryCatches();
if (tryCatchesContext != null) {
tryCatchesContext.accept(this);
}
QLParser.TryFinallyContext tryFinallyContext = ctx.tryFinally();
if (tryFinallyContext != null) {
push();
tryFinallyContext.accept(this);
pop();
}
return null;
}
@Override
public Void visitTryCatch(QLParser.TryCatchContext ctx) {
push();
super.visitTryCatch(ctx);
pop();
return null;
}
@Override
public Void visitFunctionStatement(QLParser.FunctionStatementContext ctx) {
ctx.varId().accept(this);
QLParser.FormalOrInferredParameterListContext paramList = ctx.formalOrInferredParameterList();
if (paramList != null) {
paramList.accept(this);
}
QLParser.BlockStatementsContext functionBlockStatements = ctx.blockStatements();
if (functionBlockStatements != null) {
push();
functionBlockStatements.accept(this);
pop();
}
return null;
}
public void push() {
this.existStack = this.existStack.push();
}
public void pop() {
this.existStack = this.existStack.pop();
}
public ExistStack getStack() {
return existStack;
}
}

View File

@@ -0,0 +1,101 @@
package com.alibaba.qlexpress4.aparser;
import com.alibaba.qlexpress4.runtime.operator.OperatorManager;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.Parser;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.WritableToken;
import org.antlr.v4.runtime.atn.DecisionInfo;
import org.antlr.v4.runtime.atn.DecisionState;
import org.antlr.v4.runtime.atn.PredictionMode;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.stream.Collectors;
/**
* Author: DQinYuan
*/
public class SyntaxTreeFactory {
private static final AtomicBoolean IS_WARM_UP = new AtomicBoolean();
public static void warmUp() {
if (IS_WARM_UP.compareAndSet(false, true)) {
// warm up
warmUpExpress("1+1");
warmUpExpress("a = b + c");
}
}
private static void warmUpExpress(String script) {
buildTree(script, new OperatorManager(), false, false, s -> {
}, InterpolationMode.SCRIPT, "${", "}", true);
}
public static QLParser.ProgramContext buildTree(String script, ParserOperatorManager operatorManager,
boolean printTree, boolean profile, Consumer<String> printer, InterpolationMode interpolationMode,
String selectorStart, String selectorEnd, boolean strictNewLines) {
QLexer lexer = new QLExtendLexer(CharStreams.fromString(script), script, interpolationMode, selectorStart,
selectorEnd, strictNewLines);
CommonTokenStream tokens = new CommonTokenStream(lexer);
QLParser qlGrammarParser = new QLExtendParser(new AliasTokenStream(tokens, operatorManager), operatorManager,
interpolationMode, strictNewLines);
if (!printTree) {
qlGrammarParser.removeErrorListeners();
}
qlGrammarParser.addErrorListener(new QLErrorListener(script));
qlGrammarParser.setErrorHandler(new QLErrorStrategy());
qlGrammarParser.getInterpreter().setPredictionMode(PredictionMode.SLL);
if (profile) {
qlGrammarParser.setProfile(true);
}
QLParser.ProgramContext programContext = qlGrammarParser.program();
if (printTree) {
printer.accept(tokens.getTokens().stream().map(Token::getText).collect(Collectors.joining(" | ")));
printer.accept(programContext.toStringTree(qlGrammarParser));
}
if (profile) {
profileParser(qlGrammarParser);
}
return programContext;
}
private static void profileParser(Parser parser) {
System.out.printf("%-" + 35 + "s", "rule");
System.out.printf("%-" + 15 + "s", "time");
System.out.printf("%-" + 15 + "s", "invocations");
System.out.printf("%-" + 15 + "s", "lookahead");
System.out.printf("%-" + 15 + "s", "lookahead(max)");
System.out.printf("%-" + 15 + "s", "ambiguities");
System.out.printf("%-" + 15 + "s", "errors");
System.out.printf("%-" + 15 + "s", "fallBack");
System.out.println();
for (DecisionInfo decisionInfo : parser.getParseInfo().getDecisionInfo()) {
DecisionState ds = parser.getATN().getDecisionState(decisionInfo.decision);
String rule = parser.getRuleNames()[ds.ruleIndex];
if (decisionInfo.timeInPrediction > 0) {
System.out.printf("%-" + 35 + "s", rule);
System.out.printf("%-" + 15 + "s", decisionInfo.timeInPrediction);
System.out.printf("%-" + 15 + "s", decisionInfo.invocations);
System.out.printf("%-" + 15 + "s", decisionInfo.SLL_TotalLook);
System.out.printf("%-" + 15 + "s", decisionInfo.SLL_MaxLook);
System.out.printf("%-" + 15 + "s", decisionInfo.ambiguities.size());
System.out.printf("%-" + 15 + "s", decisionInfo.errors);
System.out.printf("%-" + 15 + "s", decisionInfo.LL_Fallback);
System.out.println();
}
}
}
public static Token preHandleToken(Token originToken, ParserOperatorManager operatorManager) {
if (originToken instanceof WritableToken && originToken.getType() == QLexer.ID) {
Integer aliasId = operatorManager.getAlias(originToken.getText());
if (aliasId != null && originToken.getType() != aliasId) {
((WritableToken)originToken).setType(aliasId);
}
}
return originToken;
}
}

View File

@@ -0,0 +1,441 @@
package com.alibaba.qlexpress4.aparser;
import com.alibaba.qlexpress4.aparser.QLParser.EmptyStatementContext;
import com.alibaba.qlexpress4.aparser.QLParser.ExpressionContext;
import com.alibaba.qlexpress4.aparser.QLParser.LocalVariableDeclarationStatementContext;
import com.alibaba.qlexpress4.aparser.QLParser.PathPartContext;
import com.alibaba.qlexpress4.aparser.QLParser.VarIdContext;
import com.alibaba.qlexpress4.runtime.trace.TracePointTree;
import com.alibaba.qlexpress4.runtime.trace.TraceType;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.tree.ParseTree;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
public class TraceExpressionVisitor extends QLParserBaseVisitor<TracePointTree> {
private final List<TracePointTree> expressionTracePoints = new ArrayList<>();
public List<TracePointTree> getExpressionTracePoints() {
return expressionTracePoints;
}
// ==================== Statement ====================
@Override
public TracePointTree visitThrowStatement(QLParser.ThrowStatementContext ctx) {
TracePointTree tracePoint = newPoint(TraceType.STATEMENT, Collections.emptyList(), ctx.getStart());
expressionTracePoints.add(tracePoint);
return null;
}
@Override
public TracePointTree visitLocalVariableDeclarationStatement(LocalVariableDeclarationStatementContext ctx) {
TracePointTree tracePoint = newPoint(TraceType.STATEMENT, Collections.emptyList(), ctx.getStart());
expressionTracePoints.add(tracePoint);
return null;
}
@Override
public TracePointTree visitExpressionStatement(QLParser.ExpressionStatementContext ctx) {
TracePointTree expressionTrace = visitExpression(ctx.expression());
expressionTracePoints.add(expressionTrace);
return null;
}
@Override
public TracePointTree visitWhileStatement(QLParser.WhileStatementContext ctx) {
TracePointTree tracePoint = newPoint(TraceType.STATEMENT, Collections.emptyList(), ctx.getStart());
expressionTracePoints.add(tracePoint);
return null;
}
@Override
public TracePointTree visitTraditionalForStatement(QLParser.TraditionalForStatementContext ctx) {
TracePointTree tracePoint = newPoint(TraceType.STATEMENT, Collections.emptyList(), ctx.getStart());
expressionTracePoints.add(tracePoint);
return null;
}
@Override
public TracePointTree visitForEachStatement(QLParser.ForEachStatementContext ctx) {
TracePointTree tracePoint = newPoint(TraceType.STATEMENT, Collections.emptyList(), ctx.getStart());
expressionTracePoints.add(tracePoint);
return null;
}
@Override
public TracePointTree visitFunctionStatement(QLParser.FunctionStatementContext ctx) {
TracePointTree tracePoint =
newPoint(TraceType.DEFINE_FUNCTION, Collections.emptyList(), ctx.varId().getStart());
expressionTracePoints.add(tracePoint);
return null;
}
@Override
public TracePointTree visitMacroStatement(QLParser.MacroStatementContext ctx) {
TracePointTree tracePoint = newPoint(TraceType.DEFINE_MACRO, Collections.emptyList(), ctx.varId().getStart());
expressionTracePoints.add(tracePoint);
return null;
}
@Override
public TracePointTree visitBreakContinueStatement(QLParser.BreakContinueStatementContext ctx) {
TracePointTree tracePoint = newPoint(TraceType.STATEMENT, Collections.emptyList(), ctx.getStart());
expressionTracePoints.add(tracePoint);
return null;
}
@Override
public TracePointTree visitReturnStatement(QLParser.ReturnStatementContext ctx) {
ExpressionContext returnExpressionContext = ctx.expression();
if (returnExpressionContext != null) {
TracePointTree returnExpressionTrace = returnExpressionContext.accept(this);
TracePointTree tracePoint =
newPoint(TraceType.RETURN, Collections.singletonList(returnExpressionTrace), ctx.getStart());
expressionTracePoints.add(tracePoint);
}
else {
TracePointTree tracePoint = newPoint(TraceType.RETURN, Collections.emptyList(), ctx.getStart());
expressionTracePoints.add(tracePoint);
}
return null;
}
@Override
public TracePointTree visitEmptyStatement(QLParser.EmptyStatementContext ctx) {
expressionTracePoints.add(newPoint(TraceType.STATEMENT, Collections.emptyList(), ctx.getStart()));
return null;
}
@Override
public TracePointTree visitBlockStatements(QLParser.BlockStatementsContext ctx) {
List<QLParser.BlockStatementContext> emptyChildren = ctx.blockStatement()
.stream()
.filter(bs -> bs instanceof EmptyStatementContext)
.collect(Collectors.toList());
if (emptyChildren.size() == ctx.blockStatement().size()) {
// all emtpty
emptyChildren.get(0).accept(this);
return null;
}
for (QLParser.BlockStatementContext blockStatementContext : ctx.blockStatement()) {
if (!(blockStatementContext instanceof EmptyStatementContext)) {
blockStatementContext.accept(this);
}
}
return null;
}
// ==================== Expression ====================
@Override
public TracePointTree visitExpression(QLParser.ExpressionContext ctx) {
QLParser.TernaryExprContext ternaryExprContext = ctx.ternaryExpr();
if (ternaryExprContext != null) {
return visitTernaryExpr(ternaryExprContext);
}
TracePointTree leftChildTree = visitLeftHandSide(ctx.leftHandSide());
TracePointTree rightChildTree = visitExpression(ctx.expression());
return newPoint(TraceType.OPERATOR,
Arrays.asList(leftChildTree, rightChildTree),
ctx.assignOperator().getStart());
}
@Override
public TracePointTree visitLeftHandSide(QLParser.LeftHandSideContext ctx) {
VarIdContext varIdContext = ctx.varId();
;
List<PathPartContext> pathParts = ctx.pathPart();
TracePointTree leftChildTree = null;
int start = 0;
if (!pathParts.isEmpty() && pathParts.get(0) instanceof QLParser.CallExprContext) {
QLParser.CallExprContext callExprContext = (QLParser.CallExprContext)pathParts.get(0);
leftChildTree = newPoint(TraceType.FUNCTION,
traceArgumentList(callExprContext.argumentList()),
varIdContext.getStart());
start = 1;
}
else {
leftChildTree = newPoint(TraceType.VARIABLE, Collections.emptyList(), ctx.getStart());
}
return pathParts(leftChildTree, pathParts.subList(start, pathParts.size()));
}
@Override
public TracePointTree visitTernaryExpr(QLParser.TernaryExprContext ctx) {
if (ctx.thenExpr == null) {
return ctx.condition.accept(this);
}
TracePointTree conditionPoint = ctx.condition.accept(this);
TracePointTree thenPoint = ctx.thenExpr.accept(this);
TracePointTree elsePoint = ctx.elseExpr.accept(this);
Token keyToken = ctx.QUESTION().getSymbol();
return newPoint(TraceType.OPERATOR, Arrays.asList(conditionPoint, thenPoint, elsePoint), keyToken);
}
@Override
public TracePointTree visitBaseExpr(QLParser.BaseExprContext ctx) {
TracePointTree leftChildTree = visitPrimary(ctx.primary());
for (QLParser.LeftAssoContext leftAssoContext : ctx.leftAsso()) {
Token keyToken = leftAssoContext.binaryop().getStart();
TracePointTree rightChildTree = visitBaseExpr(leftAssoContext.baseExpr());
leftChildTree = newPoint(TraceType.OPERATOR, Arrays.asList(leftChildTree, rightChildTree), keyToken);
}
return leftChildTree;
}
@Override
public TracePointTree visitPrimary(QLParser.PrimaryContext ctx) {
TracePointTree leftChildTree = primaryBaseTrace(ctx);
// suffix
QLParser.SuffixExpressContext suffixExpressContext = ctx.suffixExpress();
if (suffixExpressContext != null) {
leftChildTree =
newPoint(TraceType.OPERATOR, Collections.singletonList(leftChildTree), suffixExpressContext.getStart());
}
// prefix
QLParser.PrefixExpressContext prefixExpressContext = ctx.prefixExpress();
if (prefixExpressContext != null) {
leftChildTree =
newPoint(TraceType.OPERATOR, Collections.singletonList(leftChildTree), prefixExpressContext.getStart());
}
return leftChildTree;
}
@Override
public TracePointTree visitConstExpr(QLParser.ConstExprContext ctx) {
return newPoint(TraceType.VALUE, Collections.emptyList(), ctx.getText(), ctx.getStart());
}
@Override
public TracePointTree visitCastExpr(QLParser.CastExprContext ctx) {
return ctx.primary().accept(this);
}
@Override
public TracePointTree visitGroupExpr(QLParser.GroupExprContext ctx) {
return ctx.expression().accept(this);
}
@Override
public TracePointTree visitNewObjExpr(QLParser.NewObjExprContext ctx) {
return newPoint(TraceType.PRIMARY, Collections.emptyList(), ctx.getText(), ctx.getStart());
}
@Override
public TracePointTree visitNewEmptyArrExpr(QLParser.NewEmptyArrExprContext ctx) {
return newPoint(TraceType.PRIMARY, Collections.emptyList(), ctx.getText(), ctx.getStart());
}
@Override
public TracePointTree visitNewInitArrExpr(QLParser.NewInitArrExprContext ctx) {
return newPoint(TraceType.PRIMARY, Collections.emptyList(), ctx.getText(), ctx.getStart());
}
@Override
public TracePointTree visitLambdaExpr(QLParser.LambdaExprContext ctx) {
return newPoint(TraceType.PRIMARY, Collections.emptyList(), ctx.ARROW().getSymbol());
}
@Override
public TracePointTree visitVarIdExpr(QLParser.VarIdExprContext ctx) {
return newPoint(TraceType.VARIABLE, Collections.emptyList(), ctx.getStart());
}
@Override
public TracePointTree visitTypeExpr(QLParser.TypeExprContext ctx) {
return newPoint(TraceType.VALUE, Collections.emptyList(), ctx.getStart());
}
@Override
public TracePointTree visitListExpr(QLParser.ListExprContext ctx) {
QLParser.ListItemsContext listItemsContext = ctx.listItems();
if (listItemsContext == null) {
return newPoint(TraceType.LIST, Collections.emptyList(), ctx.getStart());
}
List<TracePointTree> children = listItemsContext.expression()
.stream()
.map(expression -> expression.accept(this))
.collect(Collectors.toList());
return newPoint(TraceType.LIST, children, ctx.getStart());
}
@Override
public TracePointTree visitMapExpr(QLParser.MapExprContext ctx) {
return newPoint(TraceType.MAP, Collections.emptyList(), ctx.getStart());
}
@Override
public TracePointTree visitBlockExpr(QLParser.BlockExprContext ctx) {
TraceExpressionVisitor traceExpressionVisitor = new TraceExpressionVisitor();
ctx.blockStatements().accept(traceExpressionVisitor);
List<TracePointTree> children = traceExpressionVisitor.getExpressionTracePoints();
return newPoint(TraceType.BLOCK, children, ctx.getStart());
}
@Override
public TracePointTree visitQlIf(QLParser.QlIfContext ctx) {
List<TracePointTree> children = new ArrayList<>(3);
children.add(ctx.condition.accept(this));
// thenBody
children.add(visitThenBody(ctx.thenBody()));
// elseBody
if (ctx.elseBody() != null) {
children.add(visitElseBody(ctx.elseBody()));
}
return newPoint(TraceType.IF, children, "if", ctx.getStart());
}
@Override
public TracePointTree visitThenBody(QLParser.ThenBodyContext thenBody) {
if (thenBody.blockStatements() != null) {
TraceExpressionVisitor blockVisitor = new TraceExpressionVisitor();
thenBody.blockStatements().accept(blockVisitor);
return newPoint(TraceType.BLOCK, blockVisitor.getExpressionTracePoints(), thenBody.getStart());
}
else if (thenBody.expression() != null) {
return thenBody.expression().accept(this);
}
else if (thenBody.blockStatement() != null) {
TraceExpressionVisitor blockVisitor = new TraceExpressionVisitor();
thenBody.blockStatement().accept(blockVisitor);
return blockVisitor.getExpressionTracePoints().get(0);
}
return newPoint(TraceType.BLOCK, Collections.emptyList(), thenBody.getStart());
}
@Override
public TracePointTree visitElseBody(QLParser.ElseBodyContext elseBody) {
if (elseBody.blockStatements() != null) {
TraceExpressionVisitor blockVisitor = new TraceExpressionVisitor();
elseBody.blockStatements().accept(blockVisitor);
return newPoint(TraceType.BLOCK, blockVisitor.getExpressionTracePoints(), elseBody.getStart());
}
else if (elseBody.expression() != null) {
return elseBody.expression().accept(this);
}
else if (elseBody.blockStatement() != null) {
TraceExpressionVisitor blockVisitor = new TraceExpressionVisitor();
elseBody.blockStatement().accept(blockVisitor);
return blockVisitor.getExpressionTracePoints().get(0);
}
else if (elseBody.qlIf() != null) {
return elseBody.qlIf().accept(this);
}
return newPoint(TraceType.BLOCK, Collections.emptyList(), elseBody.getStart());
}
@Override
public TracePointTree visitTryCatchExpr(QLParser.TryCatchExprContext ctx) {
return newPoint(TraceType.PRIMARY, Collections.emptyList(), ctx.getStart());
}
@Override
public TracePointTree visitContextSelectExpr(QLParser.ContextSelectExprContext ctx) {
return newPoint(TraceType.PRIMARY, Collections.emptyList(), ctx.getStart());
}
// ==================== Private Helper ====================
private TracePointTree primaryBaseTrace(QLParser.PrimaryContext ctx) {
QLParser.PrimaryNoFixNonPathableContext primaryNoFixNonPathableContext = ctx.primaryNoFixNonPathable();
if (primaryNoFixNonPathableContext != null) {
return primaryNoFixNonPathableContext.accept(this);
}
QLParser.PrimaryNoFixPathableContext primaryNoFixPathableContext = ctx.primaryNoFixPathable();
List<QLParser.PathPartContext> pathPartContexts = ctx.pathPart();
TracePointTree leftChildTree = null;
int start = 0;
if (primaryNoFixPathableContext instanceof QLParser.VarIdExprContext && !pathPartContexts.isEmpty()
&& pathPartContexts.get(0) instanceof QLParser.CallExprContext) {
// function call
QLParser.VarIdExprContext functionNameContext = (QLParser.VarIdExprContext)primaryNoFixPathableContext;
QLParser.CallExprContext callExprContext = (QLParser.CallExprContext)pathPartContexts.get(0);
leftChildTree = newPoint(TraceType.FUNCTION,
traceArgumentList(callExprContext.argumentList()),
functionNameContext.getStart());
start = 1;
}
else {
leftChildTree = primaryNoFixPathableContext.accept(this);
}
return pathParts(leftChildTree, pathPartContexts.subList(start, pathPartContexts.size()));
}
private TracePointTree pathParts(TracePointTree pathRoot, List<QLParser.PathPartContext> pathPartContexts) {
TracePointTree leftChildTree = pathRoot;
for (QLParser.PathPartContext current : pathPartContexts) {
if (current instanceof QLParser.MethodInvokeContext
|| current instanceof QLParser.OptionalMethodInvokeContext
|| current instanceof QLParser.SpreadMethodInvokeContext) {
QLParser.ArgumentListContext argumentList = current.getChild(QLParser.ArgumentListContext.class, 0);
List<TracePointTree> argumentsChildren = traceArgumentList(argumentList);
List<TracePointTree> methodChildren = new ArrayList<>(1 + argumentsChildren.size());
methodChildren.add(leftChildTree);
methodChildren.addAll(argumentsChildren);
Token keyToken = current.getChild(QLParser.VarIdContext.class, 0).getStart();
leftChildTree = newPoint(TraceType.METHOD, methodChildren, keyToken);
}
else if (current instanceof QLParser.CallExprContext) {
QLParser.ArgumentListContext argumentList = current.getChild(QLParser.ArgumentListContext.class, 0);
List<TracePointTree> argumentsChildren = traceArgumentList(argumentList);
List<TracePointTree> callChildren = new ArrayList<>(1 + argumentsChildren.size());
callChildren.add(leftChildTree);
callChildren.addAll(argumentsChildren);
leftChildTree = newPoint(TraceType.OPERATOR, callChildren, current.getStart());
}
else if (current instanceof QLParser.IndexExprContext) {
QLParser.IndexValueExprContext indexValueExprContext =
current.getChild(QLParser.IndexValueExprContext.class, 0);
List<TracePointTree> indexArgChildren =
indexValueExprContext.getRuleContexts(QLParser.ExpressionContext.class)
.stream()
.map(expression -> expression.accept(this))
.collect(Collectors.toList());
List<TracePointTree> indexChildren = new ArrayList<>(1 + indexArgChildren.size());
indexChildren.add(leftChildTree);
indexChildren.addAll(indexArgChildren);
leftChildTree = newPoint(TraceType.OPERATOR, indexChildren, current.getStart());
}
else {
// field
leftChildTree = newPoint(TraceType.FIELD, Collections.singletonList(leftChildTree), current.getStop());
}
}
return leftChildTree;
}
private List<TracePointTree> traceArgumentList(QLParser.ArgumentListContext argumentListContext) {
if (argumentListContext == null || argumentListContext.isEmpty()) {
return Collections.emptyList();
}
return argumentListContext.expression()
.stream()
.map(expression -> expression.accept(this))
.collect(Collectors.toList());
}
private TracePointTree newPoint(TraceType traceType, List<TracePointTree> children, Token keyToken) {
return new TracePointTree(traceType, keyToken.getText(), children, keyToken.getLine(),
keyToken.getCharPositionInLine(), keyToken.getStartIndex());
}
private TracePointTree newPoint(TraceType traceType, List<TracePointTree> children, String text, Token keyToken) {
return new TracePointTree(traceType, text, children, keyToken.getLine(), keyToken.getCharPositionInLine(),
keyToken.getStartIndex());
}
}

View File

@@ -0,0 +1,31 @@
package com.alibaba.qlexpress4.aparser.compiletimefunction;
import com.alibaba.qlexpress4.aparser.QLParser;
import com.alibaba.qlexpress4.exception.ErrorReporter;
import com.alibaba.qlexpress4.exception.QLSyntaxException;
import com.alibaba.qlexpress4.runtime.QLambdaDefinition;
import com.alibaba.qlexpress4.runtime.QLambdaDefinitionInner;
import com.alibaba.qlexpress4.runtime.instruction.QLInstruction;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.tree.ParseTree;
import java.util.List;
/**
* Author: DQinYuan
*/
public interface CodeGenerator {
void addInstruction(QLInstruction qlInstruction);
void addInstructionsByTree(ParseTree tree);
QLSyntaxException reportParseErr(String errCode, String errReason);
QLambdaDefinition generateLambdaDefinition(QLParser.ExpressionContext expressionContext,
List<QLambdaDefinitionInner.Param> params);
ErrorReporter getErrorReporter();
ErrorReporter newReporterWithToken(Token token);
}

View File

@@ -0,0 +1,23 @@
package com.alibaba.qlexpress4.aparser.compiletimefunction;
import com.alibaba.qlexpress4.aparser.OperatorFactory;
import com.alibaba.qlexpress4.aparser.QLParser;
import java.util.List;
/**
* Author: DQinYuan
*/
public interface CompileTimeFunction {
/**
* create instructions for function in compile time
* @param functionName function name
* @param arguments arguments syntax tree
* @param operatorFactory operator factory
* @param codeGenerator tool for code generate
*/
void createFunctionInstruction(String functionName, List<QLParser.ExpressionContext> arguments,
OperatorFactory operatorFactory, CodeGenerator codeGenerator);
}

View File

@@ -0,0 +1,27 @@
package com.alibaba.qlexpress4.api;
import java.util.ArrayList;
import java.util.List;
/**
* Author: DQinYuan
*/
public class BatchAddFunctionResult {
private final List<String> succ;
private final List<String> fail;
public BatchAddFunctionResult() {
this.succ = new ArrayList<>();
this.fail = new ArrayList<>();
}
public List<String> getSucc() {
return succ;
}
public List<String> getFail() {
return fail;
}
}

View File

@@ -0,0 +1,9 @@
package com.alibaba.qlexpress4.api;
/**
* Author: TaoKan
*/
@FunctionalInterface
public interface QLFunctionalVarargs {
Object call(Object... params);
}

View File

@@ -0,0 +1,8 @@
package com.alibaba.qlexpress4.enums;
/**
* Author: TaoKan
*/
public enum AccessMode {
WRITE, READ,
}

View File

@@ -0,0 +1,37 @@
package com.alibaba.qlexpress4.exception;
/**
* Author: DQinYuan
*/
public class DefaultErrReporter implements ErrorReporter {
private final String script;
private final int tokenStartPos;
private final int line;
private final int col;
private final String lexeme;
public DefaultErrReporter(String script, int tokenStartPos, int line, int col, String lexeme) {
this.script = script;
this.tokenStartPos = tokenStartPos;
this.line = line;
this.col = col;
this.lexeme = lexeme;
}
@Override
public QLRuntimeException reportFormatWithCatch(Object catchObj, String errorCode, String format, Object... args) {
return QLException.reportRuntimeErrWithAttach(script,
tokenStartPos,
line,
col,
lexeme,
errorCode,
String.format(format, args),
catchObj);
}
}

View File

@@ -0,0 +1,18 @@
package com.alibaba.qlexpress4.exception;
public interface ErrorReporter {
default QLRuntimeException report(Object catchObj, String errorCode, String reason) {
return reportFormatWithCatch(catchObj, errorCode, reason);
}
default QLRuntimeException report(String errorCode, String reason) {
return reportFormatWithCatch(null, errorCode, reason);
}
default QLRuntimeException reportFormat(String errorCode, String format, Object... args) {
return reportFormatWithCatch(null, errorCode, format, args);
}
QLRuntimeException reportFormatWithCatch(Object catchObj, String errorCode, String format, Object... args);
}

View File

@@ -0,0 +1,67 @@
package com.alibaba.qlexpress4.exception;
import java.text.MessageFormat;
/**
* Author: DQinYuan
*/
public class ExMessageUtil {
private static final String REPORT_TEMPLATE = "[Error {0}: {1}]\n[Near: {2}]\n{3}\n[Line: {4}, Column: {5}]";
private static final int SNIPPET_EXTENSION_LEN = 20;
public static class ExMessage {
private final String message;
private final String snippet;
public ExMessage(String message, String snippet) {
this.message = message;
this.snippet = snippet;
}
public String getMessage() {
return message;
}
public String getSnippet() {
return snippet;
}
}
public static ExMessage format(String script, int tokenStartPos, int tokenLine, int tokenCol, String lexeme,
String errorCode, String reason) {
int startReportPos = Math.max(tokenStartPos - SNIPPET_EXTENSION_LEN, 0);
int endReportPos = Math.min(tokenStartPos + lexeme.length() + SNIPPET_EXTENSION_LEN, script.length());
StringBuilder snippetBuilder = new StringBuilder();
if (startReportPos > 0) {
snippetBuilder.append("...");
}
for (int i = startReportPos; i < endReportPos; i++) {
char codeChar = script.charAt(i);
snippetBuilder.append(codeChar < ' ' ? ' ' : codeChar);
}
if (endReportPos < script.length()) {
snippetBuilder.append("...");
}
StringBuilder carteBuilder = new StringBuilder().append(" ");
if (startReportPos > 0) {
carteBuilder.append(" ");
}
for (int i = startReportPos; i < tokenStartPos; i++) {
carteBuilder.append(' ');
}
for (int i = 0; i < lexeme.length(); i++) {
carteBuilder.append('^');
}
String snippet = snippetBuilder.toString();
String message = MessageFormat
.format(REPORT_TEMPLATE, errorCode, reason, snippet, carteBuilder.toString(), tokenLine, tokenCol);
return new ExMessage(message, snippet);
}
}

View File

@@ -0,0 +1,11 @@
package com.alibaba.qlexpress4.exception;
/**
* Author: DQinYuan
*/
public interface ExceptionFactory<T> {
T newException(String message, int lineNo, int colNo, String errLexeme, String errorCode, String reason,
String snippet);
}

View File

@@ -0,0 +1,17 @@
package com.alibaba.qlexpress4.exception;
/**
* Author: DQinYuan
*/
public class PureErrReporter implements ErrorReporter {
public static PureErrReporter INSTANCE = new PureErrReporter();
private PureErrReporter() {
}
@Override
public QLRuntimeException reportFormatWithCatch(Object catchObj, String errorCode, String format, Object... args) {
return new QLRuntimeException(null, String.format(format, args), errorCode);
}
}

View File

@@ -0,0 +1,83 @@
package com.alibaba.qlexpress4.exception;
/**
* Author: DQinYuan
*/
public enum QLErrorCodes {
// @formatter:off
// syntax error
SYNTAX_ERROR(""),
MISSING_INDEX("missing index expression"),
INVALID_NUMBER("invalid number"),
CLASS_NOT_FOUND("can not find class: %s"),
// runtime error
INVALID_INDEX("index can only be number"),
INDEX_OUT_BOUND("index out of bound"),
NONINDEXABLE_OBJECT("object of class %s is not indexable"),
NONTRAVERSABLE_OBJECT("object of class %s is not traversable"),
NULL_FIELD_ACCESS("can not access field from null"),
NULL_METHOD_ACCESS("can not access method from null"),
FIELD_NOT_FOUND("'%s' field not found"),
SET_FIELD_UNKNOWN_ERROR("unknown error when setting field '%s' value"),
GET_FIELD_UNKNOWN_ERROR("unknown error when getting field '%s' value"),
INVOKE_METHOD_WITH_WRONG_ARGUMENTS("invoke method '%s' with wrong arguments"),
INVOKE_METHOD_INNER_ERROR("exception from inner when invoking method '%s'"),
INVOKE_METHOD_UNKNOWN_ERROR("unknown error when invoking method '%s'"),
INVOKE_FUNCTION_INNER_ERROR("exception from inner when invoking function '%s', error message: %s"),
FUNCTION_NOT_FOUND("function '%s' not found"),
FUNCTION_TYPE_MISMATCH("symbol '%s' is not a function type"),
INVOKE_LAMBDA_ERROR("error when invoking lambda"),
NULL_CALL("can not call null"),
OBJECT_NOT_CALLABLE("type '%s' is not callable"),
METHOD_NOT_FOUND("no suitable method '%s' found for args %s"),
INVOKE_CONSTRUCTOR_UNKNOWN_ERROR("unknown error when invoking constructor"),
INVOKE_CONSTRUCTOR_INNER_ERROR("exception from inner when invoking constructor"),
NO_SUITABLE_CONSTRUCTOR("no suitable constructor for types %s"),
EXECUTE_BLOCK_ERROR("error when executing block"),
INCOMPATIBLE_TYPE_CAST("incompatible cast from type: %s to type: %s"),
INVALID_CAST_TARGET("target for type cast must be a class, but accept %s"),
SCRIPT_TIME_OUT("script exceeds timeout milliseconds, which is %d ms"),
INCOMPATIBLE_ASSIGNMENT_TYPE("variable declared type %s, assigned with incompatible value type %s"),
FOR_EACH_ITERABLE_REQUIRED("for-each can only be applied to iterable"),
FOR_EACH_TYPE_MISMATCH("for-each type mismatch, required %s, but %s provided"),
FOR_EACH_UNKNOWN_ERROR("unknown error when executing for-each"),
FOR_INIT_ERROR("error when executing for init"),
FOR_BODY_ERROR("error when executing for body"),
FOR_UPDATE_ERROR("error when executing for update"),
FOR_CONDITION_ERROR("error when executing for condition"),
FOR_CONDITION_BOOL_REQUIRED("result of for condition must be bool"),
WHILE_CONDITION_BOOL_REQUIRED("result of while condition must be bool"),
WHILE_CONDITION_ERROR("error when executing while condition"),
CONDITION_BOOL_REQUIRED("result of condition expression must be bool"),
ARRAY_SIZE_NUM_REQUIRED("size of array must be number"),
EXCEED_MAX_ARR_LENGTH("array length %d, exceed max allowed length %d"),
INCOMPATIBLE_ARRAY_ITEM_TYPE("item %d with type %s incompatible with array type %s"),
INVALID_ASSIGNMENT("value %s is not assignable"),
EXECUTE_OPERATOR_EXCEPTION("exception when executing '%s %s %s'"),
INVALID_ARITHMETIC(""),
INVALID_BINARY_OPERAND("the '%s' operator can not be applied to leftType:%s with leftValue:%s and rightType:%s with rightValue:%s"),
INVALID_UNARY_OPERAND("the '%s' operator can not be applied to type %s with value %s"),
EXECUTE_FINAL_BLOCK_ERROR("error when executing final block in try...catch...final..."),
EXECUTE_TRY_BLOCK_ERROR("error when executing try... block"),
EXECUTE_CATCH_HANDLER_ERROR("error when executing handler of '%s'"),
// operator restriction error
OPERATOR_NOT_ALLOWED("Script uses disallowed operator: %s"),
// user defined exception
INVALID_ARGUMENT(""),
BIZ_EXCEPTION(""),
QL_THROW("qlexpress throw statement");
// @formatter:on
private final String errorMsg;
QLErrorCodes(String errorMsg) {
this.errorMsg = errorMsg;
}
public String getErrorMsg() {
return errorMsg;
}
}

View File

@@ -0,0 +1,79 @@
package com.alibaba.qlexpress4.exception;
import com.alibaba.qlexpress4.exception.lsp.Diagnostic;
import com.alibaba.qlexpress4.exception.lsp.Position;
import com.alibaba.qlexpress4.exception.lsp.Range;
public class QLException extends RuntimeException {
private final Diagnostic diagnostic;
protected QLException(String message, Diagnostic diagnostic) {
super(message);
this.diagnostic = diagnostic;
}
public Diagnostic getDiagnostic() {
return diagnostic;
}
public int getPos() {
return diagnostic.getPos();
}
public String getReason() {
return diagnostic.getMessage();
}
/**
* @return line no based 1
*/
public int getLineNo() {
return diagnostic.getRange().getStart().getLine() + 1;
}
/**
* @return col no based 1
*/
public int getColNo() {
return diagnostic.getRange().getStart().getCharacter() + 1;
}
public String getErrLexeme() {
return diagnostic.getLexeme();
}
public String getErrorCode() {
return diagnostic.getCode();
}
public static QLSyntaxException reportScannerErr(String script, int tokenStartPos, int line, int col, String lexeme,
String errorCode, String reason) {
ExMessageUtil.ExMessage exMessage =
ExMessageUtil.format(script, tokenStartPos, line, col, lexeme, errorCode, reason);
Diagnostic diagnostic =
toDiagnostic(tokenStartPos, line, col, lexeme, errorCode, reason, exMessage.getSnippet());
return new QLSyntaxException(exMessage.getMessage(), diagnostic);
}
public static QLRuntimeException reportRuntimeErrWithAttach(String script, int tokenStartPos, int line, int col,
String lexeme, String errorCode, String reason, Object catchObj) {
ExMessageUtil.ExMessage exMessage =
ExMessageUtil.format(script, tokenStartPos, line, col, lexeme, errorCode, reason);
Diagnostic diagnostic =
toDiagnostic(tokenStartPos, line, col, lexeme, errorCode, reason, exMessage.getSnippet());
return errorCode.equals(QLErrorCodes.SCRIPT_TIME_OUT.name())
? new QLTimeoutException(catchObj, exMessage.getMessage(), diagnostic)
: new QLRuntimeException(catchObj, exMessage.getMessage(), diagnostic);
}
private static Diagnostic toDiagnostic(int startPos, int line, int col, String lexeme, String errorCode,
String reason, String snippet) {
int zeroBasedLine = line - 1;
int zeroBasedCol = col - 1;
Position start = new Position(zeroBasedLine, zeroBasedCol);
Position end = new Position(zeroBasedLine, zeroBasedCol + lexeme.length());
Range range = new Range(start, end);
return new Diagnostic(startPos, range, lexeme, errorCode, reason, snippet);
}
}

View File

@@ -0,0 +1,32 @@
package com.alibaba.qlexpress4.exception;
import com.alibaba.qlexpress4.exception.lsp.Diagnostic;
import com.alibaba.qlexpress4.exception.lsp.Range;
public class QLRuntimeException extends QLException {
/**
* catchObj can be catched at QLExpress catch clause
*/
private final Object catchObj;
/*
* Visible for test
*/
protected QLRuntimeException(Object catchObj, String reason, String errorCode) {
super("", new Diagnostic(0, new Range(null, null), "", errorCode, reason, ""));
this.catchObj = catchObj;
}
protected QLRuntimeException(Object catchObj, String message, Diagnostic diagnostic) {
super(message, diagnostic);
this.catchObj = catchObj;
if (catchObj instanceof Throwable) {
super.initCause((Throwable)catchObj);
}
}
public Object getCatchObj() {
return catchObj;
}
}

View File

@@ -0,0 +1,12 @@
package com.alibaba.qlexpress4.exception;
import com.alibaba.qlexpress4.exception.lsp.Diagnostic;
/**
* Author: DQinYuan
*/
public class QLSyntaxException extends QLException {
protected QLSyntaxException(String message, Diagnostic diagnostic) {
super(message, diagnostic);
}
}

View File

@@ -0,0 +1,13 @@
package com.alibaba.qlexpress4.exception;
import com.alibaba.qlexpress4.exception.lsp.Diagnostic;
public class QLTimeoutException extends QLRuntimeException {
protected QLTimeoutException(Object catchObj, String reason, String errorCode) {
super(catchObj, reason, errorCode);
}
protected QLTimeoutException(Object catchObj, String message, Diagnostic diagnostic) {
super(catchObj, message, diagnostic);
}
}

View File

@@ -0,0 +1,27 @@
package com.alibaba.qlexpress4.exception;
/**
* user define error message for custom function/operator
* Author: DQinYuan
*/
public class UserDefineException extends Exception {
public enum ExceptionType {
INVALID_ARGUMENT, BIZ_EXCEPTION
};
private final ExceptionType type;
public UserDefineException(String message) {
this(ExceptionType.BIZ_EXCEPTION, message);
}
public UserDefineException(ExceptionType type, String message) {
super(message);
this.type = type;
}
public ExceptionType getType() {
return type;
}
}

View File

@@ -0,0 +1,70 @@
package com.alibaba.qlexpress4.exception.lsp;
/**
* Author: DQinYuan
*/
public class Diagnostic {
/**
* start position in script
*/
private final int pos;
/**
* The range at which the message applies.
*/
private final Range range;
/**
* The diagnostic's code, which might appear in the user interface.
*/
private final String code;
/**
* The diagnostic's message.
*/
private final String message;
/**
* snippet near error position
*/
private final String snippet;
/**
* lexeme in range
*/
private final String lexeme;
public Diagnostic(int pos, Range range, String lexeme, String code, String message, String snippet) {
this.pos = pos;
this.range = range;
this.lexeme = lexeme;
this.code = code;
this.message = message;
this.snippet = snippet;
}
public int getPos() {
return pos;
}
public Range getRange() {
return range;
}
public String getLexeme() {
return lexeme;
}
public String getCode() {
return code;
}
public String getMessage() {
return message;
}
public String getSnippet() {
return snippet;
}
}

View File

@@ -0,0 +1,32 @@
package com.alibaba.qlexpress4.exception.lsp;
/**
* Author: DQinYuan
*/
public class Position {
/**
* Line position in a document (zero-based).
*/
private final int line;
/**
* Character offset on a line in a document (zero-based).
* If the character value is greater than the line length it defaults back
* to the line length.
*/
private final int character;
public Position(int line, int character) {
this.line = line;
this.character = character;
}
public int getLine() {
return line;
}
public int getCharacter() {
return character;
}
}

View File

@@ -0,0 +1,24 @@
package com.alibaba.qlexpress4.exception.lsp;
/**
* Author: DQinYuan
*/
public class Range {
private final Position start;
private final Position end;
public Range(Position start, Position end) {
this.start = start;
this.end = end;
}
public Position getStart() {
return start;
}
public Position getEnd() {
return end;
}
}

View File

@@ -0,0 +1,45 @@
package com.alibaba.qlexpress4.member;
import com.alibaba.qlexpress4.annotation.QLAlias;
import com.alibaba.qlexpress4.utils.QLAliasUtils;
import java.lang.reflect.Field;
/**
* Author: TaoKan
*/
public class FieldHandler {
public static class Preferred {
public static String preHandleAlias(Class<?> baseClass, String propertyName) {
Field[] fields = baseClass.getDeclaredFields();
for (Field field : fields) {
if (QLAliasUtils.matchQLAlias(propertyName, field.getAnnotationsByType(QLAlias.class))) {
return field.getName();
}
}
Class<?> superclass = baseClass.getSuperclass();
if (superclass != null) {
return preHandleAlias(superclass, propertyName);
}
return propertyName;
}
public static Field gatherFieldRecursive(Class<?> baseClass, String propertyName) {
Field[] fields = baseClass.getDeclaredFields();
for (Field field : fields) {
if (propertyName.equals(field.getName())) {
return field;
}
if (QLAliasUtils.matchQLAlias(propertyName, field.getAnnotationsByType(QLAlias.class))) {
return field;
}
}
Class<?> superclass = baseClass.getSuperclass();
if (superclass != null) {
return gatherFieldRecursive(superclass, propertyName);
}
return null;
}
}
}

View File

@@ -0,0 +1,106 @@
package com.alibaba.qlexpress4.member;
import com.alibaba.qlexpress4.runtime.IMethod;
import com.alibaba.qlexpress4.utils.BasicUtil;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
/**
* Author: TaoKan
*/
public class MethodHandler {
public static Method getGetter(Class<?> clazz, String property) {
String isGet = BasicUtil.getIsGetter(property);
String getter = BasicUtil.getGetter(property);
GetterCandidateMethod mGetCandidate = null;
for (Method method : clazz.getMethods()) {
if ((isGet.equals(method.getName())) && method.getReturnType() == boolean.class
&& BasicUtil.isPublic(method) && method.getParameterTypes().length == 0) {
GetterCandidateMethod isGetMethod = new GetterCandidateMethod(method, 2);
if (isPreferredGetter(mGetCandidate, isGetMethod)) {
mGetCandidate = isGetMethod;
}
}
else if (getter.equals(method.getName()) && BasicUtil.isPublic(method)
&& method.getParameterTypes().length == 0) {
GetterCandidateMethod getterMethod = new GetterCandidateMethod(method, 1);
if (isPreferredGetter(mGetCandidate, getterMethod)) {
mGetCandidate = getterMethod;
}
}
}
return mGetCandidate == null ? null : mGetCandidate.getMethod();
}
public static boolean isPreferredGetter(GetterCandidateMethod before, GetterCandidateMethod after) {
if (before == null) {
return true;
}
return after.getPriority() >= before.getPriority();
}
public static Method getSetter(Class<?> clazz, String property) {
property = BasicUtil.getSetter(property);
for (Method method : clazz.getMethods()) {
if (BasicUtil.isPublic(method) && method.getParameterTypes().length == 1
&& property.equals(method.getName())) {
return method;
}
}
return null;
}
public static boolean hasOnlyOneAbstractMethod(Method[] methods) {
int count = 0;
for (Method method : methods) {
if (Modifier.isAbstract(method.getModifiers())) {
count++;
if (count > 1) {
return false;
}
}
}
return count == 1;
}
public static class Access {
public static Object accessMethodValue(IMethod method, Object bean, Object[] args)
throws IllegalArgumentException, InvocationTargetException, IllegalAccessException {
if (!method.isAccess()) {
method.setAccessible(true);
}
return method.invoke(bean, args);
}
}
static class GetterCandidateMethod {
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
private Method method;
public int getPriority() {
return priority;
}
public void setPriority(int priority) {
this.priority = priority;
}
private int priority;
public GetterCandidateMethod(Method method, int priority) {
this.method = method;
this.priority = priority;
}
}
}

View File

@@ -0,0 +1,34 @@
package com.alibaba.qlexpress4.operator;
import java.util.Collections;
import java.util.Set;
/**
* Blacklist strategy that forbids specified operators
* If blackOperators is empty, all operators are allowed
*
* @author QLExpress Team
*/
public class BlackOperatorCheckStrategy implements OperatorCheckStrategy {
private final Set<String> blackOperators;
public BlackOperatorCheckStrategy(Set<String> blackOperators) {
if (blackOperators == null) {
this.blackOperators = Collections.emptySet();
}
else {
this.blackOperators = Collections.unmodifiableSet(blackOperators);
}
}
@Override
public boolean isAllowed(String operator) {
return !blackOperators.contains(operator);
}
@Override
public Set<String> getOperators() {
return blackOperators;
}
}

View File

@@ -0,0 +1,26 @@
package com.alibaba.qlexpress4.operator;
import java.util.Collections;
import java.util.Set;
/**
* @author zhoutao
*/
public class DefaultOperatorCheckStrategy implements OperatorCheckStrategy {
private static final DefaultOperatorCheckStrategy INSTANCE = new DefaultOperatorCheckStrategy();
public static DefaultOperatorCheckStrategy getInstance() {
return INSTANCE;
}
@Override
public boolean isAllowed(String operator) {
return true;
}
@Override
public Set<String> getOperators() {
return Collections.emptySet();
}
}

View File

@@ -0,0 +1,51 @@
package com.alibaba.qlexpress4.operator;
import java.util.Set;
/**
* Operator restriction strategy interface.
* Defines the contract for checking whether an operator is allowed.
*
* Supported operators (organized by category):
* - arithmetic: {@code +, -, *, /, %, mod, +=, -=, *=, /=, %=}
* - assign: {@code =}
* - bit: {@code &, |, ^, ~, <<, >>, >>>, &=, |=, ^=, <<=, >>=, >>>=}
* - collection: {@code in, not_in}
* - compare: {@code ==, !=, <>, <, <=, >, >=}
* - logic: {@code &&, ||, !, and, or}
* - string: {@code like, not_like}
* - unary: {@code ++, --, +, - (unary)}
* - root: {@code instanceof}
*
* Usage example:
* <pre>{@code
* // Whitelist strategy - only allow + and *
* Set<String> allowed = new HashSet<>(Arrays.asList("+", "*"));
* OperatorCheckStrategy strategy = OperatorCheckStrategy.whitelist(allowed);
*
* // Blacklist strategy - forbid assignment operator
* Set<String> forbidden = new HashSet<>(Arrays.asList("="));
* OperatorCheckStrategy strategy = OperatorCheckStrategy.blacklist(forbidden);
*
* // Allow all operators
* OperatorCheckStrategy strategy = OperatorCheckStrategy.allowAll();
* }</pre>
*/
public interface OperatorCheckStrategy {
static OperatorCheckStrategy allowAll() {
return DefaultOperatorCheckStrategy.getInstance();
}
static OperatorCheckStrategy whitelist(Set<String> allowedOperators) {
return new WhiteOperatorCheckStrategy(allowedOperators);
}
static OperatorCheckStrategy blacklist(Set<String> forbiddenOperators) {
return new BlackOperatorCheckStrategy(forbiddenOperators);
}
boolean isAllowed(String operator);
Set<String> getOperators();
}

View File

@@ -0,0 +1,34 @@
package com.alibaba.qlexpress4.operator;
import java.util.Collections;
import java.util.Set;
/**
* Whitelist strategy that only allows specified operators
* If allowedOperators is empty, no operators are allowed
*
* @author QLExpress Team
*/
public class WhiteOperatorCheckStrategy implements OperatorCheckStrategy {
private final Set<String> allowedOperators;
public WhiteOperatorCheckStrategy(Set<String> allowedOperators) {
if (allowedOperators == null) {
this.allowedOperators = Collections.emptySet();
}
else {
this.allowedOperators = Collections.unmodifiableSet(allowedOperators);
}
}
@Override
public boolean isAllowed(String operator) {
return allowedOperators.contains(operator);
}
@Override
public Set<String> getOperators() {
return allowedOperators;
}
}

View File

@@ -0,0 +1,26 @@
package com.alibaba.qlexpress4.proxy;
import com.alibaba.qlexpress4.runtime.QLambda;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
/**
* Author: TaoKan
*/
public class QLambdaInvocationHandler implements InvocationHandler {
private final QLambda qLambda;
public QLambdaInvocationHandler(QLambda qLambda) {
this.qLambda = qLambda;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
return Modifier.isAbstract(method.getModifiers()) ? qLambda.call(args).getResult().get()
: method.getReturnType() == String.class && "toString".equals(method.getName()) ? "QLambdaProxy"
: method.invoke(args);
}
}

View File

@@ -0,0 +1,113 @@
package com.alibaba.qlexpress4.runtime;
import com.alibaba.qlexpress4.runtime.function.CustomFunction;
import com.alibaba.qlexpress4.runtime.scope.QScope;
import com.alibaba.qlexpress4.runtime.trace.QTraces;
import java.util.Collections;
import java.util.Map;
/**
* Author: DQinYuan
*/
public class DelegateQContext implements QContext {
private final QRuntime qRuntime;
private QScope qScope;
public DelegateQContext(QRuntime qRuntime, QScope qScope) {
this.qRuntime = qRuntime;
this.qScope = qScope;
}
@Override
public long scriptStartTimeStamp() {
return qRuntime.scriptStartTimeStamp();
}
@Override
public Map<String, Object> attachment() {
return qRuntime.attachment();
}
@Override
public ReflectLoader getReflectLoader() {
return qRuntime.getReflectLoader();
}
@Override
public QTraces getTraces() {
return qRuntime.getTraces();
}
@Override
public Value getSymbol(String varName) {
return qScope.getSymbol(varName);
}
@Override
public Object getSymbolValue(String varName) {
return qScope.getSymbolValue(varName);
}
@Override
public void defineLocalSymbol(String varName, Class<?> varClz, Object value) {
qScope.defineLocalSymbol(varName, varClz, value);
}
@Override
public void defineFunction(String functionName, CustomFunction function) {
qScope.defineFunction(functionName, function);
}
@Override
public CustomFunction getFunction(String functionName) {
return qScope.getFunction(functionName);
}
@Override
public Map<String, CustomFunction> getFunctionTable() {
return qScope.getFunctionTable();
}
@Override
public void push(Value value) {
qScope.push(value);
}
@Override
public Parameters pop(int number) {
return qScope.pop(number);
}
@Override
public Value pop() {
return qScope.pop();
}
@Override
public Value peek() {
return qScope.peek();
}
@Override
public QScope getParent() {
return qScope.getParent();
}
@Override
public QScope getCurrentScope() {
return qScope;
}
@Override
public QScope newScope() {
return qScope = qScope.newScope();
}
@Override
public void closeScope() {
qScope = qScope.getParent();
}
}

View File

@@ -0,0 +1,41 @@
package com.alibaba.qlexpress4.runtime;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* Author: DQinYuan
*/
public class ExceptionTable {
public static final ExceptionTable EMPTY = new ExceptionTable(Collections.emptyList(), null);
private final List<Map.Entry<Class<?>, Integer>> handlerPosMap;
/**
* nullable
*/
private final Integer finalPos;
public ExceptionTable(List<Map.Entry<Class<?>, Integer>> handlerPosMap, Integer finalPos) {
this.handlerPosMap = handlerPosMap;
this.finalPos = finalPos;
}
public Integer getRelativePos(Object throwObj) {
for (Map.Entry<Class<?>, Integer> classHandlerMap : handlerPosMap) {
if (throwObj == null) {
return classHandlerMap.getValue();
}
else if (classHandlerMap.getKey().isAssignableFrom(throwObj.getClass())) {
return classHandlerMap.getValue();
}
}
return null;
}
public Integer getFinalPos() {
return finalPos;
}
}

View File

@@ -0,0 +1,61 @@
package com.alibaba.qlexpress4.runtime;
/**
* Author: DQinYuan
*/
public class FixedSizeStack {
private final Value[] elements;
/**
* next element to push
*/
private int cursor = 0;
public FixedSizeStack(int size) {
this.elements = new Value[size];
}
public void push(Value ele) {
elements[cursor++] = ele;
}
public Value pop() {
return elements[--cursor];
}
public Value peak() {
return elements[cursor - 1];
}
public Parameters pop(int n) {
cursor -= n;
return new StackSwapParameters(elements, cursor, n);
}
private static class StackSwapParameters implements Parameters {
private final Value[] elements;
private final int start;
private final int length;
private StackSwapParameters(Value[] elements, int start, int length) {
this.elements = elements;
this.start = start;
this.length = length;
}
@Override
public Value get(int i) {
return i >= length ? null : elements[start + i];
}
@Override
public int size() {
return length;
}
}
}

View File

@@ -0,0 +1,24 @@
package com.alibaba.qlexpress4.runtime;
import java.lang.reflect.InvocationTargetException;
/**
* Author: DQinYuan
*/
public interface IMethod {
Class<?>[] getParameterTypes();
boolean isVarArgs();
boolean isAccess();
void setAccessible(boolean flag);
String getName();
Class<?> getDeclaringClass();
Object invoke(Object obj, Object[] args)
throws InvocationTargetException, IllegalAccessException;
}

View File

@@ -0,0 +1,56 @@
package com.alibaba.qlexpress4.runtime;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
/**
* Author: DQinYuan
*/
public class JvmIMethod implements IMethod {
private final Method method;
public JvmIMethod(Method method) {
this.method = method;
}
public Method getMethod() {
return method;
}
@Override
public Class<?>[] getParameterTypes() {
return method.getParameterTypes();
}
@Override
public boolean isVarArgs() {
return method.isVarArgs();
}
@Override
public boolean isAccess() {
return Modifier.isPublic(method.getDeclaringClass().getModifiers()) && Modifier.isPublic(method.getModifiers());
}
@Override
public void setAccessible(boolean flag) {
method.setAccessible(flag);
}
@Override
public String getName() {
return method.getName();
}
@Override
public Class<?> getDeclaringClass() {
return method.getDeclaringClass();
}
@Override
public Object invoke(Object obj, Object[] args)
throws InvocationTargetException, IllegalAccessException {
return method.invoke(obj, args);
}
}

View File

@@ -0,0 +1,40 @@
package com.alibaba.qlexpress4.runtime;
import com.alibaba.qlexpress4.exception.ErrorReporter;
import com.alibaba.qlexpress4.exception.QLErrorCodes;
import com.alibaba.qlexpress4.runtime.data.convert.ObjTypeConvertor;
/**
* assignable value
* <p>
* Author: DQinYuan
*/
public interface LeftValue extends Value {
Class<?> getDefinedType();
@Override
default Class<?> getType() {
Class<?> definedType = getDefinedType();
return definedType == null ? Value.super.getType() : definedType;
}
default void set(Object newValue, ErrorReporter errorReporter) {
Class<?> defineType = getDefinedType();
ObjTypeConvertor.QConverted result = ObjTypeConvertor.cast(newValue, defineType);
if (!result.isConvertible()) {
throw errorReporter.reportFormat(QLErrorCodes.INCOMPATIBLE_ASSIGNMENT_TYPE.name(),
QLErrorCodes.INCOMPATIBLE_ASSIGNMENT_TYPE.getErrorMsg(),
newValue == null ? "null" : newValue.getClass().getName(),
defineType.getName());
}
setInner(result.getConverted());
}
void setInner(Object newValue);
/**
* @return Nullable
*/
String getSymbolName();
}

View File

@@ -0,0 +1,242 @@
package com.alibaba.qlexpress4.runtime;
import com.alibaba.qlexpress4.annotation.QLAlias;
import com.alibaba.qlexpress4.utils.BasicUtil;
import com.alibaba.qlexpress4.utils.CacheUtil;
import com.alibaba.qlexpress4.utils.QLAliasUtils;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Author: DQinYuan
*/
public class MemberResolver {
public enum MatchPriority {
MISMATCH(-1),
// e.g. Integer -> Number
EXTEND(0),
// e.g. BigDecimal -> int
NUMBER_DEMOTION(9),
// e.g. int -> long
// 1 -> 8
NUMBER_PROMOTION(8),
// e.g. Integer -> int
UNBOX(9),
// e.g. QLambda -> Function, Runnable, ...
LAMBDA(10),
// e.g. Integer -> Integer
EQUAL(11);
public final int priority;
MatchPriority(int priority) {
this.priority = priority;
}
}
public static Constructor<?> resolveConstructor(Class<?> cls, Class<?>[] argTypes) {
Constructor<?>[] constructors = cls.getConstructors();
// simple match
Class<?>[][] candidates = new Class<?>[constructors.length][];
for (int i = 0; i < constructors.length; i++) {
candidates[i] = constructors[i].getParameterTypes();
}
Integer bestIndex = resolveBestMatch(candidates, argTypes);
if (bestIndex != null) {
return constructors[bestIndex];
}
// var args match
List<Class<?>[]> varArgsCandidates = new ArrayList<>(constructors.length);
List<Integer> varArgsConstructorI = new ArrayList<>(constructors.length);
for (int i = 0; i < constructors.length; i++) {
Constructor<?> constructor = constructors[i];
if (!constructor.isVarArgs()) {
continue;
}
varArgsCandidates.add(adapt2VarArgTypes(constructor.getParameterTypes(), argTypes.length));
varArgsConstructorI.add(i);
}
Integer varArgBestIndex = resolveBestMatch(varArgsCandidates.toArray(new Class[0][]), argTypes);
if (varArgBestIndex == null) {
return null;
}
return constructors[varArgsConstructorI.get(varArgBestIndex)];
}
public static IMethod resolveMethod(Class<?> cls, String methodName, Class<?>[] argTypes, boolean isStatic,
boolean allowPrivate) {
Class<?> curCls = cls;
List<Class<?>> inters = new ArrayList<>();
while (curCls != null) {
IMethod method = resolveDeclaredMethod(curCls, methodName, argTypes, isStatic, allowPrivate);
if (method != null) {
return method;
}
// collect interfaces implemented by current class to search default methods later
Class<?>[] curInters = curCls.getInterfaces();
inters.addAll(Arrays.asList(curInters));
curCls = curCls.getSuperclass();
}
// interface method
return resolveIntersMethod(inters.toArray(new Class[0]), methodName, argTypes, isStatic);
}
private static IMethod resolveIntersMethod(Class<?>[] inters, String methodName, Class<?>[] argTypes,
boolean isStatic) {
for (Class<?> inter : inters) {
IMethod method = resolveInterMethod(inter, methodName, argTypes, isStatic);
if (method != null) {
return method;
}
}
return null;
}
private static IMethod resolveInterMethod(Class<?> inter, String methodName, Class<?>[] argTypes,
boolean isStatic) {
// no private method in interface, so pass false to 'allowPrivate'
IMethod method = resolveDeclaredMethod(inter, methodName, argTypes, isStatic, false);
if (method != null) {
return method;
}
return resolveIntersMethod(inter.getInterfaces(), methodName, argTypes, isStatic);
}
public static int resolvePriority(Class<?>[] paramTypes, Class<?>[] argTypes) {
if (paramTypes.length != argTypes.length) {
return MatchPriority.MISMATCH.priority;
}
int methodPriority = MatchPriority.EQUAL.priority;
for (int i = 0; i < paramTypes.length; i++) {
Class<?> paramType = paramTypes[i];
Class<?> argType = argTypes[i];
int paramPriority = resolveArgPriority(paramType, argType);
if (paramPriority == MatchPriority.MISMATCH.priority) {
return paramPriority;
}
if (paramPriority < methodPriority) {
// methodPriority is min(paramPriority)
methodPriority = paramPriority;
}
}
return methodPriority;
}
private static IMethod resolveDeclaredMethod(Class<?> cls, String methodName, Class<?>[] argTypes, boolean isStatic,
boolean allowPrivate) {
return resolveMethod(getDeclaredMethod(cls, methodName, isStatic, allowPrivate), argTypes);
}
public static IMethod resolveMethod(List<? extends IMethod> methods, Class<?>[] argTypes) {
// simple match
Class<?>[][] candidates = new Class<?>[methods.size()][];
for (int i = 0; i < methods.size(); i++) {
IMethod declaredMethod = methods.get(i);
candidates[i] = declaredMethod.getParameterTypes();
}
Integer bestIndex = resolveBestMatch(candidates, argTypes);
if (bestIndex != null) {
return methods.get(bestIndex);
}
// var args match
List<Class<?>[]> varArgsCandidates = new ArrayList<>(methods.size());
List<Integer> varArgsMethodI = new ArrayList<>(methods.size());
for (int i = 0; i < methods.size(); i++) {
IMethod declaredMethod = methods.get(i);
if (!declaredMethod.isVarArgs()) {
continue;
}
varArgsCandidates.add(adapt2VarArgTypes(declaredMethod.getParameterTypes(), argTypes.length));
varArgsMethodI.add(i);
}
Integer varArgBestIndex = resolveBestMatch(varArgsCandidates.toArray(new Class[0][]), argTypes);
if (varArgBestIndex == null) {
return null;
}
return methods.get(varArgsMethodI.get(varArgBestIndex));
}
private static List<IMethod> getDeclaredMethod(Class<?> cls, String methodName, boolean isStatic,
boolean allowPrivate) {
Method[] declaredMethods = cls.getDeclaredMethods();
List<IMethod> result = new ArrayList<>(declaredMethods.length);
for (Method declaredMethod : declaredMethods) {
if (!methodName.equals(declaredMethod.getName())
&& !QLAliasUtils.matchQLAlias(methodName, declaredMethod.getAnnotationsByType(QLAlias.class))) {
continue;
}
if ((!isStatic || BasicUtil.isStatic(declaredMethod))
&& (allowPrivate || BasicUtil.isPublic(declaredMethod))) {
result.add(new JvmIMethod(declaredMethod));
}
}
return result;
}
private static Class<?>[] adapt2VarArgTypes(Class<?>[] parameterTypes, int argLength) {
Class<?> varItemType = parameterTypes[parameterTypes.length - 1].getComponentType();
Class<?>[] varParamTypes = new Class<?>[argLength];
System.arraycopy(parameterTypes, 0, varParamTypes, 0, parameterTypes.length - 1);
for (int i = parameterTypes.length - 1; i < argLength; i++) {
varParamTypes[i] = varItemType;
}
return varParamTypes;
}
public static Integer resolveBestMatch(Class<?>[][] candidates, Class<?>[] argTypes) {
Integer bestMatchIndex = null;
int bestPriority = MatchPriority.MISMATCH.priority;
for (int i = 0; i < candidates.length; i++) {
Class<?>[] candidate = candidates[i];
int priority = resolvePriority(candidate, argTypes);
if (priority > bestPriority) {
bestPriority = priority;
bestMatchIndex = i;
}
}
return bestMatchIndex;
}
private static int resolveArgPriority(Class<?> paramType, Class<?> argType) {
if (paramType == argType) {
return MatchPriority.EQUAL.priority;
}
if (CacheUtil.isFunctionInterface(paramType) && QLambda.class.isAssignableFrom(argType)) {
return MatchPriority.LAMBDA.priority;
}
Class<?> primitiveArgCls = argType.isPrimitive() ? argType : BasicUtil.transToPrimitive(argType);
Class<?> primitiveParamCls = paramType.isPrimitive() ? paramType : BasicUtil.transToPrimitive(paramType);
if (primitiveArgCls != null && primitiveArgCls == primitiveParamCls) {
return MatchPriority.UNBOX.priority;
}
Integer paramNumLevel = BasicUtil.numberPromoteLevel(paramType);
Integer argNumLevel = BasicUtil.numberPromoteLevel(argType);
if (paramNumLevel != null && argNumLevel != null) {
return paramNumLevel >= argNumLevel ? MatchPriority.NUMBER_PROMOTION.priority + argNumLevel - paramNumLevel
: MatchPriority.NUMBER_DEMOTION.priority;
}
// Handle primitive to Object boxing conversion
if (argType.isPrimitive() && paramType == Object.class) {
return MatchPriority.EXTEND.priority;
}
if (argType == Nothing.class || paramType.isAssignableFrom(argType)) {
return MatchPriority.EXTEND.priority;
}
return MatchPriority.MISMATCH.priority;
}
}

View File

@@ -0,0 +1,34 @@
package com.alibaba.qlexpress4.runtime;
import java.util.Objects;
/**
* Author: DQinYuan
*/
public class MetaClass {
private final Class<?> clz;
public MetaClass(Class<?> clz) {
this.clz = clz;
}
public Class<?> getClz() {
return clz;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
MetaClass metaClass = (MetaClass)o;
return clz.equals(metaClass.clz);
}
@Override
public int hashCode() {
return Objects.hash(clz);
}
}

View File

@@ -0,0 +1,12 @@
package com.alibaba.qlexpress4.runtime;
/**
* subclass of any object
*/
public class Nothing {
private Nothing() {
}
}

View File

@@ -0,0 +1,26 @@
package com.alibaba.qlexpress4.runtime;
/**
* Author: DQinYuan
*/
public interface Parameters {
default Object getValue(int i) {
Value boxedValue = get(i);
return boxedValue == null ? null : boxedValue.get();
}
/**
* get parameters in i position
*
* @param i index
* @return value in index, null if exceed parameters' length
*/
Value get(int i);
/**
* parameters size
* @return size
*/
int size();
}

View File

@@ -0,0 +1,13 @@
package com.alibaba.qlexpress4.runtime;
import com.alibaba.qlexpress4.runtime.scope.QScope;
/**
* Author: DQinYuan
*/
public interface QContext extends QScope, QRuntime {
QScope getCurrentScope();
void closeScope();
}

View File

@@ -0,0 +1,84 @@
package com.alibaba.qlexpress4.runtime;
import com.alibaba.qlexpress4.runtime.function.CustomFunction;
import java.util.Collections;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
public interface QLambda
extends Runnable, Supplier<Object>, Consumer<Object>, Predicate<Object>, Function<Object, Object> {
/**
* @param params params of lambda
* @return result of lambda
* @throws Throwable {@link com.alibaba.qlexpress4.exception.UserDefineException} for custom error message
*/
QResult call(Object... params)
throws Throwable;
/**
* Get functions defined inside this lambda when invoked with parameters.
*
* @param params parameters passed to the lambda
* @return function table defined by the lambda (empty by default)
* @throws Throwable if user code throws an exception while collecting functions
*/
default Map<String, CustomFunction> getFunctionDefined(Object... params)
throws Throwable {
return Collections.emptyMap();
}
@Override
default Object get() {
try {
return call().getResult().get();
}
catch (Throwable t) {
throw t instanceof RuntimeException ? (RuntimeException)t : new RuntimeException(t);
}
}
@Override
default void accept(Object o) {
try {
call(o);
}
catch (Throwable t) {
throw t instanceof RuntimeException ? (RuntimeException)t : new RuntimeException(t);
}
}
@Override
default void run() {
try {
call();
}
catch (Throwable t) {
throw t instanceof RuntimeException ? (RuntimeException)t : new RuntimeException(t);
}
}
@Override
default boolean test(Object o) {
try {
return (boolean)call(o).getResult().get();
}
catch (Throwable t) {
throw t instanceof RuntimeException ? (RuntimeException)t : new RuntimeException(t);
}
}
@Override
default Object apply(Object o) {
try {
return call(o).getResult().get();
}
catch (Throwable t) {
throw t instanceof RuntimeException ? (RuntimeException)t : new RuntimeException(t);
}
}
}

View File

@@ -0,0 +1,17 @@
package com.alibaba.qlexpress4.runtime;
import com.alibaba.qlexpress4.QLOptions;
import java.util.function.Consumer;
/**
* Author: DQinYuan
*/
public interface QLambdaDefinition {
QLambda toLambda(QContext qContext, QLOptions qlOptions, boolean newEnv);
void println(int depth, Consumer<String> debug);
String getName();
}

View File

@@ -0,0 +1,29 @@
package com.alibaba.qlexpress4.runtime;
import com.alibaba.qlexpress4.QLOptions;
import com.alibaba.qlexpress4.utils.PrintlnUtils;
import java.util.function.Consumer;
/**
* Author: DQinYuan
*/
public class QLambdaDefinitionEmpty implements QLambdaDefinition {
public static QLambdaDefinition INSTANCE = new QLambdaDefinitionEmpty();
@Override
public QLambda toLambda(QContext qContext, QLOptions qlOptions, boolean newEnv) {
return QLambdaEmpty.INSTANCE;
}
@Override
public void println(int depth, Consumer<String> debug) {
PrintlnUtils.printlnByCurDepth(depth, getName(), debug);
}
@Override
public String getName() {
return "EmptyLambdaDefinition";
}
}

View File

@@ -0,0 +1,89 @@
package com.alibaba.qlexpress4.runtime;
import com.alibaba.qlexpress4.QLOptions;
import com.alibaba.qlexpress4.runtime.instruction.CloseScopeInstruction;
import com.alibaba.qlexpress4.runtime.instruction.NewScopeInstruction;
import com.alibaba.qlexpress4.runtime.instruction.QLInstruction;
import java.util.List;
import java.util.function.Consumer;
/**
* Author: DQinYuan
*/
public class QLambdaDefinitionInner implements QLambdaDefinition {
/**
* function name
*/
private final String name;
private final QLInstruction[] instructions;
private final List<Param> paramsType;
private final int maxStackSize;
public QLambdaDefinitionInner(String name, List<QLInstruction> instructions, List<Param> paramsType,
int maxStackSize) {
this.name = name;
this.instructions = instructions.toArray(new QLInstruction[0]);
this.paramsType = paramsType;
this.maxStackSize = maxStackSize;
}
public QLambdaDefinitionInner(String name, QLInstruction[] instructions, List<Param> paramsType, int maxStackSize) {
this.name = name;
this.instructions = instructions;
this.paramsType = paramsType;
this.maxStackSize = maxStackSize;
}
@Override
public String getName() {
return name;
}
public QLInstruction[] getInstructions() {
return instructions;
}
public List<Param> getParamsType() {
return paramsType;
}
public int getMaxStackSize() {
return maxStackSize;
}
@Override
public QLambda toLambda(QContext qContext, QLOptions qlOptions, boolean newEnv) {
return new QLambdaInner(this, new DelegateQContext(qContext, qContext.getCurrentScope()), qlOptions, newEnv);
}
@Override
public void println(int depth, Consumer<String> debug) {
for (int i = 0; i < instructions.length; i++) {
instructions[i].println(i, depth, debug);
}
}
public static class Param {
private final String name;
private final Class<?> clazz;
public Param(String name, Class<?> clazz) {
this.name = name;
this.clazz = clazz;
}
public String getName() {
return name;
}
public Class<?> getClazz() {
return clazz;
}
}
}

View File

@@ -0,0 +1,21 @@
package com.alibaba.qlexpress4.runtime;
import com.alibaba.qlexpress4.runtime.function.CustomFunction;
import java.util.Collections;
import java.util.Map;
/**
* Author: DQinYuan
*/
public class QLambdaEmpty implements QLambda {
public static QLambda INSTANCE = new QLambdaEmpty();
@Override
public QResult call(Object... params)
throws Throwable {
return new QResult(Value.NULL_VALUE, QResult.ResultType.RETURN);
}
}

View File

@@ -0,0 +1,99 @@
package com.alibaba.qlexpress4.runtime;
import com.alibaba.qlexpress4.QLOptions;
import com.alibaba.qlexpress4.exception.UserDefineException;
import com.alibaba.qlexpress4.runtime.data.AssignableDataValue;
import com.alibaba.qlexpress4.runtime.data.convert.ObjTypeConvertor;
import com.alibaba.qlexpress4.runtime.function.CustomFunction;
import com.alibaba.qlexpress4.runtime.instruction.QLInstruction;
import com.alibaba.qlexpress4.runtime.scope.QvmBlockScope;
import java.text.MessageFormat;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Author: DQinYuan
*/
public class QLambdaInner implements QLambda {
private final QLambdaDefinitionInner lambdaDefinition;
private final QContext qContext;
private final QLOptions qlOptions;
private final boolean newEnv;
public QLambdaInner(QLambdaDefinitionInner lambdaDefinition, QContext qContext, QLOptions qlOptions,
boolean newEnv) {
this.lambdaDefinition = lambdaDefinition;
this.qContext = qContext;
this.qlOptions = qlOptions;
this.newEnv = newEnv;
}
public QResult call(Object... params)
throws Throwable {
return callInner(newEnv ? inheritScope(params) : qContext);
}
@Override
public Map<String, CustomFunction> getFunctionDefined(Object... params)
throws Throwable {
QContext newRuntime = newEnv ? inheritScope(params) : qContext;
callInner(newRuntime);
return newRuntime.getFunctionTable();
}
private QResult callInner(QContext runtime) {
QLInstruction[] instructions = lambdaDefinition.getInstructions();
for (int i = 0; i < instructions.length; i++) {
QResult qResult = instructions[i].execute(runtime, qlOptions);
switch (qResult.getResultType()) {
case JUMP:
i += (int)qResult.getResult().get();
continue;
case RETURN:
case BREAK:
case CONTINUE:
return qResult;
}
}
return QResult.NEXT_INSTRUCTION;
}
private QContext inheritScope(Object[] params)
throws UserDefineException {
Map<String, Value> initSymbolTable = new HashMap<>(params.length);
List<QLambdaDefinitionInner.Param> paramsDefinition = lambdaDefinition.getParamsType();
for (int i = 0; i < Math.min(params.length, paramsDefinition.size()); i++) {
QLambdaDefinitionInner.Param paramDefinition = paramsDefinition.get(i);
Object originParamI = params[i];
Class<?> targetCls = paramDefinition.getClazz();
ObjTypeConvertor.QConverted qlConvertResult = ObjTypeConvertor.cast(originParamI, targetCls);
if (!qlConvertResult.isConvertible()) {
throw new UserDefineException(UserDefineException.ExceptionType.INVALID_ARGUMENT,
MessageFormat.format(
"invalid argument at index {0} (start from 0), required type {1}, but {2} provided",
i,
targetCls.getName(),
originParamI == null ? "null" : originParamI.getClass().getName()));
}
initSymbolTable.put(paramDefinition.getName(),
new AssignableDataValue(paramDefinition.getName(), qlConvertResult.getConverted(), targetCls));
}
// null for rest params
for (int i = params.length; i < paramsDefinition.size(); i++) {
QLambdaDefinitionInner.Param paramDefinition = paramsDefinition.get(i);
initSymbolTable.put(paramDefinition.getName(),
new AssignableDataValue(paramDefinition.getName(), null, paramDefinition.getClazz()));
}
QvmBlockScope newScope =
new QvmBlockScope(qContext, initSymbolTable, lambdaDefinition.getMaxStackSize(), ExceptionTable.EMPTY);
return new DelegateQContext(qContext, newScope);
}
}

View File

@@ -0,0 +1,23 @@
package com.alibaba.qlexpress4.runtime;
import com.alibaba.qlexpress4.runtime.trace.QTraces;
public class QLambdaTrace {
private final QLambda qLambda;
private final QTraces traces;
public QLambdaTrace(QLambda qLambda, QTraces traces) {
this.qLambda = qLambda;
this.traces = traces;
}
public QLambda getqLambda() {
return qLambda;
}
public QTraces getTraces() {
return traces;
}
}

View File

@@ -0,0 +1,44 @@
package com.alibaba.qlexpress4.runtime;
/**
* Author: DQinYuan
*/
public class QResult {
public static final QResult LOOP_BREAK_RESULT = new QResult(Value.NULL_VALUE, ResultType.BREAK);
public static final QResult LOOP_CONTINUE_RESULT = new QResult(Value.NULL_VALUE, ResultType.CONTINUE);
public static final QResult NEXT_INSTRUCTION = new QResult(Value.NULL_VALUE, ResultType.NEXT_INSTRUCTION);
public enum ResultType {
// break
BREAK,
// without return, different with return null
CONTINUE,
// jump to other instruction position.
// in this case, result is Value of int, which is the position to be jumped
JUMP,
// return from function/lambda/script
RETURN,
// execute next instruction
NEXT_INSTRUCTION
}
public QResult(Value result, ResultType rType) {
this.result = result;
this.resultType = rType;
}
private final Value result;
private final ResultType resultType;
public Value getResult() {
return result;
}
public ResultType getResultType() {
return resultType;
}
}

View File

@@ -0,0 +1,23 @@
package com.alibaba.qlexpress4.runtime;
import com.alibaba.qlexpress4.runtime.trace.QTraces;
import java.util.Map;
/**
* Author: DQinYuan
*/
public interface QRuntime {
/**
* get script start time
* @return start time
*/
long scriptStartTimeStamp();
Map<String, Object> attachment();
ReflectLoader getReflectLoader();
QTraces getTraces();
}

View File

@@ -0,0 +1,105 @@
package com.alibaba.qlexpress4.runtime;
import com.alibaba.qlexpress4.QLOptions;
import com.alibaba.qlexpress4.runtime.context.ExpressContext;
import com.alibaba.qlexpress4.runtime.data.AssignableDataValue;
import com.alibaba.qlexpress4.runtime.function.CustomFunction;
import com.alibaba.qlexpress4.runtime.scope.QScope;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* global scope
*
* Author: DQinYuan
*/
public class QvmGlobalScope implements QScope {
private final ExpressContext externalVariable;
private final Map<String, LeftValue> newVariables;
private final Map<String, CustomFunction> externalFunction;
private final QLOptions qlOptions;
public QvmGlobalScope(ExpressContext externalVariable, Map<String, CustomFunction> externalFunction,
QLOptions qlOptions) {
this.externalVariable = externalVariable;
this.newVariables = new HashMap<>();
this.externalFunction = externalFunction;
this.qlOptions = qlOptions;
}
@Override
public Value getSymbol(String varName) {
LeftValue newVariable = newVariables.get(varName);
if (newVariable != null) {
return newVariable;
}
Value externalValue = externalVariable.get(qlOptions.getAttachments(), varName);
if (externalValue != null && qlOptions.isPolluteUserContext()) {
return externalValue;
}
newVariable = new AssignableDataValue(varName, externalValue == null ? null : externalValue.get());
newVariables.put(varName, newVariable);
return newVariable;
}
@Override
public Object getSymbolValue(String varName) {
return QScope.super.getSymbolValue(varName);
}
@Override
public void defineLocalSymbol(String varName, Class<?> varClz, Object value) {
throw new UnsupportedOperationException();
}
@Override
public void defineFunction(String functionName, CustomFunction function) {
throw new UnsupportedOperationException();
}
@Override
public CustomFunction getFunction(String functionName) {
return externalFunction.get(functionName);
}
@Override
public Map<String, CustomFunction> getFunctionTable() {
return externalFunction;
}
@Override
public void push(Value value) {
throw new UnsupportedOperationException();
}
@Override
public Parameters pop(int number) {
throw new UnsupportedOperationException();
}
@Override
public Value pop() {
throw new UnsupportedOperationException();
}
@Override
public Value peek() {
throw new UnsupportedOperationException();
}
@Override
public QScope getParent() {
throw new UnsupportedOperationException();
}
@Override
public QScope newScope() {
throw new UnsupportedOperationException();
}
}

View File

@@ -0,0 +1,46 @@
package com.alibaba.qlexpress4.runtime;
import com.alibaba.qlexpress4.runtime.trace.QTraces;
import java.util.Map;
/**
* root runtime with external variable and function
* Author: DQinYuan
*/
public class QvmRuntime implements QRuntime {
private final QTraces traces;
private final Map<String, Object> attachments;
private final ReflectLoader reflectLoader;
private final long startTime;
public QvmRuntime(QTraces traces, Map<String, Object> attachments, ReflectLoader reflectLoader, long startTime) {
this.traces = traces;
this.attachments = attachments;
this.reflectLoader = reflectLoader;
this.startTime = startTime;
}
@Override
public long scriptStartTimeStamp() {
return startTime;
}
@Override
public Map<String, Object> attachment() {
return attachments;
}
public ReflectLoader getReflectLoader() {
return reflectLoader;
}
@Override
public QTraces getTraces() {
return traces;
}
}

View File

@@ -0,0 +1,461 @@
package com.alibaba.qlexpress4.runtime;
import com.alibaba.qlexpress4.exception.ErrorReporter;
import com.alibaba.qlexpress4.exception.QLErrorCodes;
import com.alibaba.qlexpress4.exception.QLRuntimeException;
import com.alibaba.qlexpress4.member.FieldHandler;
import com.alibaba.qlexpress4.member.MethodHandler;
import com.alibaba.qlexpress4.runtime.data.DataValue;
import com.alibaba.qlexpress4.runtime.data.FieldValue;
import com.alibaba.qlexpress4.runtime.data.MapItemValue;
import com.alibaba.qlexpress4.runtime.function.ExtensionFunction;
import com.alibaba.qlexpress4.runtime.function.FilterExtensionFunction;
import com.alibaba.qlexpress4.runtime.function.MapExtensionFunction;
import com.alibaba.qlexpress4.security.QLSecurityStrategy;
import com.alibaba.qlexpress4.security.StrategyIsolation;
import com.alibaba.qlexpress4.utils.BasicUtil;
import java.io.Serializable;
import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
/**
* java reflect util with cache
* Author: DQinYuan
*/
public class ReflectLoader {
private final QLSecurityStrategy securityStrategy;
private final boolean allowPrivateAccess;
private final Map<List<Class<?>>, Constructor<?>> constructorCache = new ConcurrentHashMap<>();
private final Map<List<?>, FieldReflectCache> fieldCache = new ConcurrentHashMap<>();
private final Map<MethodCacheKey, IMethod> staticMethodCache = new ConcurrentHashMap<>();
private final Map<MethodCacheKey, IMethod> memberMethodCache = new ConcurrentHashMap<>();
/**
* default extension functions
*/
private final List<ExtensionFunction> extensionFunctions =
new CopyOnWriteArrayList<>(Arrays.asList(FilterExtensionFunction.INSTANCE, MapExtensionFunction.INSTANCE));
public ReflectLoader(QLSecurityStrategy securityStrategy, boolean allowPrivateAccess) {
this.securityStrategy = securityStrategy;
this.allowPrivateAccess = allowPrivateAccess;
}
public void addExtendFunction(ExtensionFunction extensionFunction) {
extensionFunctions.add(extensionFunction);
}
public Constructor<?> loadConstructor(Class<?> cls, Class<?>[] paramTypes) {
if (securityStrategy instanceof StrategyIsolation) {
return null;
}
List<Class<?>> cacheKey = new ArrayList<>(paramTypes.length + 1);
cacheKey.add(cls);
cacheKey.addAll(Arrays.asList(paramTypes));
Constructor<?> cachedConstructor = constructorCache.get(cacheKey);
if (cachedConstructor != null) {
return cachedConstructor;
}
Constructor<?> constructor = securityFilter(MemberResolver.resolveConstructor(cls, paramTypes));
if (constructor == null) {
return null;
}
constructorCache.put(cacheKey, constructor);
return constructor;
}
public Value loadField(Object bean, String fieldName, boolean skipSecurity, ErrorReporter errorReporter) {
if (bean.getClass().isArray() && BasicUtil.LENGTH.equals(fieldName)) {
return new DataValue(((Object[])bean).length);
}
else if (bean instanceof List && BasicUtil.LENGTH.equals(fieldName)) {
return new DataValue(((List<?>)bean).size());
}
else if (bean instanceof Map) {
return new MapItemValue((Map<?, ?>)bean, fieldName);
}
else if (!skipSecurity && securityStrategy instanceof StrategyIsolation) {
return null;
}
else if (bean instanceof MetaClass) {
MetaClass metaClass = (MetaClass)bean;
if (BasicUtil.CLASS.equals(fieldName)) {
return new DataValue(metaClass.getClz());
}
return loadJavaField(metaClass.getClz(), null, fieldName, skipSecurity, errorReporter);
}
else {
return loadJavaField(bean.getClass(), bean, fieldName, skipSecurity, errorReporter);
}
}
public IMethod loadMethod(Object bean, String methodName, Class<?>[] argTypes) {
boolean isStaticMethod = bean instanceof MetaClass;
Class<?> clz = isStaticMethod ? ((MetaClass)bean).getClz() : bean.getClass();
// only support member extension method
if (!isStaticMethod) {
IMethod extendFunction = loadExtendFunction(clz, methodName, argTypes);
if (extendFunction != null) {
return extendFunction;
}
}
if (securityStrategy instanceof StrategyIsolation) {
return null;
}
MethodCacheKey cacheKey = new MethodCacheKey(clz, methodName, argTypes);
Map<MethodCacheKey, IMethod> methodCache = isStaticMethod ? staticMethodCache : memberMethodCache;
IMethod cachedMethod = methodCache.get(cacheKey);
if (cachedMethod != null) {
return cachedMethod;
}
IMethod method = securityFilterIMethod(
MemberResolver.resolveMethod(clz, methodName, argTypes, isStaticMethod, allowPrivateAccess));
if (method == null) {
return null;
}
methodCache.put(cacheKey, method);
return method;
}
private IMethod securityFilterIMethod(IMethod iMethod) {
if (iMethod instanceof JvmIMethod) {
Method filterResult = securityFilter(((JvmIMethod)iMethod).getMethod());
return filterResult == null ? null : iMethod;
}
else {
return iMethod;
}
}
private IMethod loadExtendFunction(Class<?> clz, String methodName, Class<?>[] argTypes) {
List<ExtensionFunction> assignableExtensionFunctions = extensionFunctions.stream()
.filter(extensionFunction -> extensionFunction.getDeclaringClass().isAssignableFrom(clz)
&& methodName.equals(extensionFunction.getName()))
.collect(Collectors.toList());
if (assignableExtensionFunctions.isEmpty()) {
return null;
}
return MemberResolver.resolveMethod(assignableExtensionFunctions, argTypes);
}
private Value loadJavaField(Class<?> cls, Object bean, String fieldName, boolean skipSecurity,
ErrorReporter errorReporter) {
FieldReflectCache fieldReflectCache = loadFieldReflectCache(cls, fieldName, skipSecurity);
if (fieldReflectCache == null) {
return null;
}
Supplier<Object> getterOp = fieldReflectCache.getterSupplier.apply(errorReporter, bean);
if (fieldReflectCache.setterSupplier == null) {
return new DataValue(getterOp.get());
}
Consumer<Object> setterOp = fieldReflectCache.setterSupplier.apply(errorReporter, bean);
return new FieldValue(getterOp, setterOp, fieldReflectCache.defType);
}
private FieldReflectCache loadFieldReflectCache(Class<?> cls, String fieldName, boolean skipSecurity) {
List<Serializable> cacheKey = Arrays.asList(cls, fieldName);
FieldReflectCache cachedField = fieldCache.get(cacheKey);
if (cachedField != null) {
return cachedField;
}
FieldReflectCache fieldReflect = loadJavaFieldInner(cls, fieldName, skipSecurity);
if (fieldReflect != null) {
fieldCache.put(cacheKey, fieldReflect);
}
return fieldReflect;
}
private FieldReflectCache loadJavaFieldInner(Class<?> cls, String fieldName, boolean skipSecurity) {
String preHandledName = FieldHandler.Preferred.preHandleAlias(cls, fieldName);
Method getMethod = skipSecurity ? MethodHandler.getGetter(cls, preHandledName)
: securityFilter(MethodHandler.getGetter(cls, preHandledName));
Field field = skipSecurity ? FieldHandler.Preferred.gatherFieldRecursive(cls, preHandledName)
: securityFilter(FieldHandler.Preferred.gatherFieldRecursive(cls, preHandledName));
BiFunction<ErrorReporter, Object, Supplier<Object>> getterSupplier = fieldGetter(getMethod, field);
if (getterSupplier == null) {
return null;
}
Method setMethod = securityFilter(MethodHandler.getSetter(cls, preHandledName));
BiFunction<ErrorReporter, Object, Consumer<Object>> setterSupplier = fieldSetter(setMethod, field);
return new FieldReflectCache(getterSupplier, setterSupplier, fieldDefCls(setMethod, field));
}
private <T extends Member> T securityFilter(T member) {
return member == null ? null : (securityStrategy.check(member) ? member : null);
}
private Class<?> fieldDefCls(Method setMethod, Field field) {
return setMethod != null ? setMethod.getParameterTypes()[0] : field != null ? field.getType() : Object.class;
}
private BiFunction<ErrorReporter, Object, Consumer<Object>> fieldSetter(Method setMethod, Field field) {
if (setMethod != null) {
if (BasicUtil.isPublic(setMethod)) {
return setMethodAccessible(setMethod);
}
if (allowPrivateAccess) {
return setMethodUnAccessible(setMethod);
}
}
if (field != null) {
if (BasicUtil.isPublic(field)) {
return setFieldAccessible(field);
}
if (allowPrivateAccess) {
return setFieldUnAccessible(field);
}
}
return null;
}
private BiFunction<ErrorReporter, Object, Supplier<Object>> fieldGetter(Method getMethod, Field field) {
if (getMethod != null) {
if (BasicUtil.isPublic(getMethod)) {
return getMethodAccessible(getMethod);
}
if (allowPrivateAccess) {
return getMethodUnAccessible(getMethod);
}
}
if (field != null) {
if (BasicUtil.isPublic(field)) {
return getFieldAccessible(field);
}
if (allowPrivateAccess) {
return getFieldUnAccessible(field);
}
}
return null;
}
private BiFunction<ErrorReporter, Object, Consumer<Object>> setMethodAccessible(Method setMethod) {
return (errorReporter, bean) -> newValue -> {
try {
setMethod.invoke(bean, newValue);
}
catch (Exception e) {
throw unwrapMethodInvokeEx(errorReporter, setMethod.getName(), e);
}
};
}
private BiFunction<ErrorReporter, Object, Consumer<Object>> setMethodUnAccessible(Method setMethod) {
return (errorReporter, bean) -> newValue -> {
try {
setMethod.setAccessible(true);
setMethod.invoke(bean, newValue);
}
catch (Exception e) {
throw unwrapMethodInvokeEx(errorReporter, setMethod.getName(), e);
}
};
}
private BiFunction<ErrorReporter, Object, Consumer<Object>> setFieldAccessible(Field field) {
return (errorReporter, bean) -> newValue -> {
try {
field.set(bean, newValue);
}
catch (Exception e) {
throw errorReporter.report(e,
QLErrorCodes.SET_FIELD_UNKNOWN_ERROR.name(),
String.format(QLErrorCodes.SET_FIELD_UNKNOWN_ERROR.getErrorMsg(), field.getName()));
}
};
}
private BiFunction<ErrorReporter, Object, Consumer<Object>> setFieldUnAccessible(Field field) {
return (errorReporter, bean) -> newValue -> {
try {
field.setAccessible(true);
field.set(bean, newValue);
}
catch (Exception e) {
throw errorReporter.report(e,
QLErrorCodes.SET_FIELD_UNKNOWN_ERROR.name(),
String.format(QLErrorCodes.SET_FIELD_UNKNOWN_ERROR.getErrorMsg(), field.getName()));
}
};
}
private BiFunction<ErrorReporter, Object, Supplier<Object>> getMethodAccessible(Method getMethod) {
return (errorReporter, bean) -> () -> {
try {
return getMethod.invoke(bean);
}
catch (Exception e) {
throw unwrapMethodInvokeEx(errorReporter, getMethod.getName(), e);
}
};
}
private BiFunction<ErrorReporter, Object, Supplier<Object>> getMethodUnAccessible(Method getMethod) {
return (errorReporter, bean) -> () -> {
try {
getMethod.setAccessible(true);
return getMethod.invoke(bean);
}
catch (Exception e) {
throw unwrapMethodInvokeEx(errorReporter, getMethod.getName(), e);
}
};
}
private BiFunction<ErrorReporter, Object, Supplier<Object>> getFieldAccessible(Field field) {
return (errorReporter, bean) -> () -> {
try {
return field.get(bean);
}
catch (Exception e) {
throw errorReporter.report(e,
QLErrorCodes.GET_FIELD_UNKNOWN_ERROR.name(),
String.format(QLErrorCodes.GET_FIELD_UNKNOWN_ERROR.getErrorMsg(), field.getName()));
}
};
}
private BiFunction<ErrorReporter, Object, Supplier<Object>> getFieldUnAccessible(Field field) {
return (errorReporter, bean) -> () -> {
try {
field.setAccessible(true);
return field.get(bean);
}
catch (Exception e) {
throw errorReporter.report(e,
QLErrorCodes.GET_FIELD_UNKNOWN_ERROR.name(),
String.format(QLErrorCodes.GET_FIELD_UNKNOWN_ERROR.getErrorMsg(), field.getName()));
}
};
}
public static QLRuntimeException unwrapMethodInvokeEx(ErrorReporter errorReporter, String methodName,
Exception ex) {
if (ex instanceof IllegalArgumentException) {
return errorReporter.reportFormat(QLErrorCodes.INVOKE_METHOD_WITH_WRONG_ARGUMENTS.name(),
String.format(QLErrorCodes.INVOKE_METHOD_WITH_WRONG_ARGUMENTS.getErrorMsg(), methodName));
}
else if (ex instanceof InvocationTargetException) {
return errorReporter.report(((InvocationTargetException)ex).getTargetException(),
QLErrorCodes.INVOKE_METHOD_INNER_ERROR.name(),
String.format(QLErrorCodes.INVOKE_METHOD_INNER_ERROR.getErrorMsg(), methodName));
}
else {
return errorReporter.report(ex,
QLErrorCodes.INVOKE_METHOD_UNKNOWN_ERROR.name(),
String.format(QLErrorCodes.INVOKE_METHOD_UNKNOWN_ERROR.getErrorMsg(), methodName));
}
}
private static class FieldReflectCache {
private final BiFunction<ErrorReporter, Object, Supplier<Object>> getterSupplier;
private final BiFunction<ErrorReporter, Object, Consumer<Object>> setterSupplier;
private final Class<?> defType;
private FieldReflectCache(BiFunction<ErrorReporter, Object, Supplier<Object>> getterSupplier,
BiFunction<ErrorReporter, Object, Consumer<Object>> setterSupplier, Class<?> defType) {
this.getterSupplier = getterSupplier;
this.setterSupplier = setterSupplier;
this.defType = defType;
}
}
private static class ExtensionMapKey {
private final Class<?> cls;
private final String methodName;
public ExtensionMapKey(Class<?> cls, String methodName) {
this.cls = cls;
this.methodName = methodName;
}
public Class<?> getCls() {
return cls;
}
public String getMethodName() {
return methodName;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
ExtensionMapKey that = (ExtensionMapKey)o;
return Objects.equals(cls, that.cls) && Objects.equals(methodName, that.methodName);
}
@Override
public int hashCode() {
return Objects.hash(cls, methodName);
}
}
private static class MethodCacheKey {
private final Class<?> cls;
private final String methodName;
private final Class<?>[] argTypes;
public MethodCacheKey(Class<?> cls, String methodName, Class<?>[] argTypes) {
this.cls = cls;
this.methodName = methodName;
this.argTypes = argTypes;
}
public Class<?> getCls() {
return cls;
}
public String getMethodName() {
return methodName;
}
public Class<?>[] getArgTypes() {
return argTypes;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
MethodCacheKey that = (MethodCacheKey)o;
return cls.equals(that.cls) && methodName.equals(that.methodName) && Arrays.equals(argTypes, that.argTypes);
}
@Override
public int hashCode() {
int result = Objects.hash(cls, methodName);
result = 31 * result + Arrays.hashCode(argTypes);
return result;
}
}
}

View File

@@ -0,0 +1,29 @@
package com.alibaba.qlexpress4.runtime;
import com.alibaba.qlexpress4.runtime.data.DataValue;
/**
* Author: DQinYuan
*/
public interface Value {
Value NULL_VALUE = new DataValue((Object)null);
Object get();
default Class<?> getType() {
Object value = get();
if (value == null) {
return Nothing.class;
}
return value.getClass();
}
default String getTypeName() {
Class<?> type = getType();
if (type == null) {
return null;
}
return type.getName();
}
}

View File

@@ -0,0 +1,51 @@
package com.alibaba.qlexpress4.runtime.context;
import com.alibaba.qlexpress4.Express4Runner;
import com.alibaba.qlexpress4.QLOptions;
import com.alibaba.qlexpress4.QLResult;
import com.alibaba.qlexpress4.runtime.Value;
import com.alibaba.qlexpress4.runtime.data.DataValue;
import com.alibaba.qlexpress4.runtime.data.MapItemValue;
import java.util.HashMap;
import java.util.Map;
public class DynamicVariableContext implements ExpressContext {
private final Express4Runner runner;
private final Map<String, Object> staticContext;
private final QLOptions qlOptions;
private final Map<String, String> dynamicContext;
public DynamicVariableContext(Express4Runner runner, Map<String, Object> staticContext, QLOptions qlOptions,
Map<String, String> dynamicContext) {
this.runner = runner;
this.staticContext = staticContext;
this.qlOptions = qlOptions;
this.dynamicContext = dynamicContext;
}
public DynamicVariableContext(Express4Runner runner, Map<String, Object> staticContext, QLOptions qlOptions) {
this.runner = runner;
this.staticContext = staticContext;
this.qlOptions = qlOptions;
this.dynamicContext = new HashMap<>();
}
public void put(String name, String valueExpression) {
dynamicContext.put(name, valueExpression);
}
@Override
public Value get(Map<String, Object> attachments, String variableName) {
String dynamicScript = dynamicContext.get(variableName);
if (dynamicScript != null) {
QLResult result = runner.execute(dynamicScript, this, qlOptions);
return new DataValue(result.getResult());
}
return new MapItemValue(staticContext, variableName);
}
}

View File

@@ -0,0 +1,12 @@
package com.alibaba.qlexpress4.runtime.context;
import com.alibaba.qlexpress4.runtime.Value;
import java.util.Map;
public class EmptyContext implements ExpressContext {
@Override
public Value get(Map<String, Object> attachments, String variableName) {
return Value.NULL_VALUE;
}
}

View File

@@ -0,0 +1,16 @@
package com.alibaba.qlexpress4.runtime.context;
import com.alibaba.qlexpress4.runtime.Value;
import java.util.Map;
/**
* Author: DQinYuan
*/
public interface ExpressContext {
ExpressContext EMPTY_CONTEXT = new EmptyContext();
Value get(Map<String, Object> attachments, String variableName);
}

View File

@@ -0,0 +1,23 @@
package com.alibaba.qlexpress4.runtime.context;
import com.alibaba.qlexpress4.runtime.Value;
import com.alibaba.qlexpress4.runtime.data.MapItemValue;
import java.util.Map;
/**
* Author: DQinYuan
*/
public class MapExpressContext implements ExpressContext {
private final Map<String, Object> source;
public MapExpressContext(Map<String, Object> source) {
this.source = source;
}
@Override
public Value get(Map<String, Object> attachments, String variableName) {
return new MapItemValue(source, variableName);
}
}

View File

@@ -0,0 +1,25 @@
package com.alibaba.qlexpress4.runtime.context;
import com.alibaba.qlexpress4.Express4Runner;
import com.alibaba.qlexpress4.exception.PureErrReporter;
import com.alibaba.qlexpress4.runtime.ReflectLoader;
import com.alibaba.qlexpress4.runtime.Value;
import java.util.Map;
public class ObjectFieldExpressContext implements ExpressContext {
private final Object object;
private final Express4Runner express4Runner;
public ObjectFieldExpressContext(Object object, Express4Runner express4Runner) {
this.object = object;
this.express4Runner = express4Runner;
}
@Override
public Value get(Map<String, Object> attachments, String variableName) {
return express4Runner.loadField(object, variableName);
}
}

View File

@@ -0,0 +1,32 @@
package com.alibaba.qlexpress4.runtime.context;
import com.alibaba.qlexpress4.annotation.QLAlias;
import com.alibaba.qlexpress4.exception.QLException;
import com.alibaba.qlexpress4.runtime.Value;
import com.alibaba.qlexpress4.runtime.data.MapItemValue;
import java.util.HashMap;
import java.util.Map;
public class QLAliasContext implements ExpressContext {
private final Map<String, Object> context;
public QLAliasContext(Object... os) {
Map<String, Object> context = new HashMap<>();
for (Object o : os) {
QLAlias[] qlAliases = o.getClass().getAnnotationsByType(QLAlias.class);
for (QLAlias qlAlias : qlAliases) {
for (String alias : qlAlias.value()) {
context.put(alias, o);
}
}
}
this.context = context;
}
@Override
public Value get(Map<String, Object> attachments, String variableName) {
return new MapItemValue(context, variableName);
}
}

View File

@@ -0,0 +1,40 @@
package com.alibaba.qlexpress4.runtime.data;
import com.alibaba.qlexpress4.runtime.LeftValue;
import java.lang.reflect.Array;
/**
* Author: DQinYuan
*/
public class ArrayItemValue implements LeftValue {
private final Object array;
private final int index;
public ArrayItemValue(Object array, int index) {
this.array = array;
this.index = index;
}
@Override
public void setInner(Object newValue) {
Array.set(array, index, newValue);
}
@Override
public String getSymbolName() {
return null;
}
@Override
public Object get() {
return Array.get(array, index);
}
@Override
public Class<?> getDefinedType() {
return array.getClass().getComponentType();
}
}

View File

@@ -0,0 +1,47 @@
package com.alibaba.qlexpress4.runtime.data;
import com.alibaba.qlexpress4.runtime.LeftValue;
/**
* Author: DQinYuan
*/
public class AssignableDataValue implements LeftValue {
private String symbolName;
private Object value;
private final Class<?> defineType;
public AssignableDataValue(String symbolName, Object value) {
this.symbolName = symbolName;
this.value = value;
this.defineType = null;
}
public AssignableDataValue(String symbolName, Object value, Class<?> defineType) {
this.symbolName = symbolName;
this.value = value;
this.defineType = defineType;
}
@Override
public void setInner(Object newValue) {
this.value = newValue;
}
@Override
public Object get() {
return value;
}
@Override
public Class<?> getDefinedType() {
return defineType;
}
@Override
public String getSymbolName() {
return symbolName;
}
}

View File

@@ -0,0 +1,24 @@
package com.alibaba.qlexpress4.runtime.data;
import com.alibaba.qlexpress4.runtime.Value;
/**
* Author: TaoKan
*/
public class DataValue implements Value {
private final Object value;
public DataValue(Object value) {
this.value = value;
}
public DataValue(Value value) {
this.value = value.get();
}
@Override
public Object get() {
return this.value;
}
}

View File

@@ -0,0 +1,43 @@
package com.alibaba.qlexpress4.runtime.data;
import com.alibaba.qlexpress4.runtime.LeftValue;
import java.util.function.Consumer;
import java.util.function.Supplier;
/**
* Author: TaoKan
*/
public class FieldValue implements LeftValue {
private final Supplier<Object> getOp;
private final Consumer<Object> setOp;
private final Class<?> defineType;
public FieldValue(Supplier<Object> getOp, Consumer<Object> setOp, Class<?> defineType) {
this.getOp = getOp;
this.setOp = setOp;
this.defineType = defineType;
}
@Override
public void setInner(Object newValue) {
setOp.accept(newValue);
}
@Override
public String getSymbolName() {
return null;
}
@Override
public Object get() {
return getOp.get();
}
@Override
public Class<?> getDefinedType() {
return this.defineType;
}
}

View File

@@ -0,0 +1,40 @@
package com.alibaba.qlexpress4.runtime.data;
import com.alibaba.qlexpress4.runtime.LeftValue;
import java.util.List;
/**
* Author: DQinYuan
*/
public class ListItemValue implements LeftValue {
private final List<? super Object> list;
private final int index;
public ListItemValue(List<? super Object> list, int index) {
this.list = list;
this.index = index;
}
@Override
public void setInner(Object newValue) {
list.set(index, newValue);
}
@Override
public String getSymbolName() {
return null;
}
@Override
public Object get() {
return list.get(index);
}
@Override
public Class<?> getDefinedType() {
return Object.class;
}
}

View File

@@ -0,0 +1,50 @@
package com.alibaba.qlexpress4.runtime.data;
import com.alibaba.qlexpress4.runtime.LeftValue;
import java.util.Map;
/**
* Author: DQinYuan
*/
@SuppressWarnings("all")
public class MapItemValue implements LeftValue {
private final String symbolName;
private final Map map;
private final Object key;
public MapItemValue(Map map, Object key) {
this.symbolName = null;
this.map = map;
this.key = key;
}
public MapItemValue(String symbolName, Map map, Object key) {
this.symbolName = symbolName;
this.map = map;
this.key = key;
}
@Override
public void setInner(Object newValue) {
map.put(key, newValue);
}
@Override
public String getSymbolName() {
return symbolName;
}
@Override
public Object get() {
return map.get(key);
}
@Override
public Class<?> getDefinedType() {
return null;
}
}

View File

@@ -0,0 +1,163 @@
package com.alibaba.qlexpress4.runtime.data.convert;
import com.alibaba.qlexpress4.proxy.QLambdaInvocationHandler;
import com.alibaba.qlexpress4.runtime.Nothing;
import com.alibaba.qlexpress4.runtime.QLambda;
import com.alibaba.qlexpress4.runtime.operator.number.NumberMath;
import com.alibaba.qlexpress4.utils.CacheUtil;
import java.lang.reflect.Proxy;
import java.math.BigDecimal;
import java.math.BigInteger;
/**
* Author: TaoKan
*/
public class ObjTypeConvertor {
public static QConverted cast(Object value, Class<?> type) {
// no need to convert
if (noNeedConvert(value, type)) {
return converted(value);
}
if (type == Character.class || type == char.class) {
return castChar(value);
}
if (CacheUtil.isFunctionInterface(type)) {
return castFunctionInter(value, type);
}
// unboxed boxed
if (type == boolean.class || type == Boolean.class) {
if (value instanceof Boolean) {
return converted(value);
}
return unConvertible();
}
if (type == byte.class || type == Byte.class) {
if (value instanceof Byte) {
return converted(value);
}
if (value instanceof Number) {
return converted(((Number)value).byteValue());
}
return unConvertible();
}
if (type == short.class || type == Short.class) {
if (value instanceof Short) {
return converted(value);
}
if (value instanceof Number) {
return converted(((Number)value).shortValue());
}
return unConvertible();
}
if (type == int.class || type == Integer.class) {
if (value instanceof Integer) {
return converted(value);
}
if (value instanceof Number) {
return converted(((Number)value).intValue());
}
return unConvertible();
}
if (type == long.class || type == Long.class) {
if (value instanceof Long) {
return converted(value);
}
if (value instanceof Number) {
return converted(((Number)value).longValue());
}
return unConvertible();
}
if (type == float.class || type == Float.class) {
if (value instanceof Float) {
return converted(value);
}
if (value instanceof Number) {
return converted(((Number)value).floatValue());
}
return unConvertible();
}
if (type == double.class || type == Double.class) {
if (value instanceof Double) {
return converted(value);
}
if (value instanceof Number) {
return converted(((Number)value).doubleValue());
}
return unConvertible();
}
if (type == BigInteger.class) {
if (value instanceof Number) {
return converted(NumberMath.toBigInteger((Number)value));
}
return unConvertible();
}
if (type == BigDecimal.class) {
if (value instanceof Number) {
return converted(NumberMath.toBigDecimal((Number)value));
}
return unConvertible();
}
return unConvertible();
}
private static QConverted castFunctionInter(Object value, Class<?> functionInter) {
if (value instanceof QLambda) {
return converted(Proxy.newProxyInstance(functionInter.getClassLoader(),
new Class<?>[] {functionInter},
new QLambdaInvocationHandler((QLambda)value)));
}
return unConvertible();
}
private static QConverted castChar(Object value) {
if (value instanceof Character) {
return converted(value);
}
if (value instanceof Number) {
return converted((char)((Number)value).intValue());
}
if (value instanceof String) {
String strValue = (String)value;
if (strValue.length() == 1) {
return converted(strValue.charAt(0));
}
return unConvertible();
}
return unConvertible();
}
private static boolean noNeedConvert(Object value, Class<?> type) {
return type == null || value == null || type.isInstance(value);
}
private static QConverted converted(Object converted) {
return new QConverted(true, converted);
}
private static QConverted unConvertible() {
return new QConverted(false, null);
}
public static class QConverted {
private final boolean convertible;
private final Object converted;
public QConverted(boolean convertible, Object converted) {
this.convertible = convertible;
this.converted = converted;
}
public boolean isConvertible() {
return convertible;
}
public Object getConverted() {
return converted;
}
}
}

View File

@@ -0,0 +1,35 @@
package com.alibaba.qlexpress4.runtime.data.convert;
import java.lang.reflect.Array;
/**
* Author: TaoKan
*/
public class ParametersTypeConvertor {
public static Object[] cast(Object[] arguments, Class<?>[] paramTypes, boolean isVarArg) {
if (!isVarArg) {
Object[] result = new Object[arguments.length];
for (int i = 0; i < arguments.length; i++) {
Object argument = arguments[i];
result[i] = ObjTypeConvertor.cast(argument, paramTypes[i]).getConverted();
}
return result;
}
Class<?> itemType = paramTypes[paramTypes.length - 1].getComponentType();
Object varArgs = Array.newInstance(itemType, arguments.length - paramTypes.length + 1);
int varArgStart = paramTypes.length - 1;
for (int i = varArgStart; i < arguments.length; i++) {
Object argument = arguments[i];
Object castValue = ObjTypeConvertor.cast(argument, itemType).getConverted();
Array.set(varArgs, i - varArgStart, castValue);
}
Object[] result = new Object[paramTypes.length];
for (int i = 0; i < paramTypes.length - 1; i++) {
result[i] = ObjTypeConvertor.cast(arguments[i], paramTypes[i]).getConverted();
}
result[result.length - 1] = varArgs;
return result;
}
}

Some files were not shown because too many files have changed in this diff Show More