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.
+ *
+ * WARNING: Calling this method will cause a significant compilation performance degradation.
+ * It is NOT recommended for normal use cases.
+ *
+ *
+ * Use Cases:
+ *
+ * - Memory-sensitive applications: When memory usage is a critical concern and you can
+ * tolerate slower compilation times
+ * - Infrequently changing scripts: When scripts are relatively stable and not frequently
+ * recompiled
+ *
+ *
+ * Best Practice:
+ *
+ * Call this method immediately after parsing and caching your expression, and ensure all subsequent
+ * executions use the cached version to avoid recompilation. Example:
+ *
+ * {@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());
+ * }
+ * }
+ */
+ 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 getParseFuture(String script) {
+ Future parseFuture = compileCache.get(script);
+ if (parseFuture != null) {
+ return parseFuture;
+ }
+ FutureTask parseTask = new FutureTask<>(() -> parseDefinition(script));
+ Future 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 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 boolean addOperatorBiFunction(String operator, BiFunction 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;
+ }
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/InitOptions.java b/src/main/java/com/alibaba/qlexpress4/InitOptions.java
new file mode 100644
index 0000000..944a20a
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/InitOptions.java
@@ -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 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 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 defaultImport, boolean debug,
+ Consumer 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 getDefaultImport() {
+ return defaultImport;
+ }
+
+ public ClassSupplier getClassSupplier() {
+ return classSupplier;
+ }
+
+ public boolean isDebug() {
+ return debug;
+ }
+
+ public Consumer 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 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 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 defaultImport) {
+ this.defaultImport.addAll(defaultImport);
+ return this;
+ }
+
+ public Builder debug(boolean debug) {
+ this.debug = debug;
+ return this;
+ }
+
+ public Builder debugInfoConsumer(Consumer 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);
+ }
+ }
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/QLOptions.java b/src/main/java/com/alibaba/qlexpress4/QLOptions.java
new file mode 100644
index 0000000..845d00d
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/QLOptions.java
@@ -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 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 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 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 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 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);
+ }
+ }
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/QLPrecedences.java b/src/main/java/com/alibaba/qlexpress4/QLPrecedences.java
new file mode 100644
index 0000000..090c86a
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/QLPrecedences.java
@@ -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;
+
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/QLResult.java b/src/main/java/com/alibaba/qlexpress4/QLResult.java
new file mode 100644
index 0000000..0d9f6ec
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/QLResult.java
@@ -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 expressionTraces;
+
+ public QLResult(Object result, List expressionTraces) {
+ this.result = result;
+ this.expressionTraces = expressionTraces;
+ }
+
+ public Object getResult() {
+ return result;
+ }
+
+ public List getExpressionTraces() {
+ return expressionTraces;
+ }
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/annotation/QLAlias.java b/src/main/java/com/alibaba/qlexpress4/annotation/QLAlias.java
new file mode 100644
index 0000000..0aa9983
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/annotation/QLAlias.java
@@ -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();
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/annotation/QLFunction.java b/src/main/java/com/alibaba/qlexpress4/annotation/QLFunction.java
new file mode 100644
index 0000000..517753f
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/annotation/QLFunction.java
@@ -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();
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/aparser/AliasTokenSource.java b/src/main/java/com/alibaba/qlexpress4/aparser/AliasTokenSource.java
new file mode 100644
index 0000000..9e4c6c8
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/aparser/AliasTokenSource.java
@@ -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();
+ }
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/aparser/AliasTokenStream.java b/src/main/java/com/alibaba/qlexpress4/aparser/AliasTokenStream.java
new file mode 100644
index 0000000..72487cb
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/aparser/AliasTokenStream.java
@@ -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();
+ }
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/aparser/BuiltInTypesSet.java b/src/main/java/com/alibaba/qlexpress4/aparser/BuiltInTypesSet.java
new file mode 100644
index 0000000..1a3c16e
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/aparser/BuiltInTypesSet.java
@@ -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;
+ }
+ }
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/aparser/CheckVisitor.java b/src/main/java/com/alibaba/qlexpress4/aparser/CheckVisitor.java
new file mode 100644
index 0000000..d067783
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/aparser/CheckVisitor.java
@@ -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 {
+
+ /**
+ * 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);
+ }
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/aparser/ExistStack.java b/src/main/java/com/alibaba/qlexpress4/aparser/ExistStack.java
new file mode 100644
index 0000000..9adcc0b
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/aparser/ExistStack.java
@@ -0,0 +1,12 @@
+package com.alibaba.qlexpress4.aparser;
+
+public interface ExistStack {
+
+ ExistStack push();
+
+ ExistStack pop();
+
+ boolean exist(String varName);
+
+ void add(String varName);
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/aparser/GeneratorScope.java b/src/main/java/com/alibaba/qlexpress4/aparser/GeneratorScope.java
new file mode 100644
index 0000000..c623e17
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/aparser/GeneratorScope.java
@@ -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 macroDefineMap;
+
+ public GeneratorScope(GeneratorScope parent, String name, Map 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;
+ }
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/aparser/ImportManager.java b/src/main/java/com/alibaba/qlexpress4/aparser/ImportManager.java
new file mode 100644
index 0000000..c1995bc
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/aparser/ImportManager.java
@@ -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 importedPacks;
+
+ private final Map> importedClses;
+
+ public ImportManager(ClassSupplier classSupplier, List imports) {
+ this.classSupplier = classSupplier;
+ this.importedPacks = new ArrayList<>();
+ this.importedClses = new HashMap<>();
+ imports.forEach(this::addImport);
+ }
+
+ public ImportManager(ClassSupplier classSupplier, List importedPacks,
+ Map> 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 fieldIds) {
+ Class> qualifiedCls = null;
+ List 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;
+ }
+ }
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/aparser/InterpolationMode.java b/src/main/java/com/alibaba/qlexpress4/aparser/InterpolationMode.java
new file mode 100644
index 0000000..69a6989
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/aparser/InterpolationMode.java
@@ -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
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/aparser/MacroDefine.java b/src/main/java/com/alibaba/qlexpress4/aparser/MacroDefine.java
new file mode 100644
index 0000000..93daf38
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/aparser/MacroDefine.java
@@ -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 instructions;
+
+ private final boolean lastStmtExpress;
+
+ public MacroDefine(List instructions, boolean lastStmtExpress) {
+ this.instructions = instructions;
+ this.lastStmtExpress = lastStmtExpress;
+ }
+
+ public List getMacroInstructions() {
+ return instructions;
+ }
+
+ public boolean isLastStmtExpress() {
+ return lastStmtExpress;
+ }
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/aparser/OperatorFactory.java b/src/main/java/com/alibaba/qlexpress4/aparser/OperatorFactory.java
new file mode 100644
index 0000000..3c9ee2a
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/aparser/OperatorFactory.java
@@ -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);
+
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/aparser/OutFunctionVisitor.java b/src/main/java/com/alibaba/qlexpress4/aparser/OutFunctionVisitor.java
new file mode 100644
index 0000000..610aac4
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/aparser/OutFunctionVisitor.java
@@ -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 outFunctions = new HashSet<>();
+
+ public OutFunctionVisitor() {
+ super(new ExistFunctionStack(null));
+ }
+
+ private static class ExistFunctionStack implements ExistStack {
+ private final ExistFunctionStack parent;
+
+ private final Set 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 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 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 getOutFunctions() {
+ return outFunctions;
+ }
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/aparser/OutVarAttrsVisitor.java b/src/main/java/com/alibaba/qlexpress4/aparser/OutVarAttrsVisitor.java
new file mode 100644
index 0000000..e88e554
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/aparser/OutVarAttrsVisitor.java
@@ -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> 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 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 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 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 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 pathPartContexts) {
+ List 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 pathPartContexts) {
+ List 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> getOutVarAttrs() {
+ return outVarAttrs;
+ }
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/aparser/OutVarNamesVisitor.java b/src/main/java/com/alibaba/qlexpress4/aparser/OutVarNamesVisitor.java
new file mode 100644
index 0000000..0a6fff8
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/aparser/OutVarNamesVisitor.java
@@ -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 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 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 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 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 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 pathPartContexts) {
+ List 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 getOutVars() {
+ return outVars;
+ }
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/aparser/ParserOperatorManager.java b/src/main/java/com/alibaba/qlexpress4/aparser/ParserOperatorManager.java
new file mode 100644
index 0000000..e5230e5
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/aparser/ParserOperatorManager.java
@@ -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);
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/aparser/QCompileCache.java b/src/main/java/com/alibaba/qlexpress4/aparser/QCompileCache.java
new file mode 100644
index 0000000..43bf810
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/aparser/QCompileCache.java
@@ -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 expressionTracePoints;
+
+ public QCompileCache(QLambdaDefinition qLambdaDefinition, List expressionTracePoints) {
+ this.qLambdaDefinition = qLambdaDefinition;
+ this.expressionTracePoints = expressionTracePoints;
+ }
+
+ public QLambdaDefinition getQLambdaDefinition() {
+ return qLambdaDefinition;
+ }
+
+ public List getExpressionTracePoints() {
+ return expressionTracePoints;
+ }
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/aparser/QLErrorListener.java b/src/main/java/com/alibaba/qlexpress4/aparser/QLErrorListener.java
new file mode 100644
index 0000000..04d4a2f
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/aparser/QLErrorListener.java
@@ -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 ? "" : currentToken.getText();
+ String preHandledScript = currentToken.getType() == Token.EOF ? script + "" : 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;
+ }
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/aparser/QLErrorStrategy.java b/src/main/java/com/alibaba/qlexpress4/aparser/QLErrorStrategy.java
new file mode 100644
index 0000000..92a9579
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/aparser/QLErrorStrategy.java
@@ -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 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 "";
+ }
+ else if (a == Token.EPSILON) {
+ return "";
+ }
+ else {
+ return vocabulary.getDisplayName(a);
+ }
+ }
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/aparser/QLExtendLexer.java b/src/main/java/com/alibaba/qlexpress4/aparser/QLExtendLexer.java
new file mode 100644
index 0000000..f717664
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/aparser/QLExtendLexer.java
@@ -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;
+ }
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/aparser/QLExtendParser.java b/src/main/java/com/alibaba/qlexpress4/aparser/QLExtendParser.java
new file mode 100644
index 0000000..0f6f20f
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/aparser/QLExtendParser.java
@@ -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;
+ }
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/aparser/QvmInstructionVisitor.java b/src/main/java/com/alibaba/qlexpress4/aparser/QvmInstructionVisitor.java
new file mode 100644
index 0000000..dc62090
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/aparser/QvmInstructionVisitor.java
@@ -0,0 +1,1752 @@
+package com.alibaba.qlexpress4.aparser;
+
+import com.alibaba.qlexpress4.DefaultClassSupplier;
+import com.alibaba.qlexpress4.InitOptions;
+import com.alibaba.qlexpress4.aparser.compiletimefunction.CodeGenerator;
+import com.alibaba.qlexpress4.aparser.compiletimefunction.CompileTimeFunction;
+import com.alibaba.qlexpress4.exception.*;
+import com.alibaba.qlexpress4.runtime.*;
+import com.alibaba.qlexpress4.runtime.instruction.*;
+import com.alibaba.qlexpress4.runtime.operator.BinaryOperator;
+import com.alibaba.qlexpress4.runtime.operator.OperatorManager;
+import com.alibaba.qlexpress4.runtime.operator.unary.UnaryOperator;
+import com.alibaba.qlexpress4.utils.QLStringUtils;
+import org.antlr.v4.runtime.RuleContext;
+import org.antlr.v4.runtime.Token;
+import org.antlr.v4.runtime.tree.ParseTree;
+import org.antlr.v4.runtime.tree.TerminalNode;
+
+import java.lang.reflect.Array;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static com.alibaba.qlexpress4.aparser.QLParser.*;
+
+/**
+ * Author: DQinYuan
+ */
+public class QvmInstructionVisitor extends QLParserBaseVisitor {
+ private static final String SCOPE_SEPARATOR = "$";
+
+ private static final String BLOCK_LAMBDA_NAME_PREFIX = "BLOCK_";
+
+ private static final String IF_PREFIX = "IF_";
+
+ private static final String THEN_SUFFIX = "_THEN";
+
+ private static final String ELSE_SUFFIX = "_ELSE";
+
+ private static final String MACRO_PREFIX = "MACRO_";
+
+ private static final String LAMBDA_PREFIX = "LAMBDA_";
+
+ private static final String TRY_PREFIX = "TRY_";
+
+ private static final String CATCH_SUFFIX = "_CATCH";
+
+ private static final String FINAL_SUFFIX = "_FINAL";
+
+ private static final String FOR_PREFIX = "FOR_";
+
+ private static final String INIT_SUFFIX = "_INIT";
+
+ private static final String CONDITION_SUFFIX = "_CONDITION";
+
+ private static final String UPDATE_SUFFIX = "_UPDATE";
+
+ private static final String BODY_SUFFIX = "_BODY";
+
+ private static final String WHILE_PREFIX = "WHILE_";
+
+ private static final String LBRACE = "{";
+
+ private static final String RBRACE = "}";
+
+ private static final BigInteger MAX_LONG = BigInteger.valueOf(Long.MAX_VALUE);
+
+ private static final BigInteger MAX_INTEGER = BigInteger.valueOf(Integer.MAX_VALUE);
+
+ private static final BigDecimal MAX_DOUBLE = new BigDecimal(String.valueOf(Double.MAX_VALUE));
+
+ private static final int TIMEOUT_CHECK_GAP = 5;
+
+ public enum Context {
+ BLOCK, MACRO
+ }
+
+ private final String script;
+
+ private final ImportManager importManager;
+
+ private final GeneratorScope generatorScope;
+
+ private final OperatorFactory operatorFactory;
+
+ private final Map compileTimeFunctions;
+
+ private final InitOptions initOptions;
+
+ private final Context context;
+
+ private final List instructionList = new ArrayList<>();
+
+ private int stackSize;
+
+ private int maxStackSize;
+
+ private int ifCounter = 0;
+
+ private int blockCounter = 0;
+
+ private int macroCounter = 0;
+
+ private int lambdaCounter = 0;
+
+ private int tryCounter = 0;
+
+ private int forCounter = 0;
+
+ private int whileCounter = 0;
+
+ private int timeoutCheckPoint = -1;
+
+ /*
+ * main constructor
+ */
+ public QvmInstructionVisitor(String script, ImportManager importManager, GeneratorScope globalScope,
+ OperatorFactory operatorFactory, Map compileTimeFunctions,
+ InitOptions initOptions) {
+ this.script = script;
+ this.importManager = importManager;
+ this.generatorScope = new GeneratorScope("main", globalScope);
+ this.operatorFactory = operatorFactory;
+ this.context = Context.BLOCK;
+ this.compileTimeFunctions = compileTimeFunctions;
+ this.initOptions = initOptions;
+ }
+
+ /*
+ * for recursion
+ */
+ public QvmInstructionVisitor(String script, ImportManager importManager, GeneratorScope generatorScope,
+ OperatorFactory operatorFactory, Context context, Map compileTimeFunctions,
+ InitOptions initOptions) {
+ this.script = script;
+ this.importManager = importManager;
+ this.generatorScope = generatorScope;
+ this.operatorFactory = operatorFactory;
+ this.context = context;
+ this.compileTimeFunctions = compileTimeFunctions;
+ this.initOptions = initOptions;
+ }
+
+ /*
+ * visible for testing
+ */
+ public QvmInstructionVisitor(String script) {
+ this.script = script;
+ this.importManager = new ImportManager(DefaultClassSupplier.getInstance(), new ArrayList<>(), new HashMap<>());
+ this.generatorScope = new GeneratorScope("test-main", null);
+ this.operatorFactory = new OperatorManager();
+ this.context = Context.BLOCK;
+ this.compileTimeFunctions = new HashMap<>();
+ this.initOptions = InitOptions.DEFAULT_OPTIONS;
+ }
+
+ @Override
+ public Void visitImportCls(ImportClsContext ctx) {
+ String importClsPath =
+ ctx.varId().stream().map(VarIdContext::getStart).map(Token::getText).collect(Collectors.joining("."));
+ importManager.addImport(ImportManager.importCls(importClsPath));
+ return null;
+ }
+
+ @Override
+ public Void visitImportPack(ImportPackContext ctx) {
+ List importPackPathTokens = ctx.varId();
+ boolean isInnerCls =
+ !Character.isLowerCase(importPackPathTokens.get(importPackPathTokens.size() - 1).getText().charAt(0));
+ String importPath = importPackPathTokens.stream()
+ .map(VarIdContext::getStart)
+ .map(Token::getText)
+ .collect(Collectors.joining("."));
+ importManager
+ .addImport(isInnerCls ? ImportManager.importInnerCls(importPath) : ImportManager.importPack(importPath));
+ return null;
+ }
+
+ @Override
+ public Void visitBlockStatements(BlockStatementsContext blockStatementsContext) {
+ boolean isPreExpress = false;
+ List nonEmptyChildren = blockStatementsContext.blockStatement()
+ .stream()
+ .filter(bs -> !(bs instanceof EmptyStatementContext))
+ .collect(Collectors.toList());
+
+ // First pass: process macro definitions to ensure they are available for functions
+ for (BlockStatementContext child : nonEmptyChildren) {
+ if (child instanceof MacroStatementContext) {
+ child.accept(this);
+ }
+ }
+
+ // Second pass: process all function definitions to support forward references
+ for (BlockStatementContext child : nonEmptyChildren) {
+ if (child instanceof FunctionStatementContext) {
+ child.accept(this);
+ }
+ }
+
+ // Third pass: process all other statements
+ for (BlockStatementContext child : nonEmptyChildren) {
+ if (!(child instanceof FunctionStatementContext) && !(child instanceof MacroStatementContext)) {
+ if (isPreExpress) {
+ addInstruction(new PopInstruction(PureErrReporter.INSTANCE));
+ }
+ isPreExpress = handleStmt(child);
+ }
+ }
+
+ if (context == Context.BLOCK) {
+ if (isPreExpress) {
+ addInstruction(new ReturnInstruction(PureErrReporter.INSTANCE, QResult.ResultType.CONTINUE, null));
+ }
+ }
+ return null;
+ }
+
+ private void visitBodyExpression(ExpressionContext expressionContext) {
+ BlockExprContext blockExprContext = blockExpr(expressionContext);
+ if (blockExprContext != null) {
+ BlockStatementsContext blockStatementsContext = blockExprContext.blockStatements();
+ if (blockStatementsContext == null) {
+ return;
+ }
+ blockStatementsContext.accept(this);
+ return;
+ }
+ expressionContext.accept(this);
+ addInstruction(
+ new ReturnInstruction(newReporterWithToken(expressionContext.getStart()), QResult.ResultType.RETURN, null));
+ }
+
+ private BlockExprContext blockExpr(ExpressionContext expressionContext) {
+ Token startToken = expressionContext.getStart();
+ Token stopToken = expressionContext.getStop();
+ if (!(LBRACE.equals(startToken.getText()) && RBRACE.equals(stopToken.getText()))) {
+ // fast fail
+ return null;
+ }
+ TernaryExprContext ternaryExprContext = expressionContext.ternaryExpr();
+ if (ternaryExprContext == null) {
+ return null;
+ }
+ if (ternaryExprContext.QUESTION() != null) {
+ return null;
+ }
+ BaseExprContext baseExprContext = ternaryExprContext.baseExpr(0);
+ if (!baseExprContext.leftAsso().isEmpty()) {
+ return null;
+ }
+ PrimaryContext primaryContext = baseExprContext.primary();
+ if (primaryContext.primaryNoFixNonPathable() != null) {
+ return null;
+ }
+
+ PrimaryNoFixPathableContext primaryNoFixPathableContext = primaryContext.primaryNoFixPathable();
+ return primaryNoFixPathableContext instanceof BlockExprContext ? (BlockExprContext)primaryNoFixPathableContext
+ : null;
+ }
+
+ @Override
+ public Void visitExpression(ExpressionContext ctx) {
+ TernaryExprContext ternaryExprContext = ctx.ternaryExpr();
+ if (ternaryExprContext != null) {
+ ternaryExprContext.accept(this);
+ return null;
+ }
+
+ ctx.leftHandSide().accept(this);
+ ctx.expression().accept(this);
+
+ AssignOperatorContext assignOperatorContext = ctx.assignOperator();
+ BinaryOperator assignOperator = operatorFactory.getBinaryOperator(assignOperatorContext.getText());
+ addInstruction(new OperatorInstruction(newReporterWithToken(assignOperatorContext.getStart()), assignOperator,
+ assignOperatorContext.getStart().getStartIndex()));
+ return null;
+ }
+
+ @Override
+ public Void visitTraditionalForStatement(TraditionalForStatementContext ctx) {
+ int forCount = forCount();
+ ErrorReporter forErrReporter = newReporterWithToken(ctx.FOR().getSymbol());
+
+ // for init
+ ForInitContext forInitContext = ctx.forInit();
+ QLambdaDefinitionInner forInitLambda =
+ forInitContext == null ? null : generateForInitLambda(forCount, forInitContext);
+
+ // condition
+ ExpressionContext forConditionContext = ctx.forCondition;
+ QLambdaDefinitionInner forConditionLambda = forConditionContext == null ? null
+ : generateForExpressLambda(forCount, CONDITION_SUFFIX, forConditionContext);
+
+ // for update
+ ExpressionContext forUpdateContext = ctx.forUpdate;
+ QLambdaDefinitionInner forUpdateLambda =
+ forUpdateContext == null ? null : generateForExpressLambda(forCount, UPDATE_SUFFIX, forUpdateContext);
+
+ // for body
+ QLambdaDefinition forBodyLambda = loopBodyVisitorDefinition(ctx.blockStatements(),
+ generatorScope.getName() + SCOPE_SEPARATOR + FOR_PREFIX + forCount + BODY_SUFFIX,
+ Collections.emptyList(),
+ forErrReporter);
+
+ int forInitSize = forInitLambda == null ? 0 : forInitLambda.getMaxStackSize();
+ int forConditionSize = forConditionLambda == null ? 0 : forConditionLambda.getMaxStackSize();
+ int forUpdateSize = forUpdateLambda == null ? 0 : forUpdateLambda.getMaxStackSize();
+ int forScopeMaxStackSize = Math.max(forInitSize, Math.max(forConditionSize, forUpdateSize));
+
+ if (initOptions.isTraceExpression()) {
+ pureAddInstruction(new TraceEvaludatedInstruction(forErrReporter, ctx.FOR().getSymbol().getStartIndex()));
+ }
+
+ addInstruction(new ForInstruction(forErrReporter, forInitLambda, forConditionLambda,
+ forConditionContext != null ? newReporterWithToken(forConditionContext.getStart()) : null, forUpdateLambda,
+ forScopeMaxStackSize, forBodyLambda));
+ return null;
+ }
+
+ private QLambdaDefinitionInner generateForInitLambda(int forCount, ForInitContext forInitContext) {
+ if (forInitContext.localVariableDeclaration() != null) {
+ String scopeName = generatorScope.getName() + SCOPE_SEPARATOR + FOR_PREFIX + forCount + INIT_SUFFIX;
+ QvmInstructionVisitor subVisitor = parseWithSubVisitor(forInitContext.localVariableDeclaration(),
+ new GeneratorScope(scopeName, generatorScope),
+ Context.MACRO);
+ return new QLambdaDefinitionInner(scopeName, subVisitor.getInstructions(), Collections.emptyList(),
+ subVisitor.getMaxStackSize());
+ }
+ else if (forInitContext.expression() != null) {
+ return generateForExpressLambda(forCount, INIT_SUFFIX, forInitContext.expression());
+ }
+ else {
+ return null;
+ }
+ }
+
+ private QLambdaDefinitionInner generateForExpressLambda(int forCount, String scopeSuffix,
+ ExpressionContext expressionContext) {
+ String scopeName = generatorScope.getName() + SCOPE_SEPARATOR + FOR_PREFIX + forCount + scopeSuffix;
+ QvmInstructionVisitor subVisitor = parseExprBodyWithSubVisitor(expressionContext,
+ new GeneratorScope(scopeName, generatorScope),
+ Context.BLOCK);
+ return new QLambdaDefinitionInner(scopeName, subVisitor.getInstructions(), Collections.emptyList(),
+ subVisitor.getMaxStackSize());
+ }
+
+ @Override
+ public Void visitForEachStatement(ForEachStatementContext ctx) {
+ ExpressionContext targetExprContext = ctx.expression();
+ targetExprContext.accept(this);
+
+ DeclTypeContext declTypeContext = ctx.declType();
+ Class> itVarCls = declTypeContext == null ? Object.class : parseDeclType(declTypeContext);
+
+ ErrorReporter forEachErrReporter = newReporterWithToken(ctx.FOR().getSymbol());
+ QLambdaDefinition bodyDefinition = loopBodyVisitorDefinition(ctx.blockStatements(),
+ generatorScope.getName() + SCOPE_SEPARATOR + FOR_PREFIX + forCount() + BODY_SUFFIX,
+ Collections.singletonList(new QLambdaDefinitionInner.Param(ctx.varId().getText(), itVarCls)),
+ forEachErrReporter);
+
+ if (initOptions.isTraceExpression()) {
+ pureAddInstruction(
+ new TraceEvaludatedInstruction(forEachErrReporter, ctx.FOR().getSymbol().getStartIndex()));
+ }
+
+ addInstruction(new ForEachInstruction(forEachErrReporter, bodyDefinition, itVarCls,
+ newReporterWithToken(targetExprContext.getStart())));
+
+ return null;
+ }
+
+ @Override
+ public Void visitWhileStatement(WhileStatementContext ctx) {
+ int whileCount = whileCount();
+
+ String whileConditionScope =
+ generatorScope.getName() + SCOPE_SEPARATOR + WHILE_PREFIX + whileCount + CONDITION_SUFFIX;
+ QvmInstructionVisitor conditionSubVisitor = parseExprBodyWithSubVisitor(ctx.expression(),
+ new GeneratorScope(whileConditionScope, generatorScope),
+ Context.BLOCK);
+ QLambdaDefinitionInner conditionLambda = new QLambdaDefinitionInner(whileConditionScope,
+ conditionSubVisitor.getInstructions(), Collections.emptyList(), conditionSubVisitor.getMaxStackSize());
+
+ ErrorReporter whileErrReporter = newReporterWithToken(ctx.WHILE().getSymbol());
+ QLambdaDefinition whileBodyLambda = loopBodyVisitorDefinition(ctx.blockStatements(),
+ generatorScope.getName() + SCOPE_SEPARATOR + WHILE_PREFIX + whileCount + BODY_SUFFIX,
+ Collections.emptyList(),
+ whileErrReporter);
+
+ if (initOptions.isTraceExpression()) {
+ pureAddInstruction(
+ new TraceEvaludatedInstruction(whileErrReporter, ctx.WHILE().getSymbol().getStartIndex()));
+ }
+
+ addInstruction(new WhileInstruction(whileErrReporter, conditionLambda, whileBodyLambda,
+ whileBodyLambda instanceof QLambdaDefinitionInner
+ ? Math.max(conditionLambda.getMaxStackSize(),
+ ((QLambdaDefinitionInner)whileBodyLambda).getMaxStackSize())
+ : conditionLambda.getMaxStackSize()));
+ return null;
+ }
+
+ private QLambdaDefinition loopBodyVisitorDefinition(BlockStatementsContext bodyCtx, String scopeName,
+ List paramsType, ErrorReporter errorReporter) {
+ if (bodyCtx == null) {
+ return QLambdaDefinitionEmpty.INSTANCE;
+ }
+ QvmInstructionVisitor bodyVisitor =
+ parseWithSubVisitor(bodyCtx, new GeneratorScope(scopeName, generatorScope), Context.MACRO);
+ List bodyInstructions = bodyVisitor.getInstructions();
+
+ List resultInstructions = new ArrayList<>();
+ resultInstructions.add(new CheckTimeOutInstruction(errorReporter));
+ resultInstructions.addAll(bodyInstructions);
+
+ return new QLambdaDefinitionInner(scopeName, resultInstructions, paramsType, bodyVisitor.getMaxStackSize());
+ }
+
+ @Override
+ public Void visitThrowStatement(ThrowStatementContext ctx) {
+ ctx.expression().accept(this);
+ addInstruction(new ThrowInstruction(newReporterWithToken(ctx.THROW().getSymbol())));
+ return null;
+ }
+
+ @Override
+ public Void visitReturnStatement(ReturnStatementContext ctx) {
+ ErrorReporter errorReporter = newReporterWithToken(ctx.getStart());
+ ExpressionContext expression = ctx.expression();
+ if (expression == null) {
+ addInstruction(new ConstInstruction(errorReporter, null, null));
+ }
+ else {
+ expression.accept(this);
+ }
+
+ addInstruction(new ReturnInstruction(errorReporter, QResult.ResultType.RETURN, ctx.getStart().getStartIndex()));
+ return null;
+ }
+
+ @Override
+ public Void visitFunctionStatement(FunctionStatementContext ctx) {
+ FormalOrInferredParameterListContext formalOrInferredParameterList = ctx.formalOrInferredParameterList();
+ List params = formalOrInferredParameterList == null ? Collections.emptyList()
+ : parseFormalOrInferredParameterList(formalOrInferredParameterList);
+ VarIdContext functionNameCtx = ctx.varId();
+ QLambdaDefinition functionDefinition = parseFunctionDefinition(functionNameCtx.getText(), ctx, params);
+
+ ErrorReporter errorReporter = newReporterWithToken(functionNameCtx.getStart());
+
+ if (initOptions.isTraceExpression()) {
+ pureAddInstruction(
+ new TraceEvaludatedInstruction(errorReporter, functionNameCtx.getStart().getStartIndex()));
+ }
+
+ addInstruction(new DefineFunctionInstruction(errorReporter, functionDefinition.getName(), functionDefinition));
+ return null;
+ }
+
+ private QLambdaDefinition parseFunctionDefinition(String functionName, FunctionStatementContext ctx,
+ List params) {
+ BlockStatementsContext blockStatementsContext = ctx.blockStatements();
+ if (blockStatementsContext == null) {
+ return new QLambdaDefinitionInner(functionName, Collections.emptyList(), params, 0);
+ }
+
+ QvmInstructionVisitor functionSubVisitor = parseWithSubVisitor(blockStatementsContext,
+ new GeneratorScope(functionName, generatorScope),
+ Context.BLOCK);
+ return new QLambdaDefinitionInner(functionName, functionSubVisitor.getInstructions(), params,
+ functionSubVisitor.getMaxStackSize());
+ }
+
+ @Override
+ public Void visitCastExpr(CastExprContext ctx) {
+ DeclTypeContext castDeclTypeContext = ctx.declType();
+ Class> castCls = parseDeclType(castDeclTypeContext);
+ ErrorReporter errorReporter = newReporterWithToken(castDeclTypeContext.getStart());
+ addInstruction(new ConstInstruction(errorReporter, castCls, null));
+ ctx.primary().accept(this);
+ addInstruction(new CastInstruction(errorReporter));
+ return null;
+ }
+
+ @Override
+ public Void visitTernaryExpr(TernaryExprContext ctx) {
+ ctx.condition.accept(this);
+
+ if (ctx.QUESTION() != null) {
+ QvmInstructionVisitor thenVisitor = parseWithSubVisitor(ctx.thenExpr, generatorScope, Context.MACRO);
+ QvmInstructionVisitor elseVisitor = parseWithSubVisitor(ctx.elseExpr, generatorScope, Context.MACRO);
+ ifElseInstructions(newReporterWithToken(ctx.QUESTION().getSymbol()),
+ thenVisitor.getInstructions(),
+ null,
+ elseVisitor.getInstructions(),
+ null,
+ ctx.QUESTION().getSymbol().getStartIndex());
+ }
+
+ return null;
+ }
+
+ @Override
+ public Void visitBlockExpr(BlockExprContext ctx) {
+ ErrorReporter blockErrReporter = newReporterWithToken(ctx.getStart());
+ BlockStatementsContext blockStatementsContext = ctx.blockStatements();
+ if (blockStatementsContext == null) {
+ addInstruction(new ConstInstruction(blockErrReporter, null, null));
+ return null;
+ }
+
+ String blockScopeName = blockScopeName();
+ QvmInstructionVisitor blockSubVisitor = parseWithSubVisitor(blockStatementsContext,
+ new GeneratorScope(blockScopeName, generatorScope),
+ Context.MACRO);
+
+ addInstruction(new NewScopeInstruction(blockErrReporter, blockScopeName));
+ blockSubVisitor.getInstructions().forEach(this::addInstruction);
+ addInstruction(new CloseScopeInstruction(blockErrReporter, blockScopeName));
+ if (initOptions.isTraceExpression()) {
+ pureAddInstruction(new TracePeekInstruction(blockErrReporter, ctx.getStart().getStartIndex()));
+ }
+ return null;
+ }
+
+ @Override
+ public Void visitQlIf(QlIfContext qlIfContext) {
+ qlIfContext.condition.accept(this);
+
+ int ifCount = ifCount();
+ ErrorReporter ifErrorReporter = newReporterWithToken(qlIfContext.IF().getSymbol());
+ String ifScopeName = generatorScope.getName() + SCOPE_SEPARATOR + IF_PREFIX + ifCount;
+ addInstruction(new NewScopeInstruction(ifErrorReporter, ifScopeName));
+
+ String thenScopeName = generatorScope.getName() + SCOPE_SEPARATOR + IF_PREFIX + ifCount + THEN_SUFFIX;
+ ThenBodyContext thenBodyContext = qlIfContext.thenBody();
+ List thenInstructions =
+ parseWithSubVisitor(thenBodyContext, new GeneratorScope(thenScopeName, generatorScope), Context.MACRO)
+ .getInstructions();
+ if (ifBodyFillConst(thenBodyContext.expression(),
+ thenBodyContext.blockStatement(),
+ thenBodyContext.blockStatements())) {
+ thenInstructions.add(new ConstInstruction(ifErrorReporter, null, null));
+ }
+ Integer thenTraceKey = thenBodyContext.LBRACE() == null ? null : thenBodyContext.getStart().getStartIndex();
+
+ String elseScopeName = generatorScope.getName() + SCOPE_SEPARATOR + IF_PREFIX + ifCount + ELSE_SUFFIX;
+ ElseBodyContext elseBodyContext = qlIfContext.elseBody();
+ List elseInstructions =
+ elseBodyContext == null ? Collections.singletonList(new ConstInstruction(ifErrorReporter, null, null))
+ : parseWithSubVisitor(elseBodyContext, new GeneratorScope(elseScopeName, generatorScope), Context.MACRO)
+ .getInstructions();
+ if (elseBodyContext != null && elseBodyContext.qlIf() == null
+ && ifBodyFillConst(elseBodyContext.expression(),
+ elseBodyContext.blockStatement(),
+ elseBodyContext.blockStatements())) {
+ elseInstructions.add(new ConstInstruction(ifErrorReporter, null, null));
+ }
+ Integer elseTraceKey = elseBodyContext == null ? null
+ : (elseBodyContext.LBRACE() == null ? null : elseBodyContext.getStart().getStartIndex());
+
+ ifElseInstructions(ifErrorReporter,
+ thenInstructions,
+ thenTraceKey,
+ elseInstructions,
+ elseTraceKey,
+ qlIfContext.getStart().getStartIndex());
+
+ addInstruction(new CloseScopeInstruction(ifErrorReporter, ifScopeName));
+ return null;
+ }
+
+ private boolean ifBodyFillConst(ExpressionContext expressionContext, BlockStatementContext blockStatementContext,
+ BlockStatementsContext blockStatementsContext) {
+ if (expressionContext != null) {
+ return false;
+ }
+ if (blockStatementContext != null) {
+ return stmtFillConst(blockStatementContext);
+ }
+ if (blockStatementsContext != null) {
+ List statementList = blockStatementsContext.blockStatement()
+ .stream()
+ .filter(bs -> !(bs instanceof EmptyStatementContext))
+ .collect(Collectors.toList());
+ return statementList.isEmpty() || stmtFillConst(statementList.get(statementList.size() - 1));
+ }
+ return true;
+ }
+
+ private boolean stmtFillConst(BlockStatementContext blockStatementContext) {
+ return !(blockStatementContext instanceof ExpressionStatementContext)
+ && !(blockStatementContext instanceof ReturnStatementContext);
+ }
+
+ @Override
+ public Void visitBreakContinueStatement(BreakContinueStatementContext ctx) {
+ TerminalNode aBreak = ctx.BREAK();
+ QResult qResult = aBreak == null ? QResult.LOOP_CONTINUE_RESULT : QResult.LOOP_BREAK_RESULT;
+
+ if (initOptions.isTraceExpression()) {
+ pureAddInstruction(
+ new TraceEvaludatedInstruction(newReporterWithToken(ctx.getStart()), ctx.getStart().getStartIndex()));
+ }
+
+ addInstruction(new BreakContinueInstruction(newReporterWithToken(ctx.getStart()), qResult));
+ return null;
+ }
+
+ @Override
+ public Void visitListExpr(ListExprContext ctx) {
+ visitListExprInner(ctx.listItems(), newReporterWithToken(ctx.getStart()));
+ return null;
+ }
+
+ private void visitListExprInner(ListItemsContext listItemsContext, ErrorReporter listErrorReporter) {
+ if (listItemsContext == null) {
+ addInstruction(new NewListInstruction(listErrorReporter, 0));
+ return;
+ }
+ List expressions = listItemsContext.expression();
+ for (ExpressionContext expression : expressions) {
+ expression.accept(this);
+ }
+ addInstruction(new NewListInstruction(listErrorReporter, expressions.size()));
+ }
+
+ @Override
+ public Void visitMapExpr(MapExprContext ctx) {
+ MapEntriesContext mapEntriesContext = ctx.mapEntries();
+ List mapEntryContexts = mapEntriesContext.mapEntry();
+ List keys = new ArrayList<>(mapEntryContexts.size());
+ Class> cls = null;
+ for (MapEntryContext mapEntryContext : mapEntryContexts) {
+ MapValueContext valueContext = mapEntryContext.mapValue();
+ if (valueContext instanceof EValueContext) {
+ EValueContext eValueContext = (EValueContext)valueContext;
+ keys.add(parseMapKey(mapEntryContext.mapKey()));
+ eValueContext.expression().accept(this);
+ continue;
+ }
+ if (valueContext instanceof ClsValueContext) {
+ ClsValueContext clsValueContext = (ClsValueContext)valueContext;
+ TerminalNode clsLiteral = clsValueContext.QuoteStringLiteral();
+ String clsText = clsLiteral.getText();
+ String clsName = clsText.substring(1, clsText.length() - 1);
+ Class> mayBeCls = importManager.loadQualified(clsName);
+ if (mayBeCls == null) {
+ String clsKeyText = mapEntryContext.mapKey().getText();
+ keys.add(clsKeyText.substring(1, clsKeyText.length() - 1));
+ addInstruction(new ConstInstruction(newReporterWithToken(clsLiteral.getSymbol()),
+ QLStringUtils.parseStringEscape(clsText), null));
+ // @class override
+ cls = null;
+ }
+ else {
+ cls = mayBeCls;
+ }
+ }
+ }
+ if (cls == null) {
+ addInstruction(new NewMapInstruction(newReporterWithToken(ctx.getStart()), keys));
+ }
+ else {
+ addInstruction(new NewFilledInstanceInstruction(newReporterWithToken(ctx.getStart()), cls, keys));
+ }
+ return null;
+ }
+
+ private String parseMapKey(MapKeyContext mapKeyContext) {
+ if (mapKeyContext instanceof IdKeyContext) {
+ return mapKeyContext.getText();
+ }
+ else if (mapKeyContext instanceof StringKeyContext || mapKeyContext instanceof QuoteStringKeyContext) {
+ return QLStringUtils.parseStringEscape(mapKeyContext.getText());
+ }
+ // shouldn't run here
+ throw new IllegalStateException();
+ }
+
+ @Override
+ public Void visitNewObjExpr(NewObjExprContext ctx) {
+ Class> newCls = parseClsIds(ctx.varId());
+ ArgumentListContext argumentListContext = ctx.argumentList();
+ if (argumentListContext != null) {
+ argumentListContext.accept(this);
+ }
+ int argNum = argumentListContext == null ? 0 : argumentListContext.expression().size();
+ addInstruction(new NewInstanceInstruction(newReporterWithToken(ctx.NEW().getSymbol()), newCls, argNum));
+ return null;
+ }
+
+ @Override
+ public Void visitNewEmptyArrExpr(NewEmptyArrExprContext ctx) {
+ ctx.dimExprs().accept(this);
+ int dims = ctx.dimExprs().expression().size();
+ Class> arrCls = parseDeclTypeNoArr(ctx.declTypeNoArr());
+ addInstruction(new MultiNewArrayInstruction(newReporterWithToken(ctx.NEW().getSymbol()), arrCls, dims));
+ return null;
+ }
+
+ @Override
+ public Void visitNewInitArrExpr(NewInitArrExprContext ctx) {
+ Class> cls = parseDeclTypeNoArr(ctx.declTypeNoArr());
+ ArrayInitializerContext arrayInitializerContext = ctx.arrayInitializer();
+ newArrWithInitializers(embedClsInDims(cls, ctx.dims().LBRACK().size() - 1), arrayInitializerContext);
+ return null;
+ }
+
+ private Class> embedClsInDims(Class> cls, int dims) {
+ for (int i = 0; i < dims; i++) {
+ cls = Array.newInstance(cls, 0).getClass();
+ }
+ return cls;
+ }
+
+ @Override
+ public Void visitLambdaExpr(LambdaExprContext ctx) {
+ List lambdaParams = parseLambdaParams(ctx.lambdaParameters());
+ String lambdaScopeName = lambdaScopeName();
+
+ QvmInstructionVisitor subVisitor = null;
+ ExpressionContext expression = ctx.expression();
+ ErrorReporter arrowErrorReporter = newReporterWithToken(ctx.ARROW().getSymbol());
+ if (expression != null) {
+ subVisitor = parseExprBodyWithSubVisitor(expression,
+ new GeneratorScope(lambdaScopeName, generatorScope),
+ Context.BLOCK);
+ }
+ else {
+ BlockStatementsContext blockStatementsContext = ctx.blockStatements();
+ if (blockStatementsContext != null) {
+ subVisitor = parseWithSubVisitor(blockStatementsContext,
+ new GeneratorScope(lambdaScopeName, generatorScope),
+ Context.BLOCK);
+ }
+ }
+
+ if (subVisitor == null) {
+ addInstruction(new LoadLambdaInstruction(arrowErrorReporter, QLambdaDefinitionEmpty.INSTANCE));
+ }
+ else {
+ QLambdaDefinition lambdaDefinition = new QLambdaDefinitionInner(lambdaScopeName,
+ subVisitor.getInstructions(), lambdaParams, subVisitor.getMaxStackSize());
+ addInstruction(new LoadLambdaInstruction(arrowErrorReporter, lambdaDefinition));
+ }
+ return null;
+ }
+
+ private List parseLambdaParams(LambdaParametersContext lambdaParametersContext) {
+ VarIdContext varIdContext = lambdaParametersContext.varId();
+ if (varIdContext != null) {
+ return Collections.singletonList(new QLambdaDefinitionInner.Param(varIdContext.getText(), Object.class));
+ }
+ FormalOrInferredParameterListContext formalOrInferredParameterList =
+ lambdaParametersContext.formalOrInferredParameterList();
+ if (formalOrInferredParameterList == null) {
+ return Collections.emptyList();
+ }
+ return formalOrInferredParameterList.formalOrInferredParameter()
+ .stream()
+ .map(this::formalOrInferredParameter2Param)
+ .collect(Collectors.toList());
+ }
+
+ private List parseFormalOrInferredParameterList(
+ FormalOrInferredParameterListContext formalOrInferredParameterList) {
+ return formalOrInferredParameterList.formalOrInferredParameter()
+ .stream()
+ .map(this::formalOrInferredParameter2Param)
+ .collect(Collectors.toList());
+ }
+
+ private QLambdaDefinitionInner.Param formalOrInferredParameter2Param(
+ FormalOrInferredParameterContext formalOrInferredParameterContext) {
+ String paramName = formalOrInferredParameterContext.varId().getText();
+ DeclTypeContext declTypeContext = formalOrInferredParameterContext.declType();
+ Class> paramCls = declTypeContext == null ? Object.class : parseDeclType(declTypeContext);
+ return new QLambdaDefinitionInner.Param(paramName, paramCls);
+ }
+
+ @Override
+ public Void visitTryCatchExpr(TryCatchExprContext ctx) {
+ BlockStatementsContext blockStatementsContext = ctx.blockStatements();
+ if (blockStatementsContext == null) {
+ addInstruction(new ConstInstruction(newReporterWithToken(ctx.TRY().getSymbol()), null,
+ ctx.getStart().getStartIndex()));
+ return null;
+ }
+
+ int tryCount = tryCount();
+ String tryScopeName = generatorScope.getName() + SCOPE_SEPARATOR + TRY_PREFIX + tryCount;
+ QvmInstructionVisitor bodySubVisitor = parseWithSubVisitor(blockStatementsContext,
+ new GeneratorScope(tryScopeName, generatorScope),
+ Context.BLOCK);
+
+ QLambdaDefinition bodyLambdaDefinition = new QLambdaDefinitionInner(tryScopeName,
+ bodySubVisitor.getInstructions(), Collections.emptyList(), bodySubVisitor.getMaxStackSize());
+ List, QLambdaDefinition>> exceptionTable = parseExceptionTable(tryCount, ctx);
+ QLambdaDefinition finalBodyDefinition = parseFinalBodyDefinition(tryCount, ctx);
+
+ addInstruction(new TryCatchInstruction(newReporterWithToken(ctx.TRY().getSymbol()), bodyLambdaDefinition,
+ exceptionTable, finalBodyDefinition));
+ return null;
+ }
+
+ private QLambdaDefinition parseFinalBodyDefinition(int tryCount, TryCatchExprContext ctx) {
+ TryFinallyContext tryFinallyContext = ctx.tryFinally();
+ if (tryFinallyContext == null) {
+ return null;
+ }
+
+ BlockStatementsContext blockStatementsContext = tryFinallyContext.blockStatements();
+ if (blockStatementsContext == null) {
+ return null;
+ }
+
+ String finalScopeName = generatorScope.getName() + SCOPE_SEPARATOR + TRY_PREFIX + tryCount + FINAL_SUFFIX;
+ QvmInstructionVisitor finalBodySubVisitor = parseWithSubVisitor(blockStatementsContext,
+ new GeneratorScope(finalScopeName, generatorScope),
+ Context.BLOCK);
+ return new QLambdaDefinitionInner(finalScopeName, finalBodySubVisitor.getInstructions(),
+ Collections.emptyList(), finalBodySubVisitor.getMaxStackSize());
+ }
+
+ private List, QLambdaDefinition>> parseExceptionTable(int tryCount, TryCatchExprContext ctx) {
+ TryCatchesContext tryCatchesContext = ctx.tryCatches();
+ if (tryCatchesContext == null) {
+ return Collections.emptyList();
+ }
+ List tryCatchContexts = tryCatchesContext.tryCatch();
+ int catchSize = tryCatchContexts.size();
+ List, QLambdaDefinition>> exceptionTable = new ArrayList<>(catchSize);
+ for (TryCatchContext tryCatchContext : tryCatchContexts) {
+ CatchParamsContext catchParamsContext = tryCatchContext.catchParams();
+ String eName = catchParamsContext.varId().getText();
+ String catchBodyName = generatorScope.getName() + SCOPE_SEPARATOR + TRY_PREFIX + tryCount + CATCH_SUFFIX;
+ QvmInstructionVisitor catchSubVisitor = tryCatchContext.blockStatements() == null ? null
+ : parseWithSubVisitor(tryCatchContext.blockStatements(),
+ new GeneratorScope(catchBodyName, generatorScope),
+ Context.BLOCK);
+
+ List catchDeclTypes = catchParamsContext.declType();
+ if (catchDeclTypes.isEmpty()) {
+ QLambdaDefinitionInner.Param param = new QLambdaDefinitionInner.Param(eName, Object.class);
+ QLambdaDefinition exceptionHandlerDefinition = catchSubVisitor == null ? QLambdaDefinitionEmpty.INSTANCE
+ : new QLambdaDefinitionInner(catchBodyName, catchSubVisitor.getInstructions(),
+ Collections.singletonList(param), catchSubVisitor.getMaxStackSize());
+ exceptionTable.add(new AbstractMap.SimpleEntry<>(Object.class, exceptionHandlerDefinition));
+ }
+ for (DeclTypeContext declTypeContext : catchDeclTypes) {
+ Class> exceptionType = parseDeclType(declTypeContext);
+ QLambdaDefinitionInner.Param param = new QLambdaDefinitionInner.Param(eName, exceptionType);
+ QLambdaDefinition exceptionHandlerDefinition = catchSubVisitor == null ? QLambdaDefinitionEmpty.INSTANCE
+ : new QLambdaDefinitionInner(catchBodyName, catchSubVisitor.getInstructions(),
+ Collections.singletonList(param), catchSubVisitor.getMaxStackSize());
+ exceptionTable.add(new AbstractMap.SimpleEntry<>(exceptionType, exceptionHandlerDefinition));
+ }
+ }
+ return exceptionTable;
+ }
+
+ @Override
+ public Void visitVarIdExpr(VarIdExprContext ctx) {
+ addInstruction(new LoadInstruction(newReporterWithToken(ctx.getStart()), ctx.varId().getText(),
+ ctx.getStart().getStartIndex()));
+ return null;
+ }
+
+ private int parsePathHeadPart(PrimaryNoFixPathableContext primaryNoFixPathableContext,
+ List pathPartContexts) {
+ if (primaryNoFixPathableContext instanceof TypeExprContext) {
+ Class> cls = BuiltInTypesSet.getCls(primaryNoFixPathableContext.getStart().getText());
+ int dimPartNum = parseDimParts(0, pathPartContexts);
+ addInstruction(new ConstInstruction(newReporterWithToken(primaryNoFixPathableContext.getStart()),
+ new MetaClass(dimPartNum > 0 ? wrapInArray(cls, dimPartNum) : cls), null));
+ return dimPartNum;
+ }
+ else if (primaryNoFixPathableContext instanceof VarIdExprContext) {
+ VarIdExprContext idContext = (VarIdExprContext)primaryNoFixPathableContext;
+ return parseIdHeadPart(idContext.varId(), pathPartContexts);
+ }
+ else {
+ primaryNoFixPathableContext.accept(this);
+ return 0;
+ }
+ }
+
+ private int parseIdHeadPart(VarIdContext idContext, List pathPartContexts) {
+ if (!pathPartContexts.isEmpty() && pathPartContexts.get(0) instanceof CallExprContext) {
+ // function call
+ CallExprContext callExprContext = (CallExprContext)pathPartContexts.get(0);
+ ArgumentListContext argumentListContext = callExprContext.argumentList();
+ visitCallFunction(idContext, argumentListContext);
+ return 1;
+ }
+ List headPartIds = new ArrayList<>();
+ headPartIds.add(idContext.getText());
+ for (PathPartContext pathPartContext : pathPartContexts) {
+ if (pathPartContext instanceof FieldAccessContext) {
+ headPartIds.add(parseFieldId(((FieldAccessContext)pathPartContext).fieldId()));
+ }
+ else {
+ break;
+ }
+ }
+ ImportManager.LoadPartQualifiedResult loadPartQualifiedResult = importManager.loadPartQualified(headPartIds);
+ if (loadPartQualifiedResult.getCls() != null) {
+ int restIndex = loadPartQualifiedResult.getRestIndex() - 1;
+ Token clsReportToken =
+ restIndex == 0 ? idContext.getStart() : pathPartContexts.get(restIndex - 1).getStop();
+ int dimPartNum = parseDimParts(restIndex, pathPartContexts);
+ Class> cls = dimPartNum > 0 ? wrapInArray(loadPartQualifiedResult.getCls(), dimPartNum)
+ : loadPartQualifiedResult.getCls();
+
+ addInstruction(new ConstInstruction(newReporterWithToken(clsReportToken), new MetaClass(cls), null));
+ return restIndex + dimPartNum;
+ }
+ else {
+ addInstruction(new LoadInstruction(newReporterWithToken(idContext.getStart()), idContext.getText(),
+ idContext.getStart().getStartIndex()));
+ return 0;
+ }
+ }
+
+ private int parseDimParts(int start, List pathPartContexts) {
+ int i = start;
+ for (; i < pathPartContexts.size(); i++) {
+ PathPartContext pathPartContext = pathPartContexts.get(i);
+ if (!isEmptyIndex(pathPartContext)) {
+ break;
+ }
+ }
+ return i - start;
+ }
+
+ private boolean isEmptyIndex(PathPartContext pathPartContext) {
+ if (!(pathPartContext instanceof IndexExprContext)) {
+ return false;
+ }
+ IndexExprContext indexExprContext = (IndexExprContext)pathPartContext;
+ return indexExprContext.indexValueExpr() == null;
+ }
+
+ @Override
+ public Void visitMethodInvoke(MethodInvokeContext ctx) {
+ visitMethodInvokeInner(ctx.argumentList(), ctx.varId(), false);
+ return null;
+ }
+
+ @Override
+ public Void visitOptionalMethodInvoke(OptionalMethodInvokeContext ctx) {
+ visitMethodInvokeInner(ctx.argumentList(), ctx.varId(), true);
+ return null;
+ }
+
+ @Override
+ public Void visitSpreadMethodInvoke(SpreadMethodInvokeContext ctx) {
+ ArgumentListContext argumentListContext = ctx.argumentList();
+ if (argumentListContext != null) {
+ argumentListContext.accept(this);
+ }
+ VarIdContext methodName = ctx.varId();
+ int argNum = argumentListContext == null ? 0 : argumentListContext.expression().size();
+ addInstruction(new SpreadMethodInvokeInstruction(newReporterWithToken(methodName.getStart()),
+ methodName.getText(), argNum));
+ return null;
+ }
+
+ private void visitMethodInvokeInner(ArgumentListContext argumentListContext, VarIdContext methodName,
+ boolean optional) {
+ if (argumentListContext != null) {
+ argumentListContext.accept(this);
+ }
+ int argNum = argumentListContext == null ? 0 : argumentListContext.expression().size();
+ addInstruction(new MethodInvokeInstruction(newReporterWithToken(methodName.getStart()), methodName.getText(),
+ argNum, optional));
+ }
+
+ @Override
+ public Void visitFieldAccess(FieldAccessContext ctx) {
+ Token fieldNameToken = ctx.getStop();
+ addInstruction(
+ new GetFieldInstruction(newReporterWithToken(fieldNameToken), parseFieldId(ctx.fieldId()), false));
+ return null;
+ }
+
+ @Override
+ public Void visitOptionalFieldAccess(OptionalFieldAccessContext ctx) {
+ Token fieldNameToken = ctx.getStop();
+ addInstruction(
+ new GetFieldInstruction(newReporterWithToken(fieldNameToken), parseFieldId(ctx.fieldId()), true));
+ return null;
+ }
+
+ @Override
+ public Void visitSpreadFieldAccess(SpreadFieldAccessContext ctx) {
+ Token fieldNameToken = ctx.getStop();
+ addInstruction(
+ new SpreadGetFieldInstruction(newReporterWithToken(fieldNameToken), parseFieldId(ctx.fieldId())));
+ return null;
+ }
+
+ private String parseFieldId(FieldIdContext ctx) {
+ TerminalNode quoteStringLiteral = ctx.QuoteStringLiteral();
+ if (quoteStringLiteral != null) {
+ return QLStringUtils.parseStringEscape(quoteStringLiteral.getText());
+ }
+ return ctx.getStart().getText();
+ }
+
+ @Override
+ public Void visitMethodAccess(MethodAccessContext ctx) {
+ VarIdContext methodName = ctx.varId();
+ addInstruction(new GetMethodInstruction(newReporterWithToken(ctx.DCOLON().getSymbol()), methodName.getText()));
+ return null;
+ }
+
+ @Override
+ public Void visitCallExpr(CallExprContext ctx) {
+ ArgumentListContext argumentListContext = ctx.argumentList();
+ if (argumentListContext != null) {
+ argumentListContext.accept(this);
+ }
+ int argNum = argumentListContext == null ? 0 : argumentListContext.expression().size();
+ addInstruction(new CallInstruction(newReporterWithToken(ctx.getStart()), argNum));
+ return null;
+ }
+
+ @Override
+ public Void visitIndexExpr(IndexExprContext ctx) {
+ IndexValueExprContext indexValueExprContext = ctx.indexValueExpr();
+ if (indexValueExprContext == null) {
+ throw reportParseErr(ctx.getStop(),
+ QLErrorCodes.MISSING_INDEX.name(),
+ QLErrorCodes.MISSING_INDEX.getErrorMsg());
+ }
+ ErrorReporter errorReporter = newReporterWithToken(ctx.getStart());
+ if (indexValueExprContext instanceof SingleIndexContext) {
+ ((SingleIndexContext)indexValueExprContext).expression().accept(this);
+ addInstruction(new IndexInstruction(errorReporter));
+ }
+ else if (indexValueExprContext instanceof SliceIndexContext) {
+ SliceIndexContext sliceIndexContext = (SliceIndexContext)indexValueExprContext;
+ if (sliceIndexContext.start == null && sliceIndexContext.end == null) {
+ addInstruction(new SliceInstruction(errorReporter, SliceInstruction.Mode.COPY));
+ }
+ else if (sliceIndexContext.start == null) {
+ sliceIndexContext.end.accept(this);
+ addInstruction(new SliceInstruction(errorReporter, SliceInstruction.Mode.LEFT));
+ }
+ else if (sliceIndexContext.end == null) {
+ sliceIndexContext.start.accept(this);
+ addInstruction(new SliceInstruction(errorReporter, SliceInstruction.Mode.RIGHT));
+ }
+ else {
+ sliceIndexContext.start.accept(this);
+ sliceIndexContext.end.accept(this);
+ addInstruction(new SliceInstruction(errorReporter, SliceInstruction.Mode.BOTH));
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public Void visitCustomPath(CustomPathContext ctx) {
+ ErrorReporter errorReporter = newReporterWithToken(ctx.getStart());
+ String path = ctx.varId().getText();
+ addInstruction(new ConstInstruction(errorReporter, path, null));
+
+ String operatorId = ctx.opId().getText();
+ BinaryOperator binaryOperator = operatorFactory.getBinaryOperator(operatorId);
+ addInstruction(new OperatorInstruction(errorReporter, binaryOperator, ctx.opId().getStart().getStartIndex()));
+ return null;
+ }
+
+ @Override
+ public Void visitLeftAsso(LeftAssoContext ctx) {
+ BinaryopContext binaryopContext = ctx.binaryop();
+ ErrorReporter opErrReporter = newReporterWithToken(binaryopContext.getStart());
+ String operatorId = binaryopContext.getText();
+ BaseExprContext rightExpr = ctx.baseExpr();
+ // short circuit operator
+ if ("&&".equals(operatorId)) {
+ jumpRightIfExpect(false, opErrReporter, rightExpr, operatorId, binaryopContext.getStart().getStartIndex());
+ }
+ else if ("||".equals(operatorId)) {
+ jumpRightIfExpect(true, opErrReporter, rightExpr, operatorId, binaryopContext.getStart().getStartIndex());
+ }
+ else {
+ rightExpr.accept(this);
+ BinaryOperator binaryOperator = operatorFactory.getBinaryOperator(operatorId);
+ addInstruction(
+ new OperatorInstruction(opErrReporter, binaryOperator, binaryopContext.getStart().getStartIndex()));
+ }
+ return null;
+ }
+
+ @Override
+ public Void visitMacroStatement(MacroStatementContext ctx) {
+ String macroId = ctx.varId().getText();
+
+ BlockStatementsContext macroBlockStatementsContext = ctx.blockStatements();
+
+ BlockStatementContext lastStmt = getMacroLastStmt(ctx, macroBlockStatementsContext);
+ generatorScope.defineMacro(macroId,
+ new MacroDefine(getMacroInstructions(macroBlockStatementsContext),
+ lastStmt instanceof ExpressionStatementContext));
+
+ if (initOptions.isTraceExpression()) {
+ pureAddInstruction(new TraceEvaludatedInstruction(newReporterWithToken(ctx.varId().getStart()),
+ ctx.varId().getStart().getStartIndex()));
+ }
+
+ return null;
+ }
+
+ private BlockStatementContext getMacroLastStmt(MacroStatementContext macroCtx,
+ BlockStatementsContext macroBlockStatementsCtx) {
+ if (macroBlockStatementsCtx == null) {
+ return macroCtx;
+ }
+ List blockStatementContexts = macroBlockStatementsCtx.blockStatement()
+ .stream()
+ .filter(bs -> !(bs instanceof EmptyStatementContext))
+ .collect(Collectors.toList());
+ return blockStatementContexts.isEmpty() ? macroCtx
+ : blockStatementContexts.get(blockStatementContexts.size() - 1);
+ }
+
+ private List getMacroInstructions(BlockStatementsContext macroBlockStatementsContext) {
+ if (macroBlockStatementsContext == null) {
+ return Collections.emptyList();
+ }
+ else {
+ QvmInstructionVisitor subVisitor = parseWithSubVisitor(macroBlockStatementsContext,
+ new GeneratorScope(macroScopeName(), generatorScope),
+ Context.MACRO);
+ return subVisitor.getInstructions();
+ }
+ }
+
+ @Override
+ public Void visitLocalVariableDeclaration(LocalVariableDeclarationContext ctx) {
+ if (initOptions.isTraceExpression()) {
+ addInstruction(
+ new TraceEvaludatedInstruction(newReporterWithToken(ctx.getStart()), ctx.getStart().getStartIndex()));
+ }
+
+ Class> declCls = parseDeclType(ctx.declType());
+ List variableDeclaratorContexts = ctx.variableDeclaratorList().variableDeclarator();
+ for (VariableDeclaratorContext variableDeclarator : variableDeclaratorContexts) {
+ VariableInitializerContext variableInitializer = variableDeclarator.variableInitializer();
+ if (variableInitializer == null) {
+ addInstruction(new ConstInstruction(newReporterWithToken(variableDeclarator.getStop()), null, null));
+ }
+ else {
+ parseInitializer(variableInitializer, declCls);
+ }
+ VariableDeclaratorIdContext variableDeclaratorIdContext = variableDeclarator.variableDeclaratorId();
+ addInstruction(new DefineLocalInstruction(newReporterWithToken(variableDeclaratorIdContext.getStart()),
+ variableDeclaratorIdContext.getText(), declCls));
+ }
+ return null;
+ }
+
+ private void parseInitializer(VariableInitializerContext variableInitializer, Class> declCls) {
+ ExpressionContext expression = variableInitializer.expression();
+ if (expression != null) {
+ expression.accept(this);
+ return;
+ }
+ ArrayInitializerContext arrayInitializerContext = variableInitializer.arrayInitializer();
+ newArrWithInitializers(declCls, arrayInitializerContext);
+ }
+
+ private void newArrWithInitializers(Class> componentClz, ArrayInitializerContext arrayInitializerContext) {
+ VariableInitializerListContext variableInitializerListContext =
+ arrayInitializerContext.variableInitializerList();
+ List initializerContexts =
+ variableInitializerListContext == null ? Collections.emptyList()
+ : variableInitializerListContext.variableInitializer();
+ for (VariableInitializerContext variableInitializerContext : initializerContexts) {
+ variableInitializerContext.accept(this);
+ }
+ addInstruction(new NewArrayInstruction(newReporterWithToken(arrayInitializerContext.getStart()), componentClz,
+ initializerContexts.size()));
+ }
+
+ @Override
+ public Void visitLeftHandSide(LeftHandSideContext ctx) {
+ VarIdContext idContext = ctx.varId();
+ List pathPartContexts = ctx.pathPart();
+ if (pathPartContexts.size() == 1 && pathPartContexts.get(0) instanceof CallExprContext) {
+ CallExprContext callExprContext = (CallExprContext)pathPartContexts.get(0);
+ ArgumentListContext argumentListContext = callExprContext.argumentList();
+ visitCallFunction(idContext, argumentListContext);
+ }
+ else {
+ int tailPartStart = parseIdHeadPart(idContext, pathPartContexts);
+ for (int i = tailPartStart; i < pathPartContexts.size(); i++) {
+ pathPartContexts.get(i).accept(this);
+ }
+ }
+ return null;
+ }
+
+ private void visitCallFunction(VarIdContext functionNameContext, ArgumentListContext argumentListContext) {
+ String functionName = functionNameContext.getText();
+ CompileTimeFunction compileTimeFunction = compileTimeFunctions.get(functionName);
+ if (compileTimeFunction != null) {
+ ErrorReporter functionNameReporter = newReporterWithToken(functionNameContext.getStart());
+ compileTimeFunction.createFunctionInstruction(functionName,
+ argumentListContext == null ? Collections.emptyList() : argumentListContext.expression(),
+ operatorFactory,
+ new CodeGenerator() {
+ @Override
+ public void addInstruction(QLInstruction qlInstruction) {
+ QvmInstructionVisitor.this.addInstruction(qlInstruction);
+ }
+
+ @Override
+ public void addInstructionsByTree(ParseTree tree) {
+ tree.accept(QvmInstructionVisitor.this);
+ }
+
+ @Override
+ public QLSyntaxException reportParseErr(String errCode, String errReason) {
+ return QvmInstructionVisitor.this
+ .reportParseErr(functionNameContext.getStart(), errCode, errReason);
+ }
+
+ @Override
+ public QLambdaDefinition generateLambdaDefinition(ExpressionContext expressionContext,
+ List params) {
+ QvmInstructionVisitor subVisitor =
+ parseExprBodyWithSubVisitor(expressionContext, generatorScope, context);
+ return new QLambdaDefinitionInner(functionName, subVisitor.getInstructions(), params,
+ subVisitor.getMaxStackSize());
+ }
+
+ @Override
+ public ErrorReporter getErrorReporter() {
+ return functionNameReporter;
+ }
+
+ @Override
+ public ErrorReporter newReporterWithToken(Token token) {
+ return QvmInstructionVisitor.this.newReporterWithToken(token);
+ }
+ }
+
+ );
+ return;
+ }
+
+ if (argumentListContext != null) {
+ argumentListContext.accept(this);
+ }
+ int argSize = argumentListContext == null ? 0 : argumentListContext.expression().size();
+ addInstruction(new CallFunctionInstruction(newReporterWithToken(functionNameContext.getStart()), functionName,
+ argSize, functionNameContext.getStart().getStartIndex()));
+ }
+
+ @Override
+ public Void visitPrimary(PrimaryContext ctx) {
+ if (ctx.primaryNoFixNonPathable() != null) {
+ ctx.primaryNoFixNonPathable().accept(this);
+ return null;
+ }
+ PrimaryNoFixPathableContext primaryNoFixPathableContext = ctx.primaryNoFixPathable();
+ List pathPartContexts = ctx.pathPart();
+
+ // path
+ // head part
+ int tailPartStart = parsePathHeadPart(primaryNoFixPathableContext, pathPartContexts);
+
+ // tail part
+ for (int i = tailPartStart; i < pathPartContexts.size(); i++) {
+ pathPartContexts.get(i).accept(this);
+ }
+
+ SuffixExpressContext suffixExpressContext = ctx.suffixExpress();
+ if (suffixExpressContext != null) {
+ String suffixOperator = suffixExpressContext.getText();
+ UnaryOperator suffixUnaryOperator = operatorFactory.getSuffixUnaryOperator(suffixOperator);
+ addInstruction(new UnaryInstruction(newReporterWithToken(suffixExpressContext.getStart()),
+ suffixUnaryOperator, suffixExpressContext.getStart().getStartIndex()));
+ }
+
+ PrefixExpressContext prefixExpressContext = ctx.prefixExpress();
+ if (prefixExpressContext != null) {
+ String prefixOperator = prefixExpressContext.getText();
+ UnaryOperator prefixUnaryOperator = operatorFactory.getPrefixUnaryOperator(prefixOperator);
+ addInstruction(new UnaryInstruction(newReporterWithToken(prefixExpressContext.getStart()),
+ prefixUnaryOperator, prefixExpressContext.getStart().getStartIndex()));
+ }
+ return null;
+ }
+
+ @Override
+ public Void visitTypeExpr(TypeExprContext ctx) {
+ Class> cls = BuiltInTypesSet.getCls(ctx.primitiveType().getText());
+ addInstruction(new ConstInstruction(newReporterWithToken(ctx.getStart()), new MetaClass(cls), null));
+ return null;
+ }
+
+ @Override
+ public Void visitArrayInitializer(ArrayInitializerContext ctx) {
+ return super.visitArrayInitializer(ctx);
+ }
+
+ @Override
+ public Void visitConstExpr(ConstExprContext ctx) {
+ ctx.literal().accept(this);
+ return null;
+ }
+
+ @Override
+ public Void visitContextSelectExpr(ContextSelectExprContext ctx) {
+ String variableName = ctx.SelectorVariable_VANME().getText().trim();
+ addInstruction(
+ new LoadInstruction(newReporterWithToken(ctx.getStart()), variableName, ctx.getStart().getStartIndex()));
+ return null;
+ }
+
+ @Override
+ public Void visitLiteral(LiteralContext literal) {
+ TerminalNode integerLiteral = literal.IntegerLiteral();
+ if (integerLiteral != null) {
+ try {
+ Number intResult = parseInteger(remove(integerLiteral.getText(), '_'));
+ addInstruction(new ConstInstruction(newReporterWithToken(integerLiteral.getSymbol()), intResult,
+ integerLiteral.getSymbol().getStartIndex()));
+ }
+ catch (NumberFormatException nfe) {
+ throw reportParseErr(integerLiteral.getSymbol(),
+ QLErrorCodes.INVALID_NUMBER.name(),
+ QLErrorCodes.INVALID_NUMBER.getErrorMsg());
+ }
+ return null;
+ }
+ TerminalNode floatingPointLiteral = literal.FloatingPointLiteral();
+ if (floatingPointLiteral != null) {
+ try {
+ Number floatingResult = parseFloating(remove(floatingPointLiteral.getText(), '_'));
+ addInstruction(new ConstInstruction(newReporterWithToken(floatingPointLiteral.getSymbol()),
+ floatingResult, floatingPointLiteral.getSymbol().getStartIndex()));
+ }
+ catch (NumberFormatException nfe) {
+ throw reportParseErr(floatingPointLiteral.getSymbol(),
+ QLErrorCodes.INVALID_NUMBER.name(),
+ QLErrorCodes.INVALID_NUMBER.getErrorMsg());
+ }
+ return null;
+ }
+ TerminalNode integerOrFloatingLiteral = literal.IntegerOrFloatingLiteral();
+ if (integerOrFloatingLiteral != null) {
+ try {
+ String numberText = integerOrFloatingLiteral.getText();
+ Number numberResult = numberText.contains(".") ? parseFloating(remove(numberText, '_'))
+ : parseInteger(remove(numberText, '_'));
+ addInstruction(new ConstInstruction(newReporterWithToken(integerOrFloatingLiteral.getSymbol()),
+ numberResult, integerOrFloatingLiteral.getSymbol().getStartIndex()));
+ }
+ catch (NumberFormatException nfe) {
+ throw reportParseErr(integerOrFloatingLiteral.getSymbol(),
+ QLErrorCodes.INVALID_NUMBER.name(),
+ QLErrorCodes.INVALID_NUMBER.getErrorMsg());
+ }
+ return null;
+ }
+ BoolenLiteralContext booleanLiteral = literal.boolenLiteral();
+ if (booleanLiteral != null) {
+ boolean boolValue = Boolean.parseBoolean(booleanLiteral.getText());
+ addInstruction(new ConstInstruction(newReporterWithToken(booleanLiteral.getStart()), boolValue,
+ booleanLiteral.getStart().getStartIndex()));
+ return null;
+ }
+ TerminalNode quoteStringLiteral = literal.QuoteStringLiteral();
+ if (quoteStringLiteral != null) {
+ String escapedStr = QLStringUtils.parseStringEscape(quoteStringLiteral.getText());
+ addInstruction(new ConstInstruction(newReporterWithToken(quoteStringLiteral.getSymbol()), escapedStr,
+ quoteStringLiteral.getSymbol().getStartIndex()));
+ return null;
+ }
+ DoubleQuoteStringLiteralContext doubleQuoteStringLiteral = literal.doubleQuoteStringLiteral();
+ if (doubleQuoteStringLiteral != null) {
+ visitDoubleQuoteStringLiteral(doubleQuoteStringLiteral);
+ return null;
+ }
+ TerminalNode nullLiteral = literal.NULL();
+ if (nullLiteral != null) {
+ addInstruction(new ConstInstruction(newReporterWithToken(nullLiteral.getSymbol()), null,
+ nullLiteral.getSymbol().getStartIndex()));
+ return null;
+ }
+ return null;
+ }
+
+ @Override
+ public Void visitDoubleQuoteStringLiteral(DoubleQuoteStringLiteralContext ctx) {
+ if (initOptions.getInterpolationMode() == InterpolationMode.DISABLE) {
+ TerminalNode characters = ctx.StaticStringCharacters();
+ if (characters == null) {
+ addInstruction(new ConstInstruction(newReporterWithToken(ctx.getStart()), "", null));
+ return null;
+ }
+ String originText = characters.getText();
+ addInstruction(new ConstInstruction(newReporterWithToken(ctx.getStart()),
+ QLStringUtils.parseStringEscapeStartEnd(originText, 0, originText.length()), null));
+ return null;
+ }
+ int childCount = ctx.getChildCount();
+ for (int i = 1; i < childCount - 1; i++) {
+ ParseTree child = ctx.getChild(i);
+ if (child instanceof StringExpressionContext) {
+ StringExpressionContext stringExpression = (StringExpressionContext)child;
+ ExpressionContext expression = stringExpression.expression();
+ if (expression != null) {
+ // SCRIPT
+ visitExpression(expression);
+ }
+ else {
+ // VARIABLE
+ TerminalNode varTerminalNode = stringExpression.SelectorVariable_VANME();
+ String varName = varTerminalNode.getText().trim();
+ addInstruction(
+ new LoadInstruction(newReporterWithToken(varTerminalNode.getSymbol()), varName, null));
+ }
+ }
+ else if (child instanceof TerminalNode) {
+ TerminalNode terminalNode = (TerminalNode)child;
+ String originStr = terminalNode.getText();
+ addInstruction(new ConstInstruction(newReporterWithToken(terminalNode.getSymbol()),
+ QLStringUtils.parseStringEscapeStartEnd(originStr, 0, originStr.length()),
+ ctx.getStart().getStartIndex()));
+ }
+ }
+ addInstruction(new StringJoinInstruction(newReporterWithToken(ctx.getStart()), childCount - 2));
+ return null;
+ }
+
+ private Number parseFloating(String floatingText) {
+ char floatingTypeFlag = floatingText.charAt(floatingText.length() - 1);
+ switch (floatingTypeFlag) {
+ case 'f':
+ case 'F':
+ return new BigDecimal(floatingText.substring(0, floatingText.length() - 1)).floatValue();
+ case 'd':
+ case 'D':
+ return new BigDecimal(floatingText.substring(0, floatingText.length() - 1)).doubleValue();
+ default:
+ BigDecimal baseDecimal = new BigDecimal(floatingText);
+ return baseDecimal.compareTo(MAX_DOUBLE) <= 0 ? maybePresentWithDouble(baseDecimal) : baseDecimal;
+ }
+ }
+
+ private Number maybePresentWithDouble(BigDecimal origin) {
+ double doubleValue = origin.doubleValue();
+ BigDecimal reference = new BigDecimal(doubleValue);
+ return reference.compareTo(origin) == 0 ? doubleValue : origin;
+ }
+
+ private Number parseInteger(String intText) {
+ char intTypeFlag = intText.charAt(intText.length() - 1);
+ switch (intTypeFlag) {
+ case 'l':
+ case 'L':
+ String baseIntText = intText.substring(0, intText.length() - 1);
+ BigInteger baseInt = parseBaseInteger(baseIntText);
+ return baseInt.longValue();
+ default:
+ // auto type
+ baseInt = parseBaseInteger(intText);
+ if (baseInt.compareTo(MAX_INTEGER) <= 0) {
+ return baseInt.intValue();
+ }
+ else if (baseInt.compareTo(MAX_LONG) <= 0) {
+ return baseInt.longValue();
+ }
+ else {
+ return baseInt;
+ }
+ }
+ }
+
+ private BigInteger parseBaseInteger(String intText) {
+ String radixPrefix = subString(intText, 2);
+ switch (radixPrefix) {
+ case "0x":
+ case "0X":
+ // radix 16
+ return new BigInteger(intText.substring(2), 16);
+ case "0b":
+ case "0B":
+ // radix 2
+ return new BigInteger(intText.substring(2), 2);
+ default:
+ if (radixPrefix.startsWith("0")) {
+ // radix 8
+ return new BigInteger(intText, 8);
+ }
+ else {
+ // radix 10
+ return new BigInteger(intText);
+ }
+ }
+ }
+
+ private Class> parseDeclTypeNoArr(DeclTypeNoArrContext declTypeNoArrContext) {
+ PrimitiveTypeContext primitiveTypeContext = declTypeNoArrContext.primitiveType();
+ if (primitiveTypeContext != null) {
+ return BuiltInTypesSet.getCls(primitiveTypeContext.getText());
+ }
+
+ ClsTypeContext clsTypeContext = declTypeNoArrContext.clsType();
+ return parseClsIds(clsTypeContext.varId());
+ }
+
+ private Class> parseDeclType(DeclTypeContext declTypeContext) {
+ Class> baseCls = parseDeclBaseCls(declTypeContext);
+ DimsContext dims = declTypeContext.dims();
+ int layers = dims == null ? 0 : dims.LBRACK().size();
+ return wrapInArray(baseCls, layers);
+ }
+
+ private Class> parseDeclBaseCls(DeclTypeContext declTypeContext) {
+ PrimitiveTypeContext primitiveTypeContext = declTypeContext.primitiveType();
+ if (primitiveTypeContext != null) {
+ return BuiltInTypesSet.getCls(primitiveTypeContext.getText());
+ }
+ ClsTypeContext clsTypeContext = declTypeContext.clsType();
+ return parseClsIds(clsTypeContext.varId());
+ }
+
+ private String remove(String target, char c) {
+ StringBuilder builder = new StringBuilder(target.length());
+ for (int i = 0; i < target.length(); i++) {
+ char iChar = target.charAt(i);
+ if (iChar != c) {
+ builder.append(iChar);
+ }
+ }
+ return builder.toString();
+ }
+
+ private String subString(String target, int end) {
+ if (end > target.length()) {
+ return target;
+ }
+ return target.substring(0, end);
+ }
+
+ private Class> wrapInArray(Class> baseType, int layers) {
+ for (int i = 0; i < layers; i++) {
+ baseType = Array.newInstance(baseType, 0).getClass();
+ }
+ return baseType;
+ }
+
+ private Class> parseClsIds(List varIdContexts) {
+ List fieldIds = varIdContexts.stream().map(RuleContext::getText).collect(Collectors.toList());
+ ImportManager.LoadPartQualifiedResult loadPartQualifiedResult = importManager.loadPartQualified(fieldIds);
+ if (loadPartQualifiedResult.getCls() == null || loadPartQualifiedResult.getRestIndex() != fieldIds.size()) {
+ Token lastIdToken = varIdContexts.get(varIdContexts.size() - 1).getStart();
+ throw reportParseErr(lastIdToken,
+ QLErrorCodes.CLASS_NOT_FOUND.name(),
+ String.format(QLErrorCodes.CLASS_NOT_FOUND.getErrorMsg(), String.join(".", fieldIds)));
+ }
+ return loadPartQualifiedResult.getCls();
+ }
+
+ private QLSyntaxException reportParseErr(Token token, String errCode, String errReason) {
+ return QLException.reportScannerErr(script,
+ token.getStartIndex(),
+ token.getLine(),
+ token.getCharPositionInLine(),
+ token.getText(),
+ errCode,
+ errReason);
+ }
+
+ public List getInstructions() {
+ return instructionList;
+ }
+
+ public int getMaxStackSize() {
+ return maxStackSize;
+ }
+
+ private void ifElseInstructions(ErrorReporter conditionReporter, List thenInstructions,
+ Integer thenTraceKey, List elseInstructions, Integer elseTraceKey, int traceKey) {
+ JumpIfPopInstruction jumpIf = new JumpIfPopInstruction(conditionReporter, false, -1);
+ pureAddInstruction(jumpIf);
+ int jumpStart = instructionList.size();
+ thenInstructions.forEach(this::pureAddInstruction);
+ if (initOptions.isTraceExpression()) {
+ if (thenTraceKey != null) {
+ pureAddInstruction(new TracePeekInstruction(conditionReporter, thenTraceKey));
+ }
+ pureAddInstruction(new TracePeekInstruction(conditionReporter, traceKey));
+ }
+ addTimeoutInstruction();
+
+ JumpInstruction jump = new JumpInstruction(conditionReporter, -1);
+ pureAddInstruction(jump);
+
+ jumpIf.setPosition(instructionList.size() - jumpStart);
+
+ jumpStart = instructionList.size();
+ elseInstructions.forEach(this::pureAddInstruction);
+ if (initOptions.isTraceExpression()) {
+ if (elseTraceKey != null) {
+ pureAddInstruction(new TracePeekInstruction(conditionReporter, elseTraceKey));
+ }
+ pureAddInstruction(new TracePeekInstruction(conditionReporter, traceKey));
+ }
+ addTimeoutInstruction();
+ jump.setPosition(instructionList.size() - jumpStart);
+ }
+
+ private void jumpRightIfExpect(boolean expect, ErrorReporter opErrReporter, RuleContext right, String operatorId,
+ int traceKey) {
+ QvmInstructionVisitor rightVisitor = parseWithSubVisitor(right, generatorScope, Context.MACRO);
+ List rightInstructions = rightVisitor.getInstructions();
+
+ JumpIfInstruction jumpIf = new JumpIfInstruction(opErrReporter, expect, -1, traceKey);
+ pureAddInstruction(jumpIf);
+
+ int jumpStart = instructionList.size();
+
+ rightInstructions.forEach(this::pureAddInstruction);
+ BinaryOperator binaryOperator = operatorFactory.getBinaryOperator(operatorId);
+ addInstruction(new OperatorInstruction(opErrReporter, binaryOperator, traceKey));
+ addTimeoutInstruction();
+
+ jumpIf.setPosition(instructionList.size() - jumpStart);
+ }
+
+ private QvmInstructionVisitor parseWithSubVisitor(RuleContext ruleContext, GeneratorScope generatorScope,
+ Context context) {
+ QvmInstructionVisitor subVisitor = new QvmInstructionVisitor(script, importManager, generatorScope,
+ operatorFactory, context, compileTimeFunctions, initOptions);
+ ruleContext.accept(subVisitor);
+ return subVisitor;
+ }
+
+ private QvmInstructionVisitor parseExprBodyWithSubVisitor(ExpressionContext expressionContext,
+ GeneratorScope generatorScope, Context context) {
+ QvmInstructionVisitor subVisitor = new QvmInstructionVisitor(script, importManager, generatorScope,
+ operatorFactory, context, compileTimeFunctions, initOptions);
+ // reduce the level of syntax tree when expression is a block
+ subVisitor.visitBodyExpression(expressionContext);
+ return subVisitor;
+ }
+
+ private boolean handleStmt(BlockStatementContext statementContext) {
+ if (maybeMacroCall(statementContext)) {
+ String macroName = statementContext.getStart().getText();
+ MacroDefine macroDefine = generatorScope.getMacroInstructions(macroName);
+ if (macroDefine != null) {
+ macroDefine.getMacroInstructions().forEach(this::pureAddInstruction);
+ addTimeoutInstruction();
+ return macroDefine.isLastStmtExpress();
+ }
+ }
+ statementContext.accept(this);
+ return statementContext instanceof ExpressionStatementContext;
+ }
+
+ private boolean maybeMacroCall(BlockStatementContext statementContext) {
+ if (statementContext instanceof ExpressionStatementContext) {
+ ExpressionContext expressionContext = ((ExpressionStatementContext)statementContext).expression();
+ if (expressionContext != null) {
+ if (expressionContext.getStart() == expressionContext.getStop()) {
+ return expressionContext.getStart().getType() == QLexer.ID;
+ }
+ }
+ }
+ return false;
+ }
+
+ private void pureAddInstruction(QLInstruction qlInstruction) {
+ int stackExpandSize = qlInstruction.stackOutput() - qlInstruction.stackInput();
+ expandStackSize(stackExpandSize);
+ instructionList.add(qlInstruction);
+ }
+
+ private void addInstruction(QLInstruction qlInstruction) {
+ if (instructionList.size() - timeoutCheckPoint > TIMEOUT_CHECK_GAP) {
+ addTimeoutInstruction();
+ }
+ pureAddInstruction(qlInstruction);
+ if (qlInstruction instanceof MethodInvokeInstruction || qlInstruction instanceof CallFunctionInstruction
+ || qlInstruction instanceof CallConstInstruction || qlInstruction instanceof CallInstruction) {
+ addTimeoutInstruction();
+ }
+ }
+
+ private void addTimeoutInstruction() {
+ QLInstruction lastInstruction = instructionList.get(instructionList.size() - 1);
+ if (lastInstruction instanceof CheckTimeOutInstruction) {
+ return;
+ }
+ this.timeoutCheckPoint = instructionList.size();
+ instructionList.add(new CheckTimeOutInstruction(lastInstruction.getErrorReporter()));
+ }
+
+ private void expandStackSize(int stackExpandSize) {
+ stackSize += stackExpandSize;
+ if (stackSize > maxStackSize) {
+ maxStackSize = stackSize;
+ }
+ }
+
+ private ErrorReporter newReporterWithToken(Token token) {
+ return new DefaultErrReporter(script, token.getStartIndex(), token.getLine(), token.getCharPositionInLine(),
+ token.getText());
+ }
+
+ private int whileCount() {
+ return whileCounter++;
+ }
+
+ private int forCount() {
+ return forCounter++;
+ }
+
+ private int ifCount() {
+ return ifCounter++;
+ }
+
+ private int tryCount() {
+ return tryCounter++;
+ }
+
+ private String blockScopeName() {
+ return generatorScope.getName() + SCOPE_SEPARATOR + BLOCK_LAMBDA_NAME_PREFIX + blockCounter++;
+ }
+
+ private String macroScopeName() {
+ return generatorScope.getName() + SCOPE_SEPARATOR + MACRO_PREFIX + macroCounter++;
+ }
+
+ private String lambdaScopeName() {
+ return generatorScope.getName() + SCOPE_SEPARATOR + LAMBDA_PREFIX + lambdaCounter++;
+ }
+
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/aparser/ScopeStackVisitor.java b/src/main/java/com/alibaba/qlexpress4/aparser/ScopeStackVisitor.java
new file mode 100644
index 0000000..9b0f191
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/aparser/ScopeStackVisitor.java
@@ -0,0 +1,99 @@
+package com.alibaba.qlexpress4.aparser;
+
+public abstract class ScopeStackVisitor extends QLParserBaseVisitor {
+
+ 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;
+ }
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/aparser/SyntaxTreeFactory.java b/src/main/java/com/alibaba/qlexpress4/aparser/SyntaxTreeFactory.java
new file mode 100644
index 0000000..33256da
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/aparser/SyntaxTreeFactory.java
@@ -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 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;
+ }
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/aparser/TraceExpressionVisitor.java b/src/main/java/com/alibaba/qlexpress4/aparser/TraceExpressionVisitor.java
new file mode 100644
index 0000000..78c6d44
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/aparser/TraceExpressionVisitor.java
@@ -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 {
+
+ private final List expressionTracePoints = new ArrayList<>();
+
+ public List 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 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 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 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 children = traceExpressionVisitor.getExpressionTracePoints();
+ return newPoint(TraceType.BLOCK, children, ctx.getStart());
+ }
+
+ @Override
+ public TracePointTree visitQlIf(QLParser.QlIfContext ctx) {
+ List 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 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 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 argumentsChildren = traceArgumentList(argumentList);
+ List 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 argumentsChildren = traceArgumentList(argumentList);
+ List 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 indexArgChildren =
+ indexValueExprContext.getRuleContexts(QLParser.ExpressionContext.class)
+ .stream()
+ .map(expression -> expression.accept(this))
+ .collect(Collectors.toList());
+ List 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 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 children, Token keyToken) {
+ return new TracePointTree(traceType, keyToken.getText(), children, keyToken.getLine(),
+ keyToken.getCharPositionInLine(), keyToken.getStartIndex());
+ }
+
+ private TracePointTree newPoint(TraceType traceType, List children, String text, Token keyToken) {
+ return new TracePointTree(traceType, text, children, keyToken.getLine(), keyToken.getCharPositionInLine(),
+ keyToken.getStartIndex());
+ }
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/aparser/compiletimefunction/CodeGenerator.java b/src/main/java/com/alibaba/qlexpress4/aparser/compiletimefunction/CodeGenerator.java
new file mode 100644
index 0000000..1638280
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/aparser/compiletimefunction/CodeGenerator.java
@@ -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 params);
+
+ ErrorReporter getErrorReporter();
+
+ ErrorReporter newReporterWithToken(Token token);
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/aparser/compiletimefunction/CompileTimeFunction.java b/src/main/java/com/alibaba/qlexpress4/aparser/compiletimefunction/CompileTimeFunction.java
new file mode 100644
index 0000000..7444227
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/aparser/compiletimefunction/CompileTimeFunction.java
@@ -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 arguments,
+ OperatorFactory operatorFactory, CodeGenerator codeGenerator);
+
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/api/BatchAddFunctionResult.java b/src/main/java/com/alibaba/qlexpress4/api/BatchAddFunctionResult.java
new file mode 100644
index 0000000..ad84a41
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/api/BatchAddFunctionResult.java
@@ -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 succ;
+
+ private final List fail;
+
+ public BatchAddFunctionResult() {
+ this.succ = new ArrayList<>();
+ this.fail = new ArrayList<>();
+ }
+
+ public List getSucc() {
+ return succ;
+ }
+
+ public List getFail() {
+ return fail;
+ }
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/api/QLFunctionalVarargs.java b/src/main/java/com/alibaba/qlexpress4/api/QLFunctionalVarargs.java
new file mode 100644
index 0000000..5d373c4
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/api/QLFunctionalVarargs.java
@@ -0,0 +1,9 @@
+package com.alibaba.qlexpress4.api;
+
+/**
+ * Author: TaoKan
+ */
+@FunctionalInterface
+public interface QLFunctionalVarargs {
+ Object call(Object... params);
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/enums/AccessMode.java b/src/main/java/com/alibaba/qlexpress4/enums/AccessMode.java
new file mode 100644
index 0000000..0ffd7cf
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/enums/AccessMode.java
@@ -0,0 +1,8 @@
+package com.alibaba.qlexpress4.enums;
+
+/**
+ * Author: TaoKan
+ */
+public enum AccessMode {
+ WRITE, READ,
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/exception/DefaultErrReporter.java b/src/main/java/com/alibaba/qlexpress4/exception/DefaultErrReporter.java
new file mode 100644
index 0000000..04aeee9
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/exception/DefaultErrReporter.java
@@ -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);
+ }
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/exception/ErrorReporter.java b/src/main/java/com/alibaba/qlexpress4/exception/ErrorReporter.java
new file mode 100644
index 0000000..d9e7b69
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/exception/ErrorReporter.java
@@ -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);
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/exception/ExMessageUtil.java b/src/main/java/com/alibaba/qlexpress4/exception/ExMessageUtil.java
new file mode 100644
index 0000000..b0aeb33
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/exception/ExMessageUtil.java
@@ -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);
+ }
+
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/exception/ExceptionFactory.java b/src/main/java/com/alibaba/qlexpress4/exception/ExceptionFactory.java
new file mode 100644
index 0000000..72cf33e
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/exception/ExceptionFactory.java
@@ -0,0 +1,11 @@
+package com.alibaba.qlexpress4.exception;
+
+/**
+ * Author: DQinYuan
+ */
+public interface ExceptionFactory {
+
+ T newException(String message, int lineNo, int colNo, String errLexeme, String errorCode, String reason,
+ String snippet);
+
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/exception/PureErrReporter.java b/src/main/java/com/alibaba/qlexpress4/exception/PureErrReporter.java
new file mode 100644
index 0000000..333a03b
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/exception/PureErrReporter.java
@@ -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);
+ }
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/exception/QLErrorCodes.java b/src/main/java/com/alibaba/qlexpress4/exception/QLErrorCodes.java
new file mode 100644
index 0000000..8e2c7b7
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/exception/QLErrorCodes.java
@@ -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;
+ }
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/exception/QLException.java b/src/main/java/com/alibaba/qlexpress4/exception/QLException.java
new file mode 100644
index 0000000..84a8140
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/exception/QLException.java
@@ -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);
+ }
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/exception/QLRuntimeException.java b/src/main/java/com/alibaba/qlexpress4/exception/QLRuntimeException.java
new file mode 100644
index 0000000..d486bda
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/exception/QLRuntimeException.java
@@ -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;
+ }
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/exception/QLSyntaxException.java b/src/main/java/com/alibaba/qlexpress4/exception/QLSyntaxException.java
new file mode 100644
index 0000000..764dff7
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/exception/QLSyntaxException.java
@@ -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);
+ }
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/exception/QLTimeoutException.java b/src/main/java/com/alibaba/qlexpress4/exception/QLTimeoutException.java
new file mode 100644
index 0000000..cdb197d
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/exception/QLTimeoutException.java
@@ -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);
+ }
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/exception/UserDefineException.java b/src/main/java/com/alibaba/qlexpress4/exception/UserDefineException.java
new file mode 100644
index 0000000..3d3a891
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/exception/UserDefineException.java
@@ -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;
+ }
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/exception/lsp/Diagnostic.java b/src/main/java/com/alibaba/qlexpress4/exception/lsp/Diagnostic.java
new file mode 100644
index 0000000..2cdd888
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/exception/lsp/Diagnostic.java
@@ -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;
+ }
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/exception/lsp/Position.java b/src/main/java/com/alibaba/qlexpress4/exception/lsp/Position.java
new file mode 100644
index 0000000..6567f88
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/exception/lsp/Position.java
@@ -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;
+ }
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/exception/lsp/Range.java b/src/main/java/com/alibaba/qlexpress4/exception/lsp/Range.java
new file mode 100644
index 0000000..9da5a48
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/exception/lsp/Range.java
@@ -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;
+ }
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/member/FieldHandler.java b/src/main/java/com/alibaba/qlexpress4/member/FieldHandler.java
new file mode 100644
index 0000000..f22ecd3
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/member/FieldHandler.java
@@ -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;
+ }
+ }
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/member/MethodHandler.java b/src/main/java/com/alibaba/qlexpress4/member/MethodHandler.java
new file mode 100644
index 0000000..ae89ac3
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/member/MethodHandler.java
@@ -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;
+ }
+ }
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/operator/BlackOperatorCheckStrategy.java b/src/main/java/com/alibaba/qlexpress4/operator/BlackOperatorCheckStrategy.java
new file mode 100644
index 0000000..66f21f5
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/operator/BlackOperatorCheckStrategy.java
@@ -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 blackOperators;
+
+ public BlackOperatorCheckStrategy(Set 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 getOperators() {
+ return blackOperators;
+ }
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/operator/DefaultOperatorCheckStrategy.java b/src/main/java/com/alibaba/qlexpress4/operator/DefaultOperatorCheckStrategy.java
new file mode 100644
index 0000000..261873f
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/operator/DefaultOperatorCheckStrategy.java
@@ -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 getOperators() {
+ return Collections.emptySet();
+ }
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/operator/OperatorCheckStrategy.java b/src/main/java/com/alibaba/qlexpress4/operator/OperatorCheckStrategy.java
new file mode 100644
index 0000000..4b85213
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/operator/OperatorCheckStrategy.java
@@ -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:
+ * {@code
+ * // Whitelist strategy - only allow + and *
+ * Set allowed = new HashSet<>(Arrays.asList("+", "*"));
+ * OperatorCheckStrategy strategy = OperatorCheckStrategy.whitelist(allowed);
+ *
+ * // Blacklist strategy - forbid assignment operator
+ * Set forbidden = new HashSet<>(Arrays.asList("="));
+ * OperatorCheckStrategy strategy = OperatorCheckStrategy.blacklist(forbidden);
+ *
+ * // Allow all operators
+ * OperatorCheckStrategy strategy = OperatorCheckStrategy.allowAll();
+ * }
+ */
+public interface OperatorCheckStrategy {
+
+ static OperatorCheckStrategy allowAll() {
+ return DefaultOperatorCheckStrategy.getInstance();
+ }
+
+ static OperatorCheckStrategy whitelist(Set allowedOperators) {
+ return new WhiteOperatorCheckStrategy(allowedOperators);
+ }
+
+ static OperatorCheckStrategy blacklist(Set forbiddenOperators) {
+ return new BlackOperatorCheckStrategy(forbiddenOperators);
+ }
+
+ boolean isAllowed(String operator);
+
+ Set getOperators();
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/operator/WhiteOperatorCheckStrategy.java b/src/main/java/com/alibaba/qlexpress4/operator/WhiteOperatorCheckStrategy.java
new file mode 100644
index 0000000..b983263
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/operator/WhiteOperatorCheckStrategy.java
@@ -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 allowedOperators;
+
+ public WhiteOperatorCheckStrategy(Set 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 getOperators() {
+ return allowedOperators;
+ }
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/proxy/QLambdaInvocationHandler.java b/src/main/java/com/alibaba/qlexpress4/proxy/QLambdaInvocationHandler.java
new file mode 100644
index 0000000..e6854f5
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/proxy/QLambdaInvocationHandler.java
@@ -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);
+ }
+}
diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/DelegateQContext.java b/src/main/java/com/alibaba/qlexpress4/runtime/DelegateQContext.java
new file mode 100644
index 0000000..73a2b28
--- /dev/null
+++ b/src/main/java/com/alibaba/qlexpress4/runtime/DelegateQContext.java
@@ -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 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