first
This commit is contained in:
436
src/main/antlr4/QLParser.g4
Normal file
436
src/main/antlr4/QLParser.g4
Normal 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
1196
src/main/antlr4/QLexer.g4
Normal file
File diff suppressed because it is too large
Load Diff
180
src/main/antlr4/QLexer.tokens
Normal file
180
src/main/antlr4/QLexer.tokens
Normal 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
|
||||
69
src/main/java/com/alibaba/qlexpress4/CheckOptions.java
Normal file
69
src/main/java/com/alibaba/qlexpress4/CheckOptions.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
src/main/java/com/alibaba/qlexpress4/ClassSupplier.java
Normal file
11
src/main/java/com/alibaba/qlexpress4/ClassSupplier.java
Normal file
@@ -0,0 +1,11 @@
|
||||
package com.alibaba.qlexpress4;
|
||||
|
||||
/**
|
||||
* Author: DQinYuan
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ClassSupplier {
|
||||
|
||||
Class<?> loadCls(String clsQualifiedName);
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
751
src/main/java/com/alibaba/qlexpress4/Express4Runner.java
Normal file
751
src/main/java/com/alibaba/qlexpress4/Express4Runner.java
Normal 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;
|
||||
}
|
||||
}
|
||||
245
src/main/java/com/alibaba/qlexpress4/InitOptions.java
Normal file
245
src/main/java/com/alibaba/qlexpress4/InitOptions.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
205
src/main/java/com/alibaba/qlexpress4/QLOptions.java
Normal file
205
src/main/java/com/alibaba/qlexpress4/QLOptions.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
89
src/main/java/com/alibaba/qlexpress4/QLPrecedences.java
Normal file
89
src/main/java/com/alibaba/qlexpress4/QLPrecedences.java
Normal 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;
|
||||
|
||||
}
|
||||
25
src/main/java/com/alibaba/qlexpress4/QLResult.java
Normal file
25
src/main/java/com/alibaba/qlexpress4/QLResult.java
Normal 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;
|
||||
}
|
||||
}
|
||||
15
src/main/java/com/alibaba/qlexpress4/annotation/QLAlias.java
Normal file
15
src/main/java/com/alibaba/qlexpress4/annotation/QLAlias.java
Normal 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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
142
src/main/java/com/alibaba/qlexpress4/aparser/CheckVisitor.java
Normal file
142
src/main/java/com/alibaba/qlexpress4/aparser/CheckVisitor.java
Normal 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);
|
||||
}
|
||||
}
|
||||
12
src/main/java/com/alibaba/qlexpress4/aparser/ExistStack.java
Normal file
12
src/main/java/com/alibaba/qlexpress4/aparser/ExistStack.java
Normal 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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
231
src/main/java/com/alibaba/qlexpress4/aparser/ImportManager.java
Normal file
231
src/main/java/com/alibaba/qlexpress4/aparser/ImportManager.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
103
src/main/java/com/alibaba/qlexpress4/aparser/QLExtendLexer.java
Normal file
103
src/main/java/com/alibaba/qlexpress4/aparser/QLExtendLexer.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.alibaba.qlexpress4.api;
|
||||
|
||||
/**
|
||||
* Author: TaoKan
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface QLFunctionalVarargs {
|
||||
Object call(Object... params);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.alibaba.qlexpress4.enums;
|
||||
|
||||
/**
|
||||
* Author: TaoKan
|
||||
*/
|
||||
public enum AccessMode {
|
||||
WRITE, READ,
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
106
src/main/java/com/alibaba/qlexpress4/member/MethodHandler.java
Normal file
106
src/main/java/com/alibaba/qlexpress4/member/MethodHandler.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
24
src/main/java/com/alibaba/qlexpress4/runtime/IMethod.java
Normal file
24
src/main/java/com/alibaba/qlexpress4/runtime/IMethod.java
Normal 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;
|
||||
}
|
||||
56
src/main/java/com/alibaba/qlexpress4/runtime/JvmIMethod.java
Normal file
56
src/main/java/com/alibaba/qlexpress4/runtime/JvmIMethod.java
Normal 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);
|
||||
}
|
||||
}
|
||||
40
src/main/java/com/alibaba/qlexpress4/runtime/LeftValue.java
Normal file
40
src/main/java/com/alibaba/qlexpress4/runtime/LeftValue.java
Normal 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();
|
||||
}
|
||||
242
src/main/java/com/alibaba/qlexpress4/runtime/MemberResolver.java
Normal file
242
src/main/java/com/alibaba/qlexpress4/runtime/MemberResolver.java
Normal 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;
|
||||
}
|
||||
}
|
||||
34
src/main/java/com/alibaba/qlexpress4/runtime/MetaClass.java
Normal file
34
src/main/java/com/alibaba/qlexpress4/runtime/MetaClass.java
Normal 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);
|
||||
}
|
||||
}
|
||||
12
src/main/java/com/alibaba/qlexpress4/runtime/Nothing.java
Normal file
12
src/main/java/com/alibaba/qlexpress4/runtime/Nothing.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package com.alibaba.qlexpress4.runtime;
|
||||
|
||||
/**
|
||||
* subclass of any object
|
||||
*/
|
||||
public class Nothing {
|
||||
|
||||
private Nothing() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
26
src/main/java/com/alibaba/qlexpress4/runtime/Parameters.java
Normal file
26
src/main/java/com/alibaba/qlexpress4/runtime/Parameters.java
Normal 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();
|
||||
}
|
||||
13
src/main/java/com/alibaba/qlexpress4/runtime/QContext.java
Normal file
13
src/main/java/com/alibaba/qlexpress4/runtime/QContext.java
Normal 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();
|
||||
}
|
||||
84
src/main/java/com/alibaba/qlexpress4/runtime/QLambda.java
Normal file
84
src/main/java/com/alibaba/qlexpress4/runtime/QLambda.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
44
src/main/java/com/alibaba/qlexpress4/runtime/QResult.java
Normal file
44
src/main/java/com/alibaba/qlexpress4/runtime/QResult.java
Normal 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;
|
||||
}
|
||||
}
|
||||
23
src/main/java/com/alibaba/qlexpress4/runtime/QRuntime.java
Normal file
23
src/main/java/com/alibaba/qlexpress4/runtime/QRuntime.java
Normal 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();
|
||||
}
|
||||
105
src/main/java/com/alibaba/qlexpress4/runtime/QvmGlobalScope.java
Normal file
105
src/main/java/com/alibaba/qlexpress4/runtime/QvmGlobalScope.java
Normal 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();
|
||||
}
|
||||
}
|
||||
46
src/main/java/com/alibaba/qlexpress4/runtime/QvmRuntime.java
Normal file
46
src/main/java/com/alibaba/qlexpress4/runtime/QvmRuntime.java
Normal 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;
|
||||
}
|
||||
}
|
||||
461
src/main/java/com/alibaba/qlexpress4/runtime/ReflectLoader.java
Normal file
461
src/main/java/com/alibaba/qlexpress4/runtime/ReflectLoader.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
29
src/main/java/com/alibaba/qlexpress4/runtime/Value.java
Normal file
29
src/main/java/com/alibaba/qlexpress4/runtime/Value.java
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user