From 2dfed7464e2a34e40b1d21f5bc4c5295b2fc9c50 Mon Sep 17 00:00:00 2001 From: small-red-hat <603606739@qq.com> Date: Mon, 29 Dec 2025 13:59:13 +0800 Subject: [PATCH] first --- .github/workflows/maven-publish.yml | 28 + .github/workflows/reduce-adoc.yml | 42 + .github/workflows/unittest.yml | 20 + .gitignore | 33 + LICENSE | 201 ++ README-EN-source.adoc | 1108 ++++++++++ README-EN.adoc | 1893 ++++++++++++++++ README-source.adoc | 1109 ++++++++++ README.adoc | 1894 +++++++++++++++++ docs/custom-item-en-source.adoc | 152 ++ docs/custom-item-en.adoc | 334 +++ docs/custom-item-source.adoc | 152 ++ docs/custom-item.adoc | 334 +++ docs/execute-en-source.adoc | 50 + docs/execute-en.adoc | 95 + docs/execute-source.adoc | 50 + docs/execute.adoc | 95 + images/json_map.png | Bin 0 -> 183949 bytes images/logo.png | Bin 0 -> 701025 bytes images/order_rules_cn.png | Bin 0 -> 57130 bytes images/qlexpress_support_group_qr_2026.jpg | Bin 0 -> 130378 bytes pom.xml | 312 +++ spotless_eclipse_formatter.xml | 295 +++ src/main/antlr4/QLParser.g4 | 436 ++++ src/main/antlr4/QLexer.g4 | 1196 +++++++++++ src/main/antlr4/QLexer.tokens | 180 ++ .../com/alibaba/qlexpress4/CheckOptions.java | 69 + .../com/alibaba/qlexpress4/ClassSupplier.java | 11 + .../qlexpress4/DefaultClassSupplier.java | 39 + .../alibaba/qlexpress4/Express4Runner.java | 751 +++++++ .../com/alibaba/qlexpress4/InitOptions.java | 245 +++ .../com/alibaba/qlexpress4/QLOptions.java | 205 ++ .../com/alibaba/qlexpress4/QLPrecedences.java | 89 + .../java/com/alibaba/qlexpress4/QLResult.java | 25 + .../qlexpress4/annotation/QLAlias.java | 15 + .../qlexpress4/annotation/QLFunction.java | 20 + .../qlexpress4/aparser/AliasTokenSource.java | 53 + .../qlexpress4/aparser/AliasTokenStream.java | 95 + .../qlexpress4/aparser/BuiltInTypesSet.java | 43 + .../qlexpress4/aparser/CheckVisitor.java | 142 ++ .../qlexpress4/aparser/ExistStack.java | 12 + .../qlexpress4/aparser/GeneratorScope.java | 50 + .../qlexpress4/aparser/ImportManager.java | 231 ++ .../qlexpress4/aparser/InterpolationMode.java | 19 + .../qlexpress4/aparser/MacroDefine.java | 28 + .../qlexpress4/aparser/OperatorFactory.java | 17 + .../aparser/OutFunctionVisitor.java | 112 + .../aparser/OutVarAttrsVisitor.java | 199 ++ .../aparser/OutVarNamesVisitor.java | 194 ++ .../aparser/ParserOperatorManager.java | 34 + .../qlexpress4/aparser/QCompileCache.java | 26 + .../qlexpress4/aparser/QLErrorListener.java | 50 + .../qlexpress4/aparser/QLErrorStrategy.java | 61 + .../qlexpress4/aparser/QLExtendLexer.java | 103 + .../qlexpress4/aparser/QLExtendParser.java | 40 + .../aparser/QvmInstructionVisitor.java | 1752 +++++++++++++++ .../qlexpress4/aparser/ScopeStackVisitor.java | 99 + .../qlexpress4/aparser/SyntaxTreeFactory.java | 101 + .../aparser/TraceExpressionVisitor.java | 441 ++++ .../compiletimefunction/CodeGenerator.java | 31 + .../CompileTimeFunction.java | 23 + .../api/BatchAddFunctionResult.java | 27 + .../qlexpress4/api/QLFunctionalVarargs.java | 9 + .../alibaba/qlexpress4/enums/AccessMode.java | 8 + .../exception/DefaultErrReporter.java | 37 + .../qlexpress4/exception/ErrorReporter.java | 18 + .../qlexpress4/exception/ExMessageUtil.java | 67 + .../exception/ExceptionFactory.java | 11 + .../qlexpress4/exception/PureErrReporter.java | 17 + .../qlexpress4/exception/QLErrorCodes.java | 83 + .../qlexpress4/exception/QLException.java | 79 + .../exception/QLRuntimeException.java | 32 + .../exception/QLSyntaxException.java | 12 + .../exception/QLTimeoutException.java | 13 + .../exception/UserDefineException.java | 27 + .../qlexpress4/exception/lsp/Diagnostic.java | 70 + .../qlexpress4/exception/lsp/Position.java | 32 + .../qlexpress4/exception/lsp/Range.java | 24 + .../qlexpress4/member/FieldHandler.java | 45 + .../qlexpress4/member/MethodHandler.java | 106 + .../operator/BlackOperatorCheckStrategy.java | 34 + .../DefaultOperatorCheckStrategy.java | 26 + .../operator/OperatorCheckStrategy.java | 51 + .../operator/WhiteOperatorCheckStrategy.java | 34 + .../proxy/QLambdaInvocationHandler.java | 26 + .../qlexpress4/runtime/DelegateQContext.java | 113 + .../qlexpress4/runtime/ExceptionTable.java | 41 + .../qlexpress4/runtime/FixedSizeStack.java | 61 + .../alibaba/qlexpress4/runtime/IMethod.java | 24 + .../qlexpress4/runtime/JvmIMethod.java | 56 + .../alibaba/qlexpress4/runtime/LeftValue.java | 40 + .../qlexpress4/runtime/MemberResolver.java | 242 +++ .../alibaba/qlexpress4/runtime/MetaClass.java | 34 + .../alibaba/qlexpress4/runtime/Nothing.java | 12 + .../qlexpress4/runtime/Parameters.java | 26 + .../alibaba/qlexpress4/runtime/QContext.java | 13 + .../alibaba/qlexpress4/runtime/QLambda.java | 84 + .../qlexpress4/runtime/QLambdaDefinition.java | 17 + .../runtime/QLambdaDefinitionEmpty.java | 29 + .../runtime/QLambdaDefinitionInner.java | 89 + .../qlexpress4/runtime/QLambdaEmpty.java | 21 + .../qlexpress4/runtime/QLambdaInner.java | 99 + .../qlexpress4/runtime/QLambdaTrace.java | 23 + .../alibaba/qlexpress4/runtime/QResult.java | 44 + .../alibaba/qlexpress4/runtime/QRuntime.java | 23 + .../qlexpress4/runtime/QvmGlobalScope.java | 105 + .../qlexpress4/runtime/QvmRuntime.java | 46 + .../qlexpress4/runtime/ReflectLoader.java | 461 ++++ .../com/alibaba/qlexpress4/runtime/Value.java | 29 + .../context/DynamicVariableContext.java | 51 + .../runtime/context/EmptyContext.java | 12 + .../runtime/context/ExpressContext.java | 16 + .../runtime/context/MapExpressContext.java | 23 + .../context/ObjectFieldExpressContext.java | 25 + .../runtime/context/QLAliasContext.java | 32 + .../runtime/data/ArrayItemValue.java | 40 + .../runtime/data/AssignableDataValue.java | 47 + .../qlexpress4/runtime/data/DataValue.java | 24 + .../qlexpress4/runtime/data/FieldValue.java | 43 + .../runtime/data/ListItemValue.java | 40 + .../qlexpress4/runtime/data/MapItemValue.java | 50 + .../data/convert/ObjTypeConvertor.java | 163 ++ .../data/convert/ParametersTypeConvertor.java | 35 + .../runtime/data/lambda/QLambdaMethod.java | 89 + .../runtime/function/CustomFunction.java | 21 + .../runtime/function/ExtensionFunction.java | 27 + .../function/FilterExtensionFunction.java | 41 + .../function/MapExtensionFunction.java | 41 + .../runtime/function/QLambdaFunction.java | 28 + .../runtime/function/QMethodFunction.java | 51 + .../instruction/BreakContinueInstruction.java | 47 + .../instruction/CallConstInstruction.java | 74 + .../instruction/CallFunctionInstruction.java | 121 ++ .../runtime/instruction/CallInstruction.java | 84 + .../runtime/instruction/CastInstruction.java | 75 + .../instruction/CheckTimeOutInstruction.java | 52 + .../instruction/CloseScopeInstruction.java | 47 + .../runtime/instruction/ConstInstruction.java | 65 + .../DefineFunctionInstruction.java | 52 + .../instruction/DefineLocalInstruction.java | 60 + .../instruction/ForEachInstruction.java | 124 ++ .../runtime/instruction/ForInstruction.java | 163 ++ .../instruction/GetFieldInstruction.java | 72 + .../instruction/GetMethodInstruction.java | 61 + .../runtime/instruction/IndexInstruction.java | 94 + .../instruction/JumpIfInstruction.java | 83 + .../instruction/JumpIfPopInstruction.java | 68 + .../runtime/instruction/JumpInstruction.java | 53 + .../runtime/instruction/LoadInstruction.java | 62 + .../instruction/LoadLambdaInstruction.java | 53 + .../instruction/MethodInvokeInstruction.java | 82 + .../instruction/MultiNewArrayInstruction.java | 71 + .../instruction/NewArrayInstruction.java | 79 + .../NewFilledInstanceInstruction.java | 93 + .../instruction/NewInstanceInstruction.java | 92 + .../instruction/NewListInstruction.java | 56 + .../instruction/NewMapInstruction.java | 57 + .../instruction/NewScopeInstruction.java | 47 + .../instruction/OperatorInstruction.java | 87 + .../runtime/instruction/PopInstruction.java | 43 + .../runtime/instruction/QLInstruction.java | 43 + .../instruction/ReturnInstruction.java | 59 + .../runtime/instruction/SliceInstruction.java | 176 ++ .../SpreadGetFieldInstruction.java | 136 ++ .../SpreadMethodInvokeInstruction.java | 119 ++ .../instruction/StringJoinInstruction.java | 58 + .../runtime/instruction/ThrowInstruction.java | 44 + .../TraceEvaludatedInstruction.java | 51 + .../instruction/TracePeekInstruction.java | 51 + .../instruction/TryCatchInstruction.java | 148 ++ .../runtime/instruction/UnaryInstruction.java | 64 + .../runtime/instruction/WhileInstruction.java | 97 + .../runtime/operator/BinaryOperator.java | 25 + .../operator/CustomBinaryOperator.java | 17 + .../runtime/operator/InstanceOfOperator.java | 53 + .../qlexpress4/runtime/operator/Operator.java | 18 + .../runtime/operator/OperatorManager.java | 321 +++ .../arithmetic/DivideAssignOperator.java | 43 + .../operator/arithmetic/DivideOperator.java | 38 + .../arithmetic/MinusAssignOperator.java | 43 + .../operator/arithmetic/MinusOperator.java | 38 + .../arithmetic/MultiplyAssignOperator.java | 43 + .../operator/arithmetic/MultiplyOperator.java | 38 + .../arithmetic/PlusAssignOperator.java | 43 + .../operator/arithmetic/PlusOperator.java | 40 + .../arithmetic/RemainderAssignOperator.java | 43 + .../arithmetic/RemainderOperator.java | 49 + .../operator/assign/AssignOperator.java | 43 + .../operator/base/BaseBinaryOperator.java | 386 ++++ .../operator/base/BaseUnaryOperator.java | 17 + .../bit/BitwiseAndAssignOperator.java | 43 + .../operator/bit/BitwiseAndOperator.java | 38 + .../operator/bit/BitwiseInvertOperator.java | 41 + .../bit/BitwiseLeftShiftAssignOperator.java | 43 + .../bit/BitwiseLeftShiftOperator.java | 38 + .../operator/bit/BitwiseOrAssignOperator.java | 43 + .../operator/bit/BitwiseOrOperator.java | 38 + .../bit/BitwiseRightShiftAssignOperator.java | 43 + .../bit/BitwiseRightShiftOperator.java | 38 + ...twiseRightShiftUnsignedAssignOperator.java | 44 + .../BitwiseRightShiftUnsignedOperator.java | 38 + .../bit/BitwiseXorAssignOperator.java | 43 + .../operator/bit/BitwiseXorOperator.java | 38 + .../operator/collection/InOperator.java | 38 + .../operator/collection/NotInOperator.java | 38 + .../operator/compare/EqualOperator.java | 38 + .../compare/GreaterEqualOperator.java | 41 + .../operator/compare/GreaterOperator.java | 41 + .../operator/compare/LessEqualOperator.java | 41 + .../operator/compare/LessOperator.java | 41 + .../operator/compare/UnequalOperator.java | 49 + .../operator/logic/LogicAndOperator.java | 62 + .../operator/logic/LogicNotOperator.java | 43 + .../operator/logic/LogicOrOperator.java | 55 + .../operator/number/BigDecimalMath.java | 109 + .../operator/number/BigIntegerMath.java | 116 + .../operator/number/FloatingPointMath.java | 81 + .../runtime/operator/number/IntegerMath.java | 123 ++ .../runtime/operator/number/LongMath.java | 127 ++ .../runtime/operator/number/NumberMath.java | 338 +++ .../runtime/operator/string/LikeOperator.java | 36 + .../operator/string/NotLikeOperator.java | 36 + .../unary/MinusMinusPrefixUnaryOperator.java | 45 + .../unary/MinusMinusSuffixUnaryOperator.java | 46 + .../operator/unary/MinusUnaryOperator.java | 41 + .../unary/PlusPlusPrefixUnaryOperator.java | 46 + .../unary/PlusPlusSuffixUnaryOperator.java | 45 + .../operator/unary/PlusUnaryOperator.java | 41 + .../runtime/operator/unary/UnaryOperator.java | 20 + .../qlexpress4/runtime/scope/QScope.java | 91 + .../runtime/scope/QvmBlockScope.java | 96 + .../runtime/trace/ExpressionTrace.java | 96 + .../qlexpress4/runtime/trace/QTraces.java | 30 + .../runtime/trace/TracePointTree.java | 62 + .../qlexpress4/runtime/trace/TraceType.java | 10 + .../runtime/util/MethodInvokeUtils.java | 73 + .../qlexpress4/runtime/util/ThrowUtils.java | 29 + .../qlexpress4/runtime/util/ValueUtils.java | 28 + .../security/QLSecurityStrategy.java | 34 + .../security/StrategyBlackList.java | 23 + .../security/StrategyIsolation.java | 21 + .../qlexpress4/security/StrategyOpen.java | 21 + .../security/StrategyWhiteList.java | 22 + .../alibaba/qlexpress4/utils/BasicUtil.java | 100 + .../alibaba/qlexpress4/utils/CacheUtil.java | 17 + .../qlexpress4/utils/PrintlnUtils.java | 28 + .../qlexpress4/utils/QLAliasUtils.java | 18 + .../qlexpress4/utils/QLFunctionUtil.java | 19 + .../qlexpress4/utils/QLStringUtils.java | 64 + .../alibaba/qlexpress4/ClearDfaCacheTest.java | 69 + .../qlexpress4/Express4RunnerTest.java | 1609 ++++++++++++++ .../alibaba/qlexpress4/OperatorLimitTest.java | 325 +++ .../alibaba/qlexpress4/QLImportTester.java | 9 + .../alibaba/qlexpress4/TestSuiteRunner.java | 295 +++ .../aparser/CompileTimeFunctionTest.java | 99 + .../qlexpress4/aparser/ImportManagerTest.java | 74 + .../alibaba/qlexpress4/aparser/MockOpM.java | 96 + .../aparser/SyntaxTreeFactoryTest.java | 311 +++ .../qlexpress4/docs/CustomItemsDocTest.java | 143 ++ .../exception/MockErrorReporter.java | 8 + .../qlexpress4/exception/QLExceptionTest.java | 24 + .../qlexpress4/generic/GenericTypeTest.java | 48 + .../qlexpress4/inport/InterGrandPa.java | 9 + .../inport/InterWithDefaultImpl.java | 4 + .../inport/InterWithDefaultImplChild.java | 4 + .../InterWithDefaultImplExtGrandPa.java | 4 + .../InterWithDefaultImplGrandPaChild.java | 4 + .../inport/InterWithDefaultMethod.java | 9 + .../com/alibaba/qlexpress4/inport/MyDesk.java | 27 + .../com/alibaba/qlexpress4/inport/MyHome.java | 43 + .../com/alibaba/qlexpress4/inport/Person.java | 17 + .../com/alibaba/qlexpress4/inport/Sample.java | 10 + .../alibaba/qlexpress4/inport/Sample1.java | 10 + .../pf4j/Pf4jClassSupplierTest.java | 56 + .../qlexpress4/pf4j/TestPluginImpl.java | 24 + .../qlexpress4/pf4j/TestPluginInterface.java | 13 + .../runtime/FixedSizeStackTest.java | 32 + .../runtime/MemberResolverTest.java | 62 + .../qlexpress4/runtime/MockLeftValue.java | 24 + .../instruction/CallInstructionTest.java | 39 + .../instruction/GetFieldInstructionTest.java | 208 ++ .../instruction/InterWithDefaultMethod.java | 13 + .../MethodInvokeInstructionTest.java | 265 +++ .../instruction/MockCastParameters.java | 35 + .../MockParametersParentClass.java | 21 + .../instruction/MockQContextParent.java | 129 ++ .../NewInstanceInstructionTest.java | 151 ++ .../runtime/operator/MockValue.java | 19 + .../MinusMinusPrefixUnaryOperatorTest.java | 26 + .../MinusMinusSuffixUnaryOperatorTest.java | 27 + .../qlexpress4/spring/HelloService.java | 18 + .../qlexpress4/spring/QLExecuteService.java | 26 + .../qlexpress4/spring/QLSpringContext.java | 35 + .../qlexpress4/spring/SpringDemoTest.java | 30 + .../qlexpress4/spring/SpringTestConfig.java | 13 + .../test/annotation/QL4AliasTest.java | 56 + .../test/constructor/HelloChild.java | 7 + .../test/constructor/HelloConstructor.java | 35 + .../test/constructor/HelloParent.java | 7 + .../test/function/HelloFunction.java | 14 + .../qlexpress4/test/issue/Issue318Test.java | 54 + .../test/lambda/UserFunctionalInterface.java | 10 + .../test/method/InterWithDefault.java | 12 + .../qlexpress4/test/method/TestChild.java | 15 + .../qlexpress4/test/method/TestParent.java | 12 + .../qlexpress4/test/property/Child.java | 73 + .../qlexpress4/test/property/Child1.java | 28 + .../qlexpress4/test/property/Child10.java | 30 + .../qlexpress4/test/property/Child2.java | 28 + .../qlexpress4/test/property/Child3.java | 29 + .../qlexpress4/test/property/Child4.java | 21 + .../qlexpress4/test/property/Child5.java | 21 + .../qlexpress4/test/property/Child6.java | 32 + .../qlexpress4/test/property/Child7.java | 24 + .../qlexpress4/test/property/Child9.java | 29 + .../test/property/NumberConstructor.java | 31 + .../qlexpress4/test/property/Parent.java | 113 + .../qlexpress4/test/property/ParentClass.java | 15 + .../test/property/ParentParameters.java | 29 + .../qlexpress4/test/property/Sample.java | 22 + .../qlexpress4/test/property/SampleEnum.java | 12 + .../test/property/SampleForPrivate.java | 14 + .../qlexpress4/test/property/SampleSet.java | 8 + .../qlexpress4/test/property/SomeInter.java | 7 + .../qlexpress4/test/property/TestEnum.java | 19 + .../qlexpress4/test/qlalias/Order.java | 29 + .../qlexpress4/test/qlalias/Patient.java | 36 + .../qlexpress4/test/qlalias/Person.java | 44 + .../alibaba/qlexpress4/test/qlalias/User.java | 29 + .../qlexpress4/test/stream/STObject.java | 14 + .../test/trycatch/SampleException.java | 12 + .../resources/perf/complexDataProcessing.ql | 514 +++++ .../resources/test-plugins/build-plugin.sh | 18 + .../resources/test-plugins/plugin.properties | 7 + .../resources/test-plugins/test-plugin.jar | Bin 0 -> 2154 bytes .../array/array_index_out_of_bound.ql | 7 + .../independent/array/array_literal.ql | 6 + .../independent/array/float_index.ql | 2 + .../independent/array/invalid_index.ql | 7 + .../independent/array/max_arr_len.ql | 15 + .../array/miss_comma_between_elements.ql | 6 + .../independent/array/no_rbrack_to_match.ql | 6 + .../testsuite/independent/array/slice.ql | 7 + .../independent/array/unindexable.ql | 7 + .../avoidnullpointer/avoid_null_pointer.ql | 15 + .../avoidnullpointer/can_not_find_function.ql | 4 + .../avoidnullpointer/get_from_null.ql | 6 + .../avoidnullpointer/get_method_from_null.ql | 4 + .../independent/block/block_as_expr.ql | 19 + .../independent/block/block_at_if.ql | 12 + .../independent/block/lambda_with_block.ql | 10 + .../independent/block/missing_rbrace.ql | 7 + .../independent/block/return_at_block.ql | 17 + .../independent/bool/bool_literal.ql | 4 + .../bool/bool_literal_is_keyword.ql | 6 + .../independent/bool/short_circuit.ql | 24 + .../independent/cast/cast_express.ql | 7 + .../testsuite/independent/cast/null_cast.ql | 3 + .../testsuite/independent/comment/comment.ql | 6 + .../doc/convenient_syntax_elements.ql | 21 + .../independent/doc/dynamic_string.ql | 12 + .../independent/doc/dynamic_typing.ql | 8 + .../testsuite/independent/doc/for.ql | 5 + .../testsuite/independent/doc/for_each.ql | 8 + .../testsuite/independent/doc/function.ql | 4 + .../resources/testsuite/independent/doc/if.ql | 13 + .../testsuite/independent/doc/if_as_expr.ql | 5 + .../testsuite/independent/doc/if_then.ql | 3 + .../testsuite/independent/doc/lambda.ql | 4 + .../independent/doc/list_map_filter.ql | 3 + .../testsuite/independent/doc/try_catch.ql | 6 + .../independent/doc/try_catch_as_expr.ql | 6 + .../testsuite/independent/doc/while.ql | 7 + .../extensionfunction/extension_function.ql | 7 + .../independent/for/break_continue.ql | 27 + .../testsuite/independent/for/c_for.ql | 19 + .../independent/for/condition_not_bool.ql | 6 + .../testsuite/independent/for/for_each.ql | 10 + .../for/for_each_break_continue.ql | 29 + .../independent/for/for_each_invalid_type.ql | 10 + .../independent/for/for_each_not_iterable.ql | 7 + .../independent/for/infinite_loop.ql | 15 + .../independent/for/missing_lparen_at_for.ql | 6 + .../for/missing_rparen_after_for_update.ql | 6 + .../for/missing_rparen_at_for_each.ql | 7 + .../for/missing_semi_after_for_init.ql | 6 + .../independent/for/return_from_for.ql | 13 + .../function/complex_parameters.ql | 67 + .../independent/function/edge_cases.ql | 100 + .../independent/function/function_call.ql | 19 + .../independent/function/function_scoping.ql | 84 + .../independent/function/invalid_argument.ql | 10 + .../function/mixed_declarations.ql | 54 + .../independent/function/multi_call.ql | 15 + .../function/nested_function_calls.ql | 40 + .../function/recursive_functions.ql | 52 + .../independent/function/return_left_value.ql | 20 + .../testsuite/independent/if/if_as_expr.ql | 16 + .../independent/if/if_condition_not_bool.ql | 8 + .../testsuite/independent/if/if_else_if.ql | 8 + .../independent/if/if_else_miss_body.ql | 8 + .../independent/if/if_followed_by_cast.ql | 3 + .../testsuite/independent/if/if_miss_body.ql | 6 + .../if/if_with_one_statement_body.ql | 6 + .../independent/if/if_without_condition.ql | 7 + .../independent/if/if_without_condition_2.ql | 6 + .../independent/if/miss_if_lparen.ql | 6 + .../independent/if/miss_if_rparen.ql | 6 + .../testsuite/independent/if/return_at_if.ql | 14 + .../testsuite/independent/if/simple_if.ql | 27 + .../independent/lambda/invalid_argument.ql | 9 + .../lambda/invalid_argument_call.ql | 10 + .../independent/lambda/lambda_doc.ql | 5 + .../independent/lambda/lambda_return.ql | 7 + .../independent/lambda/simple_lambda.ql | 10 + .../independent/lambda/unmatch_param_num.ql | 3 + .../independent/macro/empty_macro.ql | 9 + .../independent/macro/invalid_macro_name.ql | 7 + .../testsuite/independent/macro/macro.ql | 26 + .../independent/macro/macro_break_continue.ql | 12 + .../independent/macro/macro_control_flow.ql | 16 + .../macro/macro_define_in_sub_scope.ql | 17 + .../independent/macro/macro_return.ql | 9 + .../independent/macro/missing_lbrace.ql | 6 + .../map/colon_absent_between_map_entry.ql | 9 + .../independent/map/colon_absent_in_entry.ql | 9 + .../independent/map/invalid_map_key.ql | 8 + .../map/key_word_can_not_get_from_field.ql | 9 + .../independent/map/keyword_key_map.ql | 8 + .../testsuite/independent/map/map_at_block.ql | 7 + .../testsuite/independent/map/map_define.ql | 31 + .../map/string_literal_as_field_access.ql | 6 + .../independent/newlines/newlines.ql | 88 + .../testsuite/independent/number/number.ql | 14 + .../testsuite/independent/number/precise.ql | 4 + .../independent/operator/big_decimal.ql | 87 + .../independent/operator/big_integer.ql | 58 + .../testsuite/independent/operator/bitwise.ql | 145 ++ .../testsuite/independent/operator/boolean.ql | 107 + .../independent/operator/character.ql | 18 + .../independent/operator/comparable.ql | 13 + .../testsuite/independent/operator/double.ql | 58 + .../testsuite/independent/operator/equals.ql | 34 + .../independent/operator/in_not_in.ql | 59 + .../testsuite/independent/operator/integer.ql | 147 ++ .../testsuite/independent/operator/like.ql | 20 + .../testsuite/independent/operator/logic.ql | 14 + .../independent/operator/optional_chaining.ql | 14 + .../testsuite/independent/operator/string.ql | 19 + .../independent/scope/block_scope.ql | 6 + .../independent/scope/global_variable.ql | 8 + .../independent/scope/lexical_scope.ql | 14 + .../independent/scope/scope_cover.ql | 10 + .../independent/spread/arr_spread.ql | 8 + .../independent/spread/list_spread.ql | 46 + .../independent/spread/spread_avoid_null.ql | 18 + .../testsuite/independent/string/char.ql | 5 + .../independent/string/interpolation.ql | 37 + .../independent/string/invalid_char.ql | 7 + .../testsuite/independent/string/literal.ql | 1 + .../independent/string/string_escape.ql | 13 + .../independent/string/string_not_close.ql | 6 + .../independent/ternary/missing_colon.ql | 6 + .../testsuite/independent/ternary/ternary.ql | 17 + .../testsuite/independent/timeout/timeout.ql | 9 + .../independent/trycatch/catch_order.ql | 9 + .../trycatch/missing_lbrace_at_try.ql | 6 + .../trycatch/missing_lbrace_at_try_finally.ql | 10 + .../trycatch/multi_exception_catch.ql | 18 + .../independent/trycatch/return_from_try.ql | 31 + .../independent/trycatch/throw_number.ql | 6 + .../independent/trycatch/try_catch_expr.ql | 16 + .../trycatch/try_catch_final_scope.ql | 11 + .../independent/while/break_continue.ql | 18 + .../independent/while/condition_not_bool.ql | 8 + .../independent/while/missing_lparen.ql | 7 + .../independent/while/missing_rparen.ql | 9 + .../testsuite/independent/while/while.ql | 8 + .../java/array/arr_index_out_of_bound.ql | 7 + .../testsuite/java/array/arr_literal.ql | 3 + .../java/array/arr_with_init_item.ql | 6 + .../java/array/array_item_type_convert.ql | 4 + .../testsuite/java/array/array_slice.ql | 11 + .../testsuite/java/array/comma_absent.ql | 6 + .../java/array/invalid_arr_define.ql | 6 + .../testsuite/java/array/invalid_arr_item.ql | 6 + .../java/array/invalid_arr_size_define.ql | 6 + .../java/array/invalid_arr_size_type.ql | 7 + .../testsuite/java/array/multi_dim_array.ql | 4 + .../testsuite/java/array/type_arr.ql | 2 + .../testsuite/java/cast/assignable_cast.ql | 5 + .../testsuite/java/cast/define_local_cast.ql | 2 + .../testsuite/java/cast/object_cast.ql | 2 + .../testsuite/java/cast/string_cast.ql | 3 + .../testsuite/java/for/for_each_array.ql | 12 + .../testsuite/java/generics/generics.ql | 4 + .../java/generics/invalid_type_bound.ql | 6 + .../testsuite/java/implicit/arithmetic.ql | 16 + .../java/implicit/assignment_basic.ql | 32 + .../java/implicit/assignment_extend.ql | 43 + .../testsuite/java/implicit/function_param.ql | 19 + .../implicit/incompatible_assignment_type.ql | 9 + .../testsuite/java/implicit/packing.ql | 16 + .../testsuite/java/implicit/pointer.ql | 6 + .../testsuite/java/import/import_class.ql | 2 + .../java/import/import_not_at_beginning.ql | 7 + .../java/import/import_not_end_with_semi.ql | 7 + .../import/import_pack_not_end_with_semi.ql | 6 + .../testsuite/java/import/import_package.ql | 3 + .../testsuite/java/import/import_star.ql | 6 + .../java/import/incomplete_import.ql | 6 + .../testsuite/java/import/invalid_package.ql | 6 + .../testsuite/java/import/multi_import.ql | 4 + .../java/import/not_support_import_static.ql | 6 + .../java/lambda/java_functional_interface.ql | 16 + .../testsuite/java/lambda/lambda_implicit.ql | 5 + .../testsuite/java/lambda/lambda_method.ql | 5 + .../java/lambda/user_functional_interface.ql | 4 + .../testsuite/java/map/classified_json.ql | 42 + .../testsuite/java/map/equal_to_hash_map.ql | 13 + .../testsuite/java/method/method_invoke.ql | 7 + .../java/method_reference/class_method.ql | 8 + .../java/method_reference/class_obj_method.ql | 2 + .../java/method_reference/method_not_found.ql | 7 + .../java/method_reference/object_method.ql | 5 + .../testsuite/java/newexpr/new_resolver.ql | 25 + .../testsuite/java/newexpr/noArgument.ql | 3 + .../java/newexpr/no_match_constructor.ql | 8 + .../testsuite/java/number/long_max_value.ql | 2 + .../java/number/min_value_not_equal_to_hex.ql | 3 + .../testsuite/java/number/number_auto_type.ql | 6 + .../testsuite/java/number/number_invoke.ql | 2 + .../java/property/array_length_get.ql | 2 + .../testsuite/java/property/class_get.ql | 2 + .../testsuite/java/property/enum_get.ql | 4 + .../java/property/enum_get_not_exist.ql | 8 + .../java/property/enum_member_field.ql | 4 + .../java/property/interface_const_field.ql | 3 + .../java/property/jsonobject_vs_map_put.ql | 27 + .../java/property/null_set_invoke.ql | 11 + .../private_member_attr_access_get.ql | 9 + .../private_member_attr_access_set.ql | 10 + .../property/private_member_attr_getter.ql | 4 + .../private_member_attr_not_access_get.ql | 9 + .../private_member_attr_not_access_set.ql | 10 + .../property/private_member_attr_setter.ql | 5 + .../private_member_set_not_accessible.ql | 12 + .../java/property/public_member_set.ql | 5 + .../testsuite/java/property/public_static.ql | 2 + .../testsuite/java/stream/java_stream.ql | 7 + .../java/stream/java_stream_method_ref.ql | 6 + .../java/trycatch/catch_java_exception.ql | 8 + .../java/trycatch/catch_operator_exception.ql | 6 + .../testsuite/java/trycatch/catch_order.ql | 10 + .../testsuite/java/trycatch/ql_npe.ql | 22 + 555 files changed, 35895 insertions(+) create mode 100644 .github/workflows/maven-publish.yml create mode 100644 .github/workflows/reduce-adoc.yml create mode 100644 .github/workflows/unittest.yml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README-EN-source.adoc create mode 100644 README-EN.adoc create mode 100644 README-source.adoc create mode 100644 README.adoc create mode 100644 docs/custom-item-en-source.adoc create mode 100644 docs/custom-item-en.adoc create mode 100644 docs/custom-item-source.adoc create mode 100644 docs/custom-item.adoc create mode 100644 docs/execute-en-source.adoc create mode 100644 docs/execute-en.adoc create mode 100644 docs/execute-source.adoc create mode 100644 docs/execute.adoc create mode 100644 images/json_map.png create mode 100644 images/logo.png create mode 100644 images/order_rules_cn.png create mode 100644 images/qlexpress_support_group_qr_2026.jpg create mode 100644 pom.xml create mode 100644 spotless_eclipse_formatter.xml create mode 100644 src/main/antlr4/QLParser.g4 create mode 100644 src/main/antlr4/QLexer.g4 create mode 100644 src/main/antlr4/QLexer.tokens create mode 100644 src/main/java/com/alibaba/qlexpress4/CheckOptions.java create mode 100644 src/main/java/com/alibaba/qlexpress4/ClassSupplier.java create mode 100644 src/main/java/com/alibaba/qlexpress4/DefaultClassSupplier.java create mode 100644 src/main/java/com/alibaba/qlexpress4/Express4Runner.java create mode 100644 src/main/java/com/alibaba/qlexpress4/InitOptions.java create mode 100644 src/main/java/com/alibaba/qlexpress4/QLOptions.java create mode 100644 src/main/java/com/alibaba/qlexpress4/QLPrecedences.java create mode 100644 src/main/java/com/alibaba/qlexpress4/QLResult.java create mode 100644 src/main/java/com/alibaba/qlexpress4/annotation/QLAlias.java create mode 100644 src/main/java/com/alibaba/qlexpress4/annotation/QLFunction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/aparser/AliasTokenSource.java create mode 100644 src/main/java/com/alibaba/qlexpress4/aparser/AliasTokenStream.java create mode 100644 src/main/java/com/alibaba/qlexpress4/aparser/BuiltInTypesSet.java create mode 100644 src/main/java/com/alibaba/qlexpress4/aparser/CheckVisitor.java create mode 100644 src/main/java/com/alibaba/qlexpress4/aparser/ExistStack.java create mode 100644 src/main/java/com/alibaba/qlexpress4/aparser/GeneratorScope.java create mode 100644 src/main/java/com/alibaba/qlexpress4/aparser/ImportManager.java create mode 100644 src/main/java/com/alibaba/qlexpress4/aparser/InterpolationMode.java create mode 100644 src/main/java/com/alibaba/qlexpress4/aparser/MacroDefine.java create mode 100644 src/main/java/com/alibaba/qlexpress4/aparser/OperatorFactory.java create mode 100644 src/main/java/com/alibaba/qlexpress4/aparser/OutFunctionVisitor.java create mode 100644 src/main/java/com/alibaba/qlexpress4/aparser/OutVarAttrsVisitor.java create mode 100644 src/main/java/com/alibaba/qlexpress4/aparser/OutVarNamesVisitor.java create mode 100644 src/main/java/com/alibaba/qlexpress4/aparser/ParserOperatorManager.java create mode 100644 src/main/java/com/alibaba/qlexpress4/aparser/QCompileCache.java create mode 100644 src/main/java/com/alibaba/qlexpress4/aparser/QLErrorListener.java create mode 100644 src/main/java/com/alibaba/qlexpress4/aparser/QLErrorStrategy.java create mode 100644 src/main/java/com/alibaba/qlexpress4/aparser/QLExtendLexer.java create mode 100644 src/main/java/com/alibaba/qlexpress4/aparser/QLExtendParser.java create mode 100644 src/main/java/com/alibaba/qlexpress4/aparser/QvmInstructionVisitor.java create mode 100644 src/main/java/com/alibaba/qlexpress4/aparser/ScopeStackVisitor.java create mode 100644 src/main/java/com/alibaba/qlexpress4/aparser/SyntaxTreeFactory.java create mode 100644 src/main/java/com/alibaba/qlexpress4/aparser/TraceExpressionVisitor.java create mode 100644 src/main/java/com/alibaba/qlexpress4/aparser/compiletimefunction/CodeGenerator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/aparser/compiletimefunction/CompileTimeFunction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/api/BatchAddFunctionResult.java create mode 100644 src/main/java/com/alibaba/qlexpress4/api/QLFunctionalVarargs.java create mode 100644 src/main/java/com/alibaba/qlexpress4/enums/AccessMode.java create mode 100644 src/main/java/com/alibaba/qlexpress4/exception/DefaultErrReporter.java create mode 100644 src/main/java/com/alibaba/qlexpress4/exception/ErrorReporter.java create mode 100644 src/main/java/com/alibaba/qlexpress4/exception/ExMessageUtil.java create mode 100644 src/main/java/com/alibaba/qlexpress4/exception/ExceptionFactory.java create mode 100644 src/main/java/com/alibaba/qlexpress4/exception/PureErrReporter.java create mode 100644 src/main/java/com/alibaba/qlexpress4/exception/QLErrorCodes.java create mode 100644 src/main/java/com/alibaba/qlexpress4/exception/QLException.java create mode 100644 src/main/java/com/alibaba/qlexpress4/exception/QLRuntimeException.java create mode 100644 src/main/java/com/alibaba/qlexpress4/exception/QLSyntaxException.java create mode 100644 src/main/java/com/alibaba/qlexpress4/exception/QLTimeoutException.java create mode 100644 src/main/java/com/alibaba/qlexpress4/exception/UserDefineException.java create mode 100644 src/main/java/com/alibaba/qlexpress4/exception/lsp/Diagnostic.java create mode 100644 src/main/java/com/alibaba/qlexpress4/exception/lsp/Position.java create mode 100644 src/main/java/com/alibaba/qlexpress4/exception/lsp/Range.java create mode 100644 src/main/java/com/alibaba/qlexpress4/member/FieldHandler.java create mode 100644 src/main/java/com/alibaba/qlexpress4/member/MethodHandler.java create mode 100644 src/main/java/com/alibaba/qlexpress4/operator/BlackOperatorCheckStrategy.java create mode 100644 src/main/java/com/alibaba/qlexpress4/operator/DefaultOperatorCheckStrategy.java create mode 100644 src/main/java/com/alibaba/qlexpress4/operator/OperatorCheckStrategy.java create mode 100644 src/main/java/com/alibaba/qlexpress4/operator/WhiteOperatorCheckStrategy.java create mode 100644 src/main/java/com/alibaba/qlexpress4/proxy/QLambdaInvocationHandler.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/DelegateQContext.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/ExceptionTable.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/FixedSizeStack.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/IMethod.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/JvmIMethod.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/LeftValue.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/MemberResolver.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/MetaClass.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/Nothing.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/Parameters.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/QContext.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/QLambda.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/QLambdaDefinition.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/QLambdaDefinitionEmpty.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/QLambdaDefinitionInner.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/QLambdaEmpty.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/QLambdaInner.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/QLambdaTrace.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/QResult.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/QRuntime.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/QvmGlobalScope.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/QvmRuntime.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/ReflectLoader.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/Value.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/context/DynamicVariableContext.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/context/EmptyContext.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/context/ExpressContext.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/context/MapExpressContext.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/context/ObjectFieldExpressContext.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/context/QLAliasContext.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/data/ArrayItemValue.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/data/AssignableDataValue.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/data/DataValue.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/data/FieldValue.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/data/ListItemValue.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/data/MapItemValue.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/data/convert/ObjTypeConvertor.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/data/convert/ParametersTypeConvertor.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/data/lambda/QLambdaMethod.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/function/CustomFunction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/function/ExtensionFunction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/function/FilterExtensionFunction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/function/MapExtensionFunction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/function/QLambdaFunction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/function/QMethodFunction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/instruction/BreakContinueInstruction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/instruction/CallConstInstruction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/instruction/CallFunctionInstruction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/instruction/CallInstruction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/instruction/CastInstruction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/instruction/CheckTimeOutInstruction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/instruction/CloseScopeInstruction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/instruction/ConstInstruction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/instruction/DefineFunctionInstruction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/instruction/DefineLocalInstruction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/instruction/ForEachInstruction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/instruction/ForInstruction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/instruction/GetFieldInstruction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/instruction/GetMethodInstruction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/instruction/IndexInstruction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/instruction/JumpIfInstruction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/instruction/JumpIfPopInstruction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/instruction/JumpInstruction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/instruction/LoadInstruction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/instruction/LoadLambdaInstruction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/instruction/MethodInvokeInstruction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/instruction/MultiNewArrayInstruction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/instruction/NewArrayInstruction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/instruction/NewFilledInstanceInstruction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/instruction/NewInstanceInstruction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/instruction/NewListInstruction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/instruction/NewMapInstruction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/instruction/NewScopeInstruction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/instruction/OperatorInstruction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/instruction/PopInstruction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/instruction/QLInstruction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/instruction/ReturnInstruction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/instruction/SliceInstruction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/instruction/SpreadGetFieldInstruction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/instruction/SpreadMethodInvokeInstruction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/instruction/StringJoinInstruction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/instruction/ThrowInstruction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/instruction/TraceEvaludatedInstruction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/instruction/TracePeekInstruction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/instruction/TryCatchInstruction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/instruction/UnaryInstruction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/instruction/WhileInstruction.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/BinaryOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/CustomBinaryOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/InstanceOfOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/Operator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/OperatorManager.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/arithmetic/DivideAssignOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/arithmetic/DivideOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/arithmetic/MinusAssignOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/arithmetic/MinusOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/arithmetic/MultiplyAssignOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/arithmetic/MultiplyOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/arithmetic/PlusAssignOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/arithmetic/PlusOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/arithmetic/RemainderAssignOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/arithmetic/RemainderOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/assign/AssignOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/base/BaseBinaryOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/base/BaseUnaryOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseAndAssignOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseAndOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseInvertOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseLeftShiftAssignOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseLeftShiftOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseOrAssignOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseOrOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseRightShiftAssignOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseRightShiftOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseRightShiftUnsignedAssignOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseRightShiftUnsignedOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseXorAssignOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseXorOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/collection/InOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/collection/NotInOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/compare/EqualOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/compare/GreaterEqualOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/compare/GreaterOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/compare/LessEqualOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/compare/LessOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/compare/UnequalOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/logic/LogicAndOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/logic/LogicNotOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/logic/LogicOrOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/number/BigDecimalMath.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/number/BigIntegerMath.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/number/FloatingPointMath.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/number/IntegerMath.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/number/LongMath.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/number/NumberMath.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/string/LikeOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/string/NotLikeOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/unary/MinusMinusPrefixUnaryOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/unary/MinusMinusSuffixUnaryOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/unary/MinusUnaryOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/unary/PlusPlusPrefixUnaryOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/unary/PlusPlusSuffixUnaryOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/unary/PlusUnaryOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/operator/unary/UnaryOperator.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/scope/QScope.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/scope/QvmBlockScope.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/trace/ExpressionTrace.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/trace/QTraces.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/trace/TracePointTree.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/trace/TraceType.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/util/MethodInvokeUtils.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/util/ThrowUtils.java create mode 100644 src/main/java/com/alibaba/qlexpress4/runtime/util/ValueUtils.java create mode 100644 src/main/java/com/alibaba/qlexpress4/security/QLSecurityStrategy.java create mode 100644 src/main/java/com/alibaba/qlexpress4/security/StrategyBlackList.java create mode 100644 src/main/java/com/alibaba/qlexpress4/security/StrategyIsolation.java create mode 100644 src/main/java/com/alibaba/qlexpress4/security/StrategyOpen.java create mode 100644 src/main/java/com/alibaba/qlexpress4/security/StrategyWhiteList.java create mode 100644 src/main/java/com/alibaba/qlexpress4/utils/BasicUtil.java create mode 100644 src/main/java/com/alibaba/qlexpress4/utils/CacheUtil.java create mode 100644 src/main/java/com/alibaba/qlexpress4/utils/PrintlnUtils.java create mode 100644 src/main/java/com/alibaba/qlexpress4/utils/QLAliasUtils.java create mode 100644 src/main/java/com/alibaba/qlexpress4/utils/QLFunctionUtil.java create mode 100644 src/main/java/com/alibaba/qlexpress4/utils/QLStringUtils.java create mode 100644 src/test/java/com/alibaba/qlexpress4/ClearDfaCacheTest.java create mode 100644 src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java create mode 100644 src/test/java/com/alibaba/qlexpress4/OperatorLimitTest.java create mode 100644 src/test/java/com/alibaba/qlexpress4/QLImportTester.java create mode 100644 src/test/java/com/alibaba/qlexpress4/TestSuiteRunner.java create mode 100644 src/test/java/com/alibaba/qlexpress4/aparser/CompileTimeFunctionTest.java create mode 100644 src/test/java/com/alibaba/qlexpress4/aparser/ImportManagerTest.java create mode 100644 src/test/java/com/alibaba/qlexpress4/aparser/MockOpM.java create mode 100644 src/test/java/com/alibaba/qlexpress4/aparser/SyntaxTreeFactoryTest.java create mode 100644 src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java create mode 100644 src/test/java/com/alibaba/qlexpress4/exception/MockErrorReporter.java create mode 100644 src/test/java/com/alibaba/qlexpress4/exception/QLExceptionTest.java create mode 100644 src/test/java/com/alibaba/qlexpress4/generic/GenericTypeTest.java create mode 100644 src/test/java/com/alibaba/qlexpress4/inport/InterGrandPa.java create mode 100644 src/test/java/com/alibaba/qlexpress4/inport/InterWithDefaultImpl.java create mode 100644 src/test/java/com/alibaba/qlexpress4/inport/InterWithDefaultImplChild.java create mode 100644 src/test/java/com/alibaba/qlexpress4/inport/InterWithDefaultImplExtGrandPa.java create mode 100644 src/test/java/com/alibaba/qlexpress4/inport/InterWithDefaultImplGrandPaChild.java create mode 100644 src/test/java/com/alibaba/qlexpress4/inport/InterWithDefaultMethod.java create mode 100644 src/test/java/com/alibaba/qlexpress4/inport/MyDesk.java create mode 100644 src/test/java/com/alibaba/qlexpress4/inport/MyHome.java create mode 100644 src/test/java/com/alibaba/qlexpress4/inport/Person.java create mode 100644 src/test/java/com/alibaba/qlexpress4/inport/Sample.java create mode 100644 src/test/java/com/alibaba/qlexpress4/inport/Sample1.java create mode 100644 src/test/java/com/alibaba/qlexpress4/pf4j/Pf4jClassSupplierTest.java create mode 100644 src/test/java/com/alibaba/qlexpress4/pf4j/TestPluginImpl.java create mode 100644 src/test/java/com/alibaba/qlexpress4/pf4j/TestPluginInterface.java create mode 100644 src/test/java/com/alibaba/qlexpress4/runtime/FixedSizeStackTest.java create mode 100644 src/test/java/com/alibaba/qlexpress4/runtime/MemberResolverTest.java create mode 100644 src/test/java/com/alibaba/qlexpress4/runtime/MockLeftValue.java create mode 100644 src/test/java/com/alibaba/qlexpress4/runtime/instruction/CallInstructionTest.java create mode 100644 src/test/java/com/alibaba/qlexpress4/runtime/instruction/GetFieldInstructionTest.java create mode 100644 src/test/java/com/alibaba/qlexpress4/runtime/instruction/InterWithDefaultMethod.java create mode 100644 src/test/java/com/alibaba/qlexpress4/runtime/instruction/MethodInvokeInstructionTest.java create mode 100644 src/test/java/com/alibaba/qlexpress4/runtime/instruction/MockCastParameters.java create mode 100644 src/test/java/com/alibaba/qlexpress4/runtime/instruction/MockParametersParentClass.java create mode 100644 src/test/java/com/alibaba/qlexpress4/runtime/instruction/MockQContextParent.java create mode 100644 src/test/java/com/alibaba/qlexpress4/runtime/instruction/NewInstanceInstructionTest.java create mode 100644 src/test/java/com/alibaba/qlexpress4/runtime/operator/MockValue.java create mode 100644 src/test/java/com/alibaba/qlexpress4/runtime/operator/unary/MinusMinusPrefixUnaryOperatorTest.java create mode 100644 src/test/java/com/alibaba/qlexpress4/runtime/operator/unary/MinusMinusSuffixUnaryOperatorTest.java create mode 100644 src/test/java/com/alibaba/qlexpress4/spring/HelloService.java create mode 100644 src/test/java/com/alibaba/qlexpress4/spring/QLExecuteService.java create mode 100644 src/test/java/com/alibaba/qlexpress4/spring/QLSpringContext.java create mode 100644 src/test/java/com/alibaba/qlexpress4/spring/SpringDemoTest.java create mode 100644 src/test/java/com/alibaba/qlexpress4/spring/SpringTestConfig.java create mode 100644 src/test/java/com/alibaba/qlexpress4/test/annotation/QL4AliasTest.java create mode 100644 src/test/java/com/alibaba/qlexpress4/test/constructor/HelloChild.java create mode 100644 src/test/java/com/alibaba/qlexpress4/test/constructor/HelloConstructor.java create mode 100644 src/test/java/com/alibaba/qlexpress4/test/constructor/HelloParent.java create mode 100644 src/test/java/com/alibaba/qlexpress4/test/function/HelloFunction.java create mode 100644 src/test/java/com/alibaba/qlexpress4/test/issue/Issue318Test.java create mode 100644 src/test/java/com/alibaba/qlexpress4/test/lambda/UserFunctionalInterface.java create mode 100644 src/test/java/com/alibaba/qlexpress4/test/method/InterWithDefault.java create mode 100644 src/test/java/com/alibaba/qlexpress4/test/method/TestChild.java create mode 100644 src/test/java/com/alibaba/qlexpress4/test/method/TestParent.java create mode 100644 src/test/java/com/alibaba/qlexpress4/test/property/Child.java create mode 100644 src/test/java/com/alibaba/qlexpress4/test/property/Child1.java create mode 100644 src/test/java/com/alibaba/qlexpress4/test/property/Child10.java create mode 100644 src/test/java/com/alibaba/qlexpress4/test/property/Child2.java create mode 100644 src/test/java/com/alibaba/qlexpress4/test/property/Child3.java create mode 100644 src/test/java/com/alibaba/qlexpress4/test/property/Child4.java create mode 100644 src/test/java/com/alibaba/qlexpress4/test/property/Child5.java create mode 100644 src/test/java/com/alibaba/qlexpress4/test/property/Child6.java create mode 100644 src/test/java/com/alibaba/qlexpress4/test/property/Child7.java create mode 100644 src/test/java/com/alibaba/qlexpress4/test/property/Child9.java create mode 100644 src/test/java/com/alibaba/qlexpress4/test/property/NumberConstructor.java create mode 100644 src/test/java/com/alibaba/qlexpress4/test/property/Parent.java create mode 100644 src/test/java/com/alibaba/qlexpress4/test/property/ParentClass.java create mode 100644 src/test/java/com/alibaba/qlexpress4/test/property/ParentParameters.java create mode 100644 src/test/java/com/alibaba/qlexpress4/test/property/Sample.java create mode 100644 src/test/java/com/alibaba/qlexpress4/test/property/SampleEnum.java create mode 100644 src/test/java/com/alibaba/qlexpress4/test/property/SampleForPrivate.java create mode 100644 src/test/java/com/alibaba/qlexpress4/test/property/SampleSet.java create mode 100644 src/test/java/com/alibaba/qlexpress4/test/property/SomeInter.java create mode 100644 src/test/java/com/alibaba/qlexpress4/test/property/TestEnum.java create mode 100644 src/test/java/com/alibaba/qlexpress4/test/qlalias/Order.java create mode 100644 src/test/java/com/alibaba/qlexpress4/test/qlalias/Patient.java create mode 100644 src/test/java/com/alibaba/qlexpress4/test/qlalias/Person.java create mode 100644 src/test/java/com/alibaba/qlexpress4/test/qlalias/User.java create mode 100644 src/test/java/com/alibaba/qlexpress4/test/stream/STObject.java create mode 100644 src/test/java/com/alibaba/qlexpress4/test/trycatch/SampleException.java create mode 100644 src/test/resources/perf/complexDataProcessing.ql create mode 100644 src/test/resources/test-plugins/build-plugin.sh create mode 100644 src/test/resources/test-plugins/plugin.properties create mode 100644 src/test/resources/test-plugins/test-plugin.jar create mode 100644 src/test/resources/testsuite/independent/array/array_index_out_of_bound.ql create mode 100644 src/test/resources/testsuite/independent/array/array_literal.ql create mode 100644 src/test/resources/testsuite/independent/array/float_index.ql create mode 100644 src/test/resources/testsuite/independent/array/invalid_index.ql create mode 100644 src/test/resources/testsuite/independent/array/max_arr_len.ql create mode 100644 src/test/resources/testsuite/independent/array/miss_comma_between_elements.ql create mode 100644 src/test/resources/testsuite/independent/array/no_rbrack_to_match.ql create mode 100644 src/test/resources/testsuite/independent/array/slice.ql create mode 100644 src/test/resources/testsuite/independent/array/unindexable.ql create mode 100644 src/test/resources/testsuite/independent/avoidnullpointer/avoid_null_pointer.ql create mode 100644 src/test/resources/testsuite/independent/avoidnullpointer/can_not_find_function.ql create mode 100644 src/test/resources/testsuite/independent/avoidnullpointer/get_from_null.ql create mode 100644 src/test/resources/testsuite/independent/avoidnullpointer/get_method_from_null.ql create mode 100644 src/test/resources/testsuite/independent/block/block_as_expr.ql create mode 100644 src/test/resources/testsuite/independent/block/block_at_if.ql create mode 100644 src/test/resources/testsuite/independent/block/lambda_with_block.ql create mode 100644 src/test/resources/testsuite/independent/block/missing_rbrace.ql create mode 100644 src/test/resources/testsuite/independent/block/return_at_block.ql create mode 100644 src/test/resources/testsuite/independent/bool/bool_literal.ql create mode 100644 src/test/resources/testsuite/independent/bool/bool_literal_is_keyword.ql create mode 100644 src/test/resources/testsuite/independent/bool/short_circuit.ql create mode 100644 src/test/resources/testsuite/independent/cast/cast_express.ql create mode 100644 src/test/resources/testsuite/independent/cast/null_cast.ql create mode 100644 src/test/resources/testsuite/independent/comment/comment.ql create mode 100644 src/test/resources/testsuite/independent/doc/convenient_syntax_elements.ql create mode 100644 src/test/resources/testsuite/independent/doc/dynamic_string.ql create mode 100644 src/test/resources/testsuite/independent/doc/dynamic_typing.ql create mode 100644 src/test/resources/testsuite/independent/doc/for.ql create mode 100644 src/test/resources/testsuite/independent/doc/for_each.ql create mode 100644 src/test/resources/testsuite/independent/doc/function.ql create mode 100644 src/test/resources/testsuite/independent/doc/if.ql create mode 100644 src/test/resources/testsuite/independent/doc/if_as_expr.ql create mode 100644 src/test/resources/testsuite/independent/doc/if_then.ql create mode 100644 src/test/resources/testsuite/independent/doc/lambda.ql create mode 100644 src/test/resources/testsuite/independent/doc/list_map_filter.ql create mode 100644 src/test/resources/testsuite/independent/doc/try_catch.ql create mode 100644 src/test/resources/testsuite/independent/doc/try_catch_as_expr.ql create mode 100644 src/test/resources/testsuite/independent/doc/while.ql create mode 100644 src/test/resources/testsuite/independent/extensionfunction/extension_function.ql create mode 100644 src/test/resources/testsuite/independent/for/break_continue.ql create mode 100644 src/test/resources/testsuite/independent/for/c_for.ql create mode 100644 src/test/resources/testsuite/independent/for/condition_not_bool.ql create mode 100644 src/test/resources/testsuite/independent/for/for_each.ql create mode 100644 src/test/resources/testsuite/independent/for/for_each_break_continue.ql create mode 100644 src/test/resources/testsuite/independent/for/for_each_invalid_type.ql create mode 100644 src/test/resources/testsuite/independent/for/for_each_not_iterable.ql create mode 100644 src/test/resources/testsuite/independent/for/infinite_loop.ql create mode 100644 src/test/resources/testsuite/independent/for/missing_lparen_at_for.ql create mode 100644 src/test/resources/testsuite/independent/for/missing_rparen_after_for_update.ql create mode 100644 src/test/resources/testsuite/independent/for/missing_rparen_at_for_each.ql create mode 100644 src/test/resources/testsuite/independent/for/missing_semi_after_for_init.ql create mode 100644 src/test/resources/testsuite/independent/for/return_from_for.ql create mode 100644 src/test/resources/testsuite/independent/function/complex_parameters.ql create mode 100644 src/test/resources/testsuite/independent/function/edge_cases.ql create mode 100644 src/test/resources/testsuite/independent/function/function_call.ql create mode 100644 src/test/resources/testsuite/independent/function/function_scoping.ql create mode 100644 src/test/resources/testsuite/independent/function/invalid_argument.ql create mode 100644 src/test/resources/testsuite/independent/function/mixed_declarations.ql create mode 100644 src/test/resources/testsuite/independent/function/multi_call.ql create mode 100644 src/test/resources/testsuite/independent/function/nested_function_calls.ql create mode 100644 src/test/resources/testsuite/independent/function/recursive_functions.ql create mode 100644 src/test/resources/testsuite/independent/function/return_left_value.ql create mode 100644 src/test/resources/testsuite/independent/if/if_as_expr.ql create mode 100644 src/test/resources/testsuite/independent/if/if_condition_not_bool.ql create mode 100644 src/test/resources/testsuite/independent/if/if_else_if.ql create mode 100644 src/test/resources/testsuite/independent/if/if_else_miss_body.ql create mode 100644 src/test/resources/testsuite/independent/if/if_followed_by_cast.ql create mode 100644 src/test/resources/testsuite/independent/if/if_miss_body.ql create mode 100644 src/test/resources/testsuite/independent/if/if_with_one_statement_body.ql create mode 100644 src/test/resources/testsuite/independent/if/if_without_condition.ql create mode 100644 src/test/resources/testsuite/independent/if/if_without_condition_2.ql create mode 100644 src/test/resources/testsuite/independent/if/miss_if_lparen.ql create mode 100644 src/test/resources/testsuite/independent/if/miss_if_rparen.ql create mode 100644 src/test/resources/testsuite/independent/if/return_at_if.ql create mode 100644 src/test/resources/testsuite/independent/if/simple_if.ql create mode 100644 src/test/resources/testsuite/independent/lambda/invalid_argument.ql create mode 100644 src/test/resources/testsuite/independent/lambda/invalid_argument_call.ql create mode 100644 src/test/resources/testsuite/independent/lambda/lambda_doc.ql create mode 100644 src/test/resources/testsuite/independent/lambda/lambda_return.ql create mode 100644 src/test/resources/testsuite/independent/lambda/simple_lambda.ql create mode 100644 src/test/resources/testsuite/independent/lambda/unmatch_param_num.ql create mode 100644 src/test/resources/testsuite/independent/macro/empty_macro.ql create mode 100644 src/test/resources/testsuite/independent/macro/invalid_macro_name.ql create mode 100644 src/test/resources/testsuite/independent/macro/macro.ql create mode 100644 src/test/resources/testsuite/independent/macro/macro_break_continue.ql create mode 100644 src/test/resources/testsuite/independent/macro/macro_control_flow.ql create mode 100644 src/test/resources/testsuite/independent/macro/macro_define_in_sub_scope.ql create mode 100644 src/test/resources/testsuite/independent/macro/macro_return.ql create mode 100644 src/test/resources/testsuite/independent/macro/missing_lbrace.ql create mode 100644 src/test/resources/testsuite/independent/map/colon_absent_between_map_entry.ql create mode 100644 src/test/resources/testsuite/independent/map/colon_absent_in_entry.ql create mode 100644 src/test/resources/testsuite/independent/map/invalid_map_key.ql create mode 100644 src/test/resources/testsuite/independent/map/key_word_can_not_get_from_field.ql create mode 100644 src/test/resources/testsuite/independent/map/keyword_key_map.ql create mode 100644 src/test/resources/testsuite/independent/map/map_at_block.ql create mode 100644 src/test/resources/testsuite/independent/map/map_define.ql create mode 100644 src/test/resources/testsuite/independent/map/string_literal_as_field_access.ql create mode 100644 src/test/resources/testsuite/independent/newlines/newlines.ql create mode 100644 src/test/resources/testsuite/independent/number/number.ql create mode 100644 src/test/resources/testsuite/independent/number/precise.ql create mode 100644 src/test/resources/testsuite/independent/operator/big_decimal.ql create mode 100644 src/test/resources/testsuite/independent/operator/big_integer.ql create mode 100644 src/test/resources/testsuite/independent/operator/bitwise.ql create mode 100644 src/test/resources/testsuite/independent/operator/boolean.ql create mode 100644 src/test/resources/testsuite/independent/operator/character.ql create mode 100644 src/test/resources/testsuite/independent/operator/comparable.ql create mode 100644 src/test/resources/testsuite/independent/operator/double.ql create mode 100644 src/test/resources/testsuite/independent/operator/equals.ql create mode 100644 src/test/resources/testsuite/independent/operator/in_not_in.ql create mode 100644 src/test/resources/testsuite/independent/operator/integer.ql create mode 100644 src/test/resources/testsuite/independent/operator/like.ql create mode 100644 src/test/resources/testsuite/independent/operator/logic.ql create mode 100644 src/test/resources/testsuite/independent/operator/optional_chaining.ql create mode 100644 src/test/resources/testsuite/independent/operator/string.ql create mode 100644 src/test/resources/testsuite/independent/scope/block_scope.ql create mode 100644 src/test/resources/testsuite/independent/scope/global_variable.ql create mode 100644 src/test/resources/testsuite/independent/scope/lexical_scope.ql create mode 100644 src/test/resources/testsuite/independent/scope/scope_cover.ql create mode 100644 src/test/resources/testsuite/independent/spread/arr_spread.ql create mode 100644 src/test/resources/testsuite/independent/spread/list_spread.ql create mode 100644 src/test/resources/testsuite/independent/spread/spread_avoid_null.ql create mode 100644 src/test/resources/testsuite/independent/string/char.ql create mode 100644 src/test/resources/testsuite/independent/string/interpolation.ql create mode 100644 src/test/resources/testsuite/independent/string/invalid_char.ql create mode 100644 src/test/resources/testsuite/independent/string/literal.ql create mode 100644 src/test/resources/testsuite/independent/string/string_escape.ql create mode 100644 src/test/resources/testsuite/independent/string/string_not_close.ql create mode 100644 src/test/resources/testsuite/independent/ternary/missing_colon.ql create mode 100644 src/test/resources/testsuite/independent/ternary/ternary.ql create mode 100644 src/test/resources/testsuite/independent/timeout/timeout.ql create mode 100644 src/test/resources/testsuite/independent/trycatch/catch_order.ql create mode 100644 src/test/resources/testsuite/independent/trycatch/missing_lbrace_at_try.ql create mode 100644 src/test/resources/testsuite/independent/trycatch/missing_lbrace_at_try_finally.ql create mode 100644 src/test/resources/testsuite/independent/trycatch/multi_exception_catch.ql create mode 100644 src/test/resources/testsuite/independent/trycatch/return_from_try.ql create mode 100644 src/test/resources/testsuite/independent/trycatch/throw_number.ql create mode 100644 src/test/resources/testsuite/independent/trycatch/try_catch_expr.ql create mode 100644 src/test/resources/testsuite/independent/trycatch/try_catch_final_scope.ql create mode 100644 src/test/resources/testsuite/independent/while/break_continue.ql create mode 100644 src/test/resources/testsuite/independent/while/condition_not_bool.ql create mode 100644 src/test/resources/testsuite/independent/while/missing_lparen.ql create mode 100644 src/test/resources/testsuite/independent/while/missing_rparen.ql create mode 100644 src/test/resources/testsuite/independent/while/while.ql create mode 100644 src/test/resources/testsuite/java/array/arr_index_out_of_bound.ql create mode 100644 src/test/resources/testsuite/java/array/arr_literal.ql create mode 100644 src/test/resources/testsuite/java/array/arr_with_init_item.ql create mode 100644 src/test/resources/testsuite/java/array/array_item_type_convert.ql create mode 100644 src/test/resources/testsuite/java/array/array_slice.ql create mode 100644 src/test/resources/testsuite/java/array/comma_absent.ql create mode 100644 src/test/resources/testsuite/java/array/invalid_arr_define.ql create mode 100644 src/test/resources/testsuite/java/array/invalid_arr_item.ql create mode 100644 src/test/resources/testsuite/java/array/invalid_arr_size_define.ql create mode 100644 src/test/resources/testsuite/java/array/invalid_arr_size_type.ql create mode 100644 src/test/resources/testsuite/java/array/multi_dim_array.ql create mode 100644 src/test/resources/testsuite/java/array/type_arr.ql create mode 100644 src/test/resources/testsuite/java/cast/assignable_cast.ql create mode 100644 src/test/resources/testsuite/java/cast/define_local_cast.ql create mode 100644 src/test/resources/testsuite/java/cast/object_cast.ql create mode 100644 src/test/resources/testsuite/java/cast/string_cast.ql create mode 100644 src/test/resources/testsuite/java/for/for_each_array.ql create mode 100644 src/test/resources/testsuite/java/generics/generics.ql create mode 100644 src/test/resources/testsuite/java/generics/invalid_type_bound.ql create mode 100644 src/test/resources/testsuite/java/implicit/arithmetic.ql create mode 100644 src/test/resources/testsuite/java/implicit/assignment_basic.ql create mode 100644 src/test/resources/testsuite/java/implicit/assignment_extend.ql create mode 100644 src/test/resources/testsuite/java/implicit/function_param.ql create mode 100644 src/test/resources/testsuite/java/implicit/incompatible_assignment_type.ql create mode 100644 src/test/resources/testsuite/java/implicit/packing.ql create mode 100644 src/test/resources/testsuite/java/implicit/pointer.ql create mode 100644 src/test/resources/testsuite/java/import/import_class.ql create mode 100644 src/test/resources/testsuite/java/import/import_not_at_beginning.ql create mode 100644 src/test/resources/testsuite/java/import/import_not_end_with_semi.ql create mode 100644 src/test/resources/testsuite/java/import/import_pack_not_end_with_semi.ql create mode 100644 src/test/resources/testsuite/java/import/import_package.ql create mode 100644 src/test/resources/testsuite/java/import/import_star.ql create mode 100644 src/test/resources/testsuite/java/import/incomplete_import.ql create mode 100644 src/test/resources/testsuite/java/import/invalid_package.ql create mode 100644 src/test/resources/testsuite/java/import/multi_import.ql create mode 100644 src/test/resources/testsuite/java/import/not_support_import_static.ql create mode 100644 src/test/resources/testsuite/java/lambda/java_functional_interface.ql create mode 100644 src/test/resources/testsuite/java/lambda/lambda_implicit.ql create mode 100644 src/test/resources/testsuite/java/lambda/lambda_method.ql create mode 100644 src/test/resources/testsuite/java/lambda/user_functional_interface.ql create mode 100644 src/test/resources/testsuite/java/map/classified_json.ql create mode 100644 src/test/resources/testsuite/java/map/equal_to_hash_map.ql create mode 100644 src/test/resources/testsuite/java/method/method_invoke.ql create mode 100644 src/test/resources/testsuite/java/method_reference/class_method.ql create mode 100644 src/test/resources/testsuite/java/method_reference/class_obj_method.ql create mode 100644 src/test/resources/testsuite/java/method_reference/method_not_found.ql create mode 100644 src/test/resources/testsuite/java/method_reference/object_method.ql create mode 100644 src/test/resources/testsuite/java/newexpr/new_resolver.ql create mode 100644 src/test/resources/testsuite/java/newexpr/noArgument.ql create mode 100644 src/test/resources/testsuite/java/newexpr/no_match_constructor.ql create mode 100644 src/test/resources/testsuite/java/number/long_max_value.ql create mode 100644 src/test/resources/testsuite/java/number/min_value_not_equal_to_hex.ql create mode 100644 src/test/resources/testsuite/java/number/number_auto_type.ql create mode 100644 src/test/resources/testsuite/java/number/number_invoke.ql create mode 100644 src/test/resources/testsuite/java/property/array_length_get.ql create mode 100644 src/test/resources/testsuite/java/property/class_get.ql create mode 100644 src/test/resources/testsuite/java/property/enum_get.ql create mode 100644 src/test/resources/testsuite/java/property/enum_get_not_exist.ql create mode 100644 src/test/resources/testsuite/java/property/enum_member_field.ql create mode 100644 src/test/resources/testsuite/java/property/interface_const_field.ql create mode 100644 src/test/resources/testsuite/java/property/jsonobject_vs_map_put.ql create mode 100644 src/test/resources/testsuite/java/property/null_set_invoke.ql create mode 100644 src/test/resources/testsuite/java/property/private_member_attr_access_get.ql create mode 100644 src/test/resources/testsuite/java/property/private_member_attr_access_set.ql create mode 100644 src/test/resources/testsuite/java/property/private_member_attr_getter.ql create mode 100644 src/test/resources/testsuite/java/property/private_member_attr_not_access_get.ql create mode 100644 src/test/resources/testsuite/java/property/private_member_attr_not_access_set.ql create mode 100644 src/test/resources/testsuite/java/property/private_member_attr_setter.ql create mode 100644 src/test/resources/testsuite/java/property/private_member_set_not_accessible.ql create mode 100644 src/test/resources/testsuite/java/property/public_member_set.ql create mode 100644 src/test/resources/testsuite/java/property/public_static.ql create mode 100644 src/test/resources/testsuite/java/stream/java_stream.ql create mode 100644 src/test/resources/testsuite/java/stream/java_stream_method_ref.ql create mode 100644 src/test/resources/testsuite/java/trycatch/catch_java_exception.ql create mode 100644 src/test/resources/testsuite/java/trycatch/catch_operator_exception.ql create mode 100644 src/test/resources/testsuite/java/trycatch/catch_order.ql create mode 100644 src/test/resources/testsuite/java/trycatch/ql_npe.ql diff --git a/.github/workflows/maven-publish.yml b/.github/workflows/maven-publish.yml new file mode 100644 index 0000000..98a2ae1 --- /dev/null +++ b/.github/workflows/maven-publish.yml @@ -0,0 +1,28 @@ +name: Maven Central Repo Deployment +on: + release: + types: [released, prereleased] +jobs: + publish: + runs-on: ubuntu-latest + steps: + - name: Checkout Git Repo + uses: actions/checkout@v2 + - name: Set up Maven Central Repo + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 8 + server-id: ossrh + server-username: 'OSSRH_USER' + server-password: 'OSSRH_PASSWORD' + gpg-passphrase: 'MAVEN_GPG_PASSPHRASE' + gpg-private-key: ${{ secrets.GPG_SECRET }} + - name: debug settings.xml + run: cat /home/runner/.m2/settings.xml + - name: Publish to Maven Central Repo + run: mvn clean deploy --batch-mode --activate-profiles deploy + env: + OSSRH_USER: ${{ secrets.OSSRH_TOKEN_USER }} + OSSRH_PASSWORD: ${{ secrets.OSSRH_TOKEN_PASSWORD }} + MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSWORD }} \ No newline at end of file diff --git a/.github/workflows/reduce-adoc.yml b/.github/workflows/reduce-adoc.yml new file mode 100644 index 0000000..7cc2945 --- /dev/null +++ b/.github/workflows/reduce-adoc.yml @@ -0,0 +1,42 @@ +name: Reduce Adoc +on: + push: + paths: + - '**/*-source.adoc' + branches: ['**'] + workflow_dispatch: + +permissions: + contents: write + +jobs: + reduce: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.2' + + - name: Install asciidoctor-reducer + run: gem install --no-document asciidoctor-reducer + + - name: Reduce all *-source.adoc files + shell: bash + run: | + set -euo pipefail + mapfile -d '' -t sources < <(find . -type f -name '*-source.adoc' -print0) + for src in "${sources[@]}"; do + out="${src%-source.adoc}.adoc" + mkdir -p "$(dirname "$out")" + echo "Reducing $src -> $out" + asciidoctor-reducer --preserve-conditionals -o "$out" "$src" + done + + - name: Commit reduced files + uses: EndBug/add-and-commit@v9 \ No newline at end of file diff --git a/.github/workflows/unittest.yml b/.github/workflows/unittest.yml new file mode 100644 index 0000000..2239b9f --- /dev/null +++ b/.github/workflows/unittest.yml @@ -0,0 +1,20 @@ +name: Java Unit Test with Maven + +on: [push] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up JDK 11 + uses: actions/setup-java@v3 + with: + java-version: '11' + distribution: 'temurin' + - name: Run tests with Maven + run: mvn -B clean test --file pom.xml + - name: Upload results to Codecov + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d51290c --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +# Created by .ignore support plugin (hsz.mobi) +### Java template +*.class + +target/* +*.iml +.idea/ +.spec/ +openspec/ +AGENTS.md +gen/ + +.vscode/ + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.ear +!src/test/resources/test-plugins/*.jar +dependency-reduced-pom.xml + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +test.log +test-push.sh + +.DS_Store + +src/main/antlr4/.antlr \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README-EN-source.adoc b/README-EN-source.adoc new file mode 100644 index 0000000..9bf3304 --- /dev/null +++ b/README-EN-source.adoc @@ -0,0 +1,1108 @@ +:toc: + += QLExpress + +image::images/logo.png[] + +link:README.adoc[【中文版】] | [English] + +image::https://api.star-history.com/svg?repos=alibaba/QLExpress&type=Date[Star History Chart] + +== Background Introduction + +QLExpress is an embedded Java dynamic scripting tool evolved from Alibaba's e-commerce business rules. It has strong influence within Alibaba Group, and was open-sourced in 2012 to continuously optimize itself and promote the spirit of open source contribution. + +Based on basic expression calculation, it has the following features: + +* Flexible customization capabilities - through Java API custom functions and operators, you can quickly implement business rule DSLs +* Compatible with the latest Java syntax, making it easy for Java programmers to get familiar quickly. Business personnel familiar with C-like languages will also find it very convenient to use +* Native JSON support for quickly defining complex data structures +* Friendly error reporting - whether compilation or runtime errors, it can precisely and friendly indicate error locations +* Unique expression tracing functionality that can trace the calculated values at intermediate nodes of expressions, helping business personnel or AI perform root cause analysis of online rule calculation results +* Secure by default - scripts are not allowed to interact with application code by default, but if interaction is needed, you can also define secure interaction methods yourself +* Interpreted execution, doesn't occupy JVM metaspace, can enable caching to improve interpretation performance +* Lightweight code with minimal dependencies, suitable for all Java runtime environments + +QLExpress4, as the latest evolution of QLExpress, rewrote the parsing engine based on Antlr4, further enhancing the original advantages and fully embracing functional programming, with further improvements in both performance and expressive power. + +Usage scenarios: + +* E-commerce coupon rule configuration: Quickly implement coupon rule DSLs through QLExpress custom functions and operators, allowing operations personnel to dynamically configure according to requirements +* Form building control association rule configuration: Form building platforms allow users to drag and drop controls to build custom forms, using QLExpress scripts to configure associations between different controls +* Workflow engine condition rule configuration +* Advertising system billing rule configuration + +\...\... + +== New Version Features + +The new version is not a simple functional refactoring of the old version, but our exploration of the next generation rule expression engine based on insights into user needs. It has many very practical but missing important features in other engines. + +=== Expression Calculation Tracing + +After business personnel complete rule script configuration, it's difficult to perceive their online execution situation. For example, in e-commerce promotion rules requiring users to satisfy the rule `isVip && not logged in for more than 10 days`. How many online users are blocked by the vip condition, and how many users are blocked by the login condition? This is just a simple rule with only two conditions, but the actual online situation is much more complex. + +Tracing online rule execution not only helps business personnel understand the actual online situation and troubleshoot and fix problems, but the accumulated data is also very valuable and can be used for subsequent rule optimization and business decisions. Below is a simplified product diagram of a rule platform performing root cause analysis and annotation decisions on rules based on QLExpress4's expression tracing capability: + +image::images/order_rules_cn.png[] + +The principle of root cause analysis lies in using QLExpress4's expression tracing capability to obtain the value of each intermediate result during expression calculation, and accordingly determine the cause of the final execution result. + +For specific usage methods, refer to: link:#expression-calculation-tracing-1[Expression Calculation Tracing] + +=== Native JSON Syntax Support + +QLExpress4 natively supports JSON syntax and can quickly define complex data structures. + +JSON arrays represent lists (List), while JSON objects represent mappings (Map), and complex objects can also be directly defined. + +Products can implement JSON mapping rules based on this feature, allowing users to conveniently define mapping relationships from one model to another. Below is a simplified product diagram of a rule platform implementing model mapping based on this capability: + +image::images/json_map.png[] + +For specific usage methods, refer to: link:#convenient-syntax-elements[Convenient Syntax Elements] + +=== Convenient String Processing + +QLExpress4 has targeted enhancements to string processing capabilities, allowing expressions to be directly embedded in strings through `$\{expression}`. + +For specific usage methods, refer to: link:#dynamic-strings[Dynamic Strings] + +=== Attachment Pass-through + +Normally, all information needed for script execution is in the `context`. Keys in the context can be referenced as variables in scripts and ultimately passed to custom functions or operators. + +However, for security or convenience reasons, some information is not wanted to be referenced by users through variables, such as tenant names, passwords, etc. + +At this time, this information can be passed to custom functions or operators through attachments. + +For specific usage methods, refer to: link:#adding-custom-functions-and-operators[Adding Custom Functions and Operators], where the `hello` custom function returns different welcome messages according to different tenants in the attachment. + +=== Functional Programming + +Functions are elevated to first-class citizens in QLExpress4, can be used as variables, and can also be returned as function return values. They can also be easily combined with common functional APIs in Java (such as Stream). + +Here's a simple QLExpress example script: + +[source,java] +---- +include::./src/test/resources/testsuite/independent/lambda/lambda_doc.ql[] +---- + +For more usage methods, refer to: + +* link:#lambda-expressions[Lambda Expressions] +* link:#list-filtering-and-mapping[List Filtering and Mapping] +* link:#stream-api[Stream API] +* link:#functional-interfaces[Functional Interfaces] + +=== Semicolon Simplification + +QLExpress4 supports omitting semicolons, making expressions more concise. For details, refer to link:#semicolons[Semicolons] + +== API Quick Start + +=== Adding Dependencies + +[source,xml] +---- + + com.alibaba + qlexpress4 + 4.0.7 + +---- + +Requirements + +* JDK 8 or higher + + +=== First QLExpress Program + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=firstQl] +---- + +More expression execution methods can be found in the documentation link:docs/execute-en.adoc[Expression Execution] + +=== Adding Custom Functions and Operators + +The simplest way is to quickly define function/operator logic through Java Lambda expressions: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=addFunctionAndOperator] +---- + +If the logic of custom functions is complex, or you need to obtain script context information, you can also implement it by inheriting `CustomFunction`. + +For example, the following `hello` custom function returns different welcome messages according to different tenants: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/test/function/HelloFunction.java[] +---- + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=customComplexFunction] +---- + +QLExpress4 also supports adding custom functions through QLExpress scripts. Note that variables defined outside of functions (such as defineTime in the example) are initialized when the function is defined and will not be recalculated when the function is subsequently called. + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=addFunctionsDefinedInScript] +---- + +It is recommended to use Java to define custom functions as much as possible, as this can provide better performance and stability. + +For more ways to customize syntax elements, see the documentation link:docs/custom-item-en.adoc[Custom Syntax Elements]. + +=== Validating Syntax Correctness + +To validate syntax correctness without executing scripts, including operator restriction validation: call `check` and catch exceptions. If `QLSyntaxException` is caught, it indicates syntax errors exist. + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=checkSyntax] +---- + +You can use `CheckOptions` to configure more fine-grained syntax validation rules, supporting the following two options: + +1. `operatorCheckStrategy`: Operator validation strategy, used to restrict which operators can be used in the script +2. `disableFunctionCalls`: Whether to disable function calls, default is false + +Example 1: Using Operator Validation Strategy (Whitelist) + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/OperatorLimitTest.java[tag=operatorCheckStrategyExample] +---- + +Example 2: Disabling Function Calls + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=disableFunctionCallsExample] +---- + +=== Parsing External Variables Required by Scripts + +Some variables used in scripts are generated within the script, while others need to be passed in from outside through `context`. + +QLExpress4 provides a method to parse all variables that need to be passed in from outside in the script: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=getOutVarNames] +---- + +More script-dependency parsing tools: + +* `getOutFunctions` – parses all functions that must be defined externally +* `getOutVarAttrs` – parses all variables (and the attributes they reference) that must be supplied from outside; an enhanced version of `getOutVarNames` + +=== High-Precision Calculation + +QLExpress internally uses BigDecimal to represent all numbers that cannot be precisely represented by double, to represent calculation precision as much as possible: + +> Example: 0.1 cannot be precisely represented in double + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=bigDecimalForPrecise] +---- + +This approach can solve some calculation precision problems: + +For example, 0.1+0.2 is not equal to 0.3 in Java due to precision issues. +QLExpress can automatically identify that 0.1 and 0.2 cannot be precisely represented by double precision, and change to use BigDecimal representation to ensure the result equals 0.3 + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=preciseComparisonWithJava] +---- + +In addition to the default precision guarantee, there's also a `precise` switch. When turned on, all calculations use BigDecimal to prevent problems caused by low-precision numbers passed in from outside: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=preciseSwitch] +---- + +=== Security Strategy + +QLExpress4 adopts isolation security strategy by default, not allowing scripts to access Java object fields and methods, which ensures script execution security. If you need to access Java objects, you can configure through different security strategies. + +Assuming the application has the following Java class: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/inport/MyDesk.java[] +---- + +The script execution context is set as follows: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=securityStrategyContextSetup] +---- + +QLExpress4 provides four security strategies: + +==== 1. Isolation Strategy (Default) + +By default, QLExpress4 adopts isolation strategy, not allowing access to any fields and methods: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=securityStrategyIsolation] +---- + +==== 2. Blacklist Strategy + +Through blacklist strategy, you can prohibit access to specific fields or methods, while other fields and methods can be accessed normally: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=securityStrategyBlackList] +---- + +==== 3. Whitelist Strategy + +Through whitelist strategy, only specified fields or methods are allowed to be accessed, while other fields and methods are prohibited: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=securityStrategyWhiteList] +---- + +==== 4. Open Strategy + +Open strategy allows access to all fields and methods, similar to QLExpress3 behavior, but security risks need to be noted: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=securityStrategyOpen] +---- + +> Note: While open strategy provides maximum flexibility, it also brings security risks. It's recommended to use only in trusted environments and not recommended for processing end-user input scripts. + +==== Strategy Recommendations + +It's recommended to directly adopt the default strategy and not directly call Java object fields and methods in scripts. Instead, provide system functionality to embedded scripts through custom functions and operators (refer to link:#adding-custom-functions-and-operators[Adding Custom Functions and Operators]). This can ensure both script security and flexibility, with better user experience. + +If you do need to call Java object fields and methods, at least use whitelist strategy to provide limited access permissions to scripts. + +As for blacklist and open strategies, they're not recommended for external input script scenarios unless you ensure each script will be reviewed. + +=== Calling Java Classes in Applications + +> Requires relaxing security strategy, not recommended for end-user input + +Assuming the application has the following Java class (`com.alibaba.qlexpress4.QLImportTester`): + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/QLImportTester.java[] +---- + +In QLExpress, there are two calling methods. + +==== 1. Using `import` Statement in Scripts to Import Classes and Use + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=importJavaCls] +---- + +==== 2. Default Import When Creating `Express4Runner`, No Additional `import` Statement Needed in Scripts + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=defaultImport] +---- + +In addition to using `ImportManager.importCls` to import individual classes, there are other more convenient import methods: + + * `ImportManager.importPack` directly imports all classes under a package path, such as `ImportManager.importPack("java.util")` will import all classes under the `java.util` package. QLExpress defaults to importing the following packages: + ** `ImportManager.importPack("java.lang")` + ** `ImportManager.importPack("java.util")` + ** `ImportManager.importPack("java.math")` + ** `ImportManager.importPack("java.util.stream")` + ** `ImportManager.importPack("java.util.function")` + * `ImportManager.importInnerCls` imports all inner classes in a given class path + +=== Custom ClassLoader + +QLExpress4 supports specifying class loaders through custom `ClassSupplier`, which is very useful in scenarios like plugin architecture and modular applications. Through custom class loaders, QLExpress scripts can access classes in specific ClassLoaders. + +The following example shows how to integrate with the link:https://pf4j.org/[PF4J] plugin framework to allow QLExpress scripts to access classes in plugins: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/pf4j/Pf4jClassSupplierTest.java[tag=pluginClassSupplier] +---- + +Typical application scenarios for custom ClassSupplier: + +* **Plugin Architecture**: Allow scripts to access classes and interfaces defined in plugins +* **Modular Applications**: In modular frameworks like OSGi, allow scripts to access classes in specific modules +* **Dynamic Class Loading**: Load classes from remote repositories or dynamically generated bytecode +* **Class Isolation**: Use different ClassLoaders to achieve class isolation + +=== Expression Caching + +Through the `cache` option, you can enable expression caching, so the same expressions won't be recompiled, greatly improving performance. + +Note that this cache has no size limit and is only suitable for use when expressions are in limited quantities: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=cacheSwitch] +---- + +However, when scripts are executed for the first time, they're still relatively slow because there's no cache. + +You can cache scripts before first execution using the following method to ensure first execution speed: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=parseToCache] +---- + +Note that this cache has an unlimited size; be sure to control its size in your application. You can periodically clear the compilation cache by calling the `clearCompileCache` method. + +=== Clearing DFA Cache + +QLExpress uses ANTLR4 as its parsing engine. ANTLR4 builds a DFA (Deterministic Finite Automaton) cache at runtime to accelerate subsequent syntax parsing. This cache consumes a certain amount of memory. + +In some memory-sensitive scenarios, you can call the `clearDFACache` method to clear the DFA cache and release memory. + +> **IMPORTANT WARNING**: Clearing the DFA cache 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 option to avoid recompilation. Example code: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/ClearDfaCacheTest.java[tag=clearDFACacheBestPractice] +---- + +By following this approach, you can minimize memory usage while maintaining performance. + +=== Setting Timeout + +You can set a timeout for scripts to prevent infinite loops or other reasons from causing excessive consumption of application resources. + +The following example code sets a 10ms timeout for the script: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=scripTimeout] +---- + +> Note: For system performance considerations, QLExpress's detection of timeout is not accurate. Especially for timeouts occurring in Java code callbacks (such as custom functions or operators), they won't be detected immediately. Only after execution is complete and back to QLExpress runtime will they be detected and execution interrupted. + +=== Extension Functions + +Using QLExpress's extension function capability, you can add additional member methods to Java classes. + +Extension functions are implemented based on QLExpress runtime, so they're only effective in QLExpress scripts. + +The following example code adds a `hello()` extension function to the String class: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=extensionFunction] +---- + +=== Java Class Object, Field, and Method Aliases + +QLExpress supports defining one or more aliases for objects, fields, or methods through the `QLAlias` annotation, making it convenient for non-technical personnel to use expressions to define rules. + +In the following example, the final order amount is calculated based on whether the user is a vip. + +User class definition: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/test/qlalias/User.java[] +---- + +Order class definition: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/test/qlalias/Order.java[] +---- + +Calculate final order amount through QLExpress script rules: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=qlAlias] +---- + +=== Keyword, Operator, and Function Aliases + +To further facilitate non-technical personnel in writing rules, QLExpress provides `addAlias` to add aliases to original keywords, operators, and functions, making the entire script expression more natural language-like. + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=addAlias] +---- + +Keywords that support setting aliases include: + + * if + * then + * else + * for + * while + * break + * continue + * return + * function + * macro + * new + * null + * true + * false + +> Note: Some familiar usages are actually operators, not keywords, such as the `in` operator. All operators and functions support aliases by default + +=== Macros + +Macros are a powerful code reuse mechanism in QLExpress that allows users to define reusable script fragments and call them when needed. Unlike simple text replacement, QLExpress macros are implemented based on instruction replay mechanism, providing better performance and semantic accuracy. + +Macros are particularly suitable for the following scenarios: + +* **Code Reuse**: Encapsulate commonly used script fragments into macros to avoid repeatedly writing the same logic +* **Business Rule Templates**: Define standard business rule templates such as price calculation, permission checking, etc. +* **Flow Control**: Encapsulate complex control flows such as conditional judgment, loop logic, etc. +* **DSL Construction**: As basic components for building domain-specific languages + +Macros can be defined in two ways: + +**1. Using `macro` keyword in scripts** + +[source,java] +---- +include::./src/test/resources/testsuite/independent/macro/macro.ql[tag=addMacroInScript] +---- + +**2. Adding through Java API** + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=addMacro] +---- + +Differences between macros and functions: + +[cols="1,1,1"] +|=== +| Feature | Macro | Function +| Parameter Passing | No parameters, relies on context variables | Supports parameter passing +| Performance | Direct instruction insertion, no call overhead | Has function call overhead +| Scope | Shares caller's scope | Independent scope +| Applicable Scenarios | Code fragment reuse | Logic encapsulation and parameterization +|=== + +Macros are particularly suitable for code fragment reuse scenarios that don't require parameter passing and mainly rely on context variables, while functions are more suitable for scenarios requiring parameterization and independent scope. + +**Changes in macro features in QLExpress4 compared to version 3**: + + * Version 4's macro implementation is closer to the definition of macros in common programming languages, equivalent to inserting predefined code fragments at the macro's location, sharing the same scope as the call point. `return`, `continue`, and `break` in macros can affect the control flow of the caller. However, version 3's implementation is actually closer to parameterless function calls. + * Version 4's macros cannot be used as variables, only when standing alone as a line statement can they be macro-replaced. Because macros can be arbitrary scripts, not necessarily expressions with return values, using them as variables would have semantic issues. Version 3's macros are essentially parameterless function calls, so they're often used as variables. + +If you want to be compatible with version 3's macro features, it's recommended to use link:#dynamic-variables[Dynamic Variables] + +=== Dynamic Variables + +Regular "static variables" are fixed values associated with keys in the context. Dynamic variables can be expressions calculated from other variables. Dynamic variables support nesting, meaning dynamic variables can depend on another dynamic variable for calculation. + +Example: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=dynamicVar] +---- + +=== Expression Calculation Tracing + +If relevant options are enabled, QLExpress4 will return an expression trace tree along with the rule script calculation result. The structure of the expression trace tree is similar to a syntax tree, with the difference that it records intermediate results of this execution at each node. + +For example, for the expression `!true || myTest(a, 1)`, the structure of the expression trace tree is roughly as follows: + +[source] +---- + || true + / \ + ! false myTest + / / \ + true a 10 1 +---- + +It can be applied to various scenarios: + + * Help business personnel analyze and troubleshoot rule calculation results + * Sample and categorize rules judged as false online + * AI automatic diagnosis and repair of rules + +Node calculation results are placed in the `value` field of the `ExpressionTrace` object. If short-circuit occurs in the middle causing some expressions to not be calculated, the `evaluated` field of the `ExpressionTrace` object will be set to false. Code example: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=expressionTrace] +---- + +> Note: You must set the `InitOptions.traceExpression` option to true when creating `Express4Runner`, and set `QLOptions.traceExpression` to true when executing scripts for this feature to take effect. + +You can also get all expression trace points without executing scripts: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=getExpressionTracePoints] +---- + +Supported expression trace point types and corresponding child node meanings are as follows: + +[cols="1,1,1"] +|=== +| Node Type | Node Meaning | Child Node Meaning +| OPERATOR | Operator | Both operands +| FUNCTION | Function | Function parameters +| METHOD | Method | Method parameters +| FIELD | Field | Target object for field access +| LIST | List | List elements +| MAP | Field | None +| IF | Conditional branch | Condition expression, then logic block and else logic block +| RETURN | Return statement | Return expression +| VARIABLE | Variable | None +| VALUE | Literal value | None +| DEFINE_FUNCTION | Define function | None +| DEFINE_MACRO | Define macro | None +| PRIMARY | Other composite values not yet drilled down (such as dictionaries, if, etc.) | None +| STATEMENT | Other composite statements not yet drilled down (such as while, for, etc.) | None +|=== + +=== Spring Integration + +QLExpress doesn't need special integration with Spring, just a `Express4Runner` singleton is sufficient. + +The "integration" example provided here allows direct reference to any Spring Bean in QLExpress scripts. + +While this approach is very convenient, it gives scripts too much permission and freedom. It's no longer recommended. It's still recommended to only put objects that users are allowed to access in the context. + +Core integration components: + +* link:src/test/java/com/alibaba/qlexpress4/spring/QLSpringContext.java[QLSpringContext]: Implements the `ExpressContext` interface, providing access capability to the Spring container. It first looks for variables from the passed context, and if not found, tries to get beans with the same name from the Spring container. +* link:src/test/java/com/alibaba/qlexpress4/spring/QLExecuteService.java[QLExecuteService]: Encapsulates QLExpress execution logic, integrates with Spring container, convenient for use in Spring applications. + +Assuming there's a Spring Bean named `helloService`: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/spring/HelloService.java[] +---- + +Call this Bean in scripts: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/spring/SpringDemoTest.java[] +---- + + +== Syntax Introduction + +QLExpress4 is compatible with Java8 syntax while also providing many more flexible and loose syntax patterns to help users write expressions more quickly. + +Based on expression-first syntax design, complex conditional judgment statements can also be directly used as expressions. + +Code snippets appearing in this chapter are all qlexpress scripts. +`assert` is an assertion method injected by the test framework into the engine, ensuring its parameter is `true`. +`assertErrCode` ensures that the lambda parameter expression execution will definitely throw a QLException containing the second parameter error code. + +=== Variable Declaration + +Supports both static typing and dynamic typing: + + * When declaring variables without writing types, the variable is dynamic type and also an assignment expression + * When declaring variables with types, it's static type, and at this time it's a variable declaration statement + +[source,java] +---- +include::./src/test/resources/testsuite/independent/doc/dynamic_typing.ql[] +---- + +=== Convenient Syntax Elements + +Common syntax elements like lists (List), mappings (Map), etc. all have very convenient construction syntax sugar in QLExpress: + +[source,java] +---- +include::./src/test/resources/testsuite/independent/doc/convenient_syntax_elements.ql[] +---- + +Through the `*.` operator, you can quickly process lists and mappings, such as taking properties of list elements, or getting key lists and value lists of mappings: + +[source,java] +---- +include::./src/test/resources/testsuite/independent/spread/list_spread.ql[tag=spreadExample] +---- + +In mappings, by specifying the fully qualified name of the type through the `@class` key, you can directly use JSON to create complex Java objects. For example, the following MyHome is a Java class with complex nested types: + +[source,java] +---- +include::./src/test/java/com/alibaba/qlexpress4/inport/MyHome.java[] +---- + +You can conveniently create it through the following QLExpress script: + +> Note: This feature requires opening security options as referenced in link:#security-strategy[Security Strategy] to execute normally. + +[source,java] +---- +include::./src/test/resources/testsuite/java/map/classified_json.ql[tag=classifiedJson] +---- + +=== Numbers + +For numbers without declared types, +QLExpress will automatically select the most appropriate one from data types like int, long, BigInteger, double, BigDecimal based on their range: + +[source,java] +---- +include::./src/test/resources/testsuite/java/number/number_auto_type.ql[] +---- + +Therefore, when writing custom functions or operators, it's recommended to use Number type for receiving, because numeric types cannot be determined in advance. + +=== Dynamic Strings + +Dynamic strings are a new capability introduced in QLExpress version 4 to enhance string processing capabilities. + +Supports inserting expression calculations in strings through the `$\{expression}` format: + +> If you want to keep `$\{expression}` as-is in strings, you can escape `$` using `\$` + +[source,java] +---- +include::./src/test/resources/testsuite/independent/doc/dynamic_string.ql[] +---- + +If you want QLExpress4 strings to maintain compatibility with version 3 and not process interpolation expressions, you can directly turn off this feature when creating `Express4Runner`: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=disableInterpolation] +---- + +=== Template Rendering + +Leveraging dynamic strings, QLExpress4 can also be used as a lightweight template engine. + +You don't need to manually add string quotes in the script. Just call `executeTemplate` to render the template string: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=templateEngine] +---- + +=== Placeholders + +Placeholders are used to extract values of arbitrary keys from the context. + +Global variables can also extract values from the context, but are limited by QLExpress keywords and syntax, so the keys that can be extracted are limited. +For example, the value corresponding to key "0" in the context cannot be extracted through variables, because 0 is not a legal variable in QLExpress, but a numeric constant. +At this time, you can use the default placeholder `$\{0}` to extract it. + +> Note the distinction from interpolation in dynamic strings. Placeholders are written outside strings. Dynamic string interpolation is `$\{expression}`, where expressions are written by default, and the running result of `"${0}"` is `"0"`. Placeholders are `$\{placeholder}`, where keys in the context are written by default, and the running result of `${0}` is the value corresponding to key "0" in the context. + +QLExpress uses `${placeholder}` format placeholders by default, where: + +* `${` is the start marker +* `}` is the end marker +* `placeholder` is the placeholder content, corresponding to the key in the context + +In addition to default placeholders, QLExpress also supports customizing the start and end markers of placeholders: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=customSelector] +---- + +Custom placeholders are not arbitrary, with the following restrictions: + +* **Start Marker Restriction**: `selectorStart` must be one of the following four formats: + ** `${` (default) + ** `$[` + ** `#{` + ** `#[` +* **End Marker Restriction**: `selectorEnd` must be a string of 1 or more characters + +=== Semicolons + +Expression statements can omit ending semicolons, and the return value of the entire script is the calculation result of the last expression. + +The return value of the following script is 2: + +[source,java] +---- +a = 1 +b = 2 +// last express +1+1 +---- + +Equivalent to the following写法: + +[source,java] +---- +a = 1 +b = 2 +// return statment +return 1+1; +---- + +Because semicolons can be omitted, QLExpress4's handling of line breaks is stricter compared to version 3 or Java language. If you want to split multi-line expressions into multiple lines, it's recommended to keep operators on the current line and move the right operand to the next line. + +The following multi-line expression will report a syntax error (counterexample): + +[source,java] +---- +// syntax error +a ++ b +---- + +The following is a correct line break example (positive example): + +[source,java] +---- +a + +b +---- + +Other syntax habits remain consistent with Java. + +=== Expressions + +QLExpress adopts expression-first design, where almost everything is an expression except for import, return, and loop structures. + +if statements are also expressions: + +[source,java] +---- +include::./src/test/resources/testsuite/independent/doc/if_as_expr.ql[] +---- + +try catch structures are also expressions: + +[source,java] +---- +include::./src/test/resources/testsuite/independent/doc/try_catch_as_expr.ql[] +---- + +=== Short-Circuit Evaluation + +Similar to Java, `&&` and `||` logical operations are all short-circuit evaluations. + +For example, the expression `false && (1/0)` won't cause a division by zero error because `&&` short-circuits at the initial `false`. + +Short-circuit evaluation is enabled by default, and the engine also provides options to turn off short-circuit for a specific execution: + +> One scenario for turning off short-circuit is to ensure full warm-up of expressions + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=disableShortCircuit] +---- + + +=== Control Structures + +==== if Branch + +In addition to being fully compatible with Java's `if` syntax, it also supports rule engine-like `if ... then ... else ...` syntax, where `then` can be treated as an omittable keyword: + +[source,java] +---- +include::./src/test/resources/testsuite/independent/doc/if.ql[] +---- + +==== while Loop + +[source,java] +---- +include::./src/test/resources/testsuite/independent/doc/while.ql[] +---- + +==== for Loop + +[source,java] +---- +include::./src/test/resources/testsuite/independent/doc/for.ql[] +---- + +==== for-each Loop + +[source,java] +---- +include::./src/test/resources/testsuite/independent/doc/for_each.ql[] +---- + +==== try-catch + +[source,java] +---- +include::./src/test/resources/testsuite/independent/doc/try_catch.ql[] +---- + +=== Function Definition + +[source,java] +---- +include::./src/test/resources/testsuite/independent/doc/function.ql[] +---- + +=== Lambda Expressions + +In QLExpress4, Lambda expressions as first-class citizens can be passed as variables or returned. + +[source,java] +---- +include::./src/test/resources/testsuite/independent/doc/lambda.ql[] +---- + +=== List Filtering and Mapping + +Supports direct functional filtering and mapping of list types through filter, map methods. + +Implemented by adding link:#extension-functions[Extension Functions] to list types, note the distinction from methods with the same names in Stream API. + +Compared to Stream API, it can directly operate on lists, and return values are also directly lists, making it more convenient. + +[source,java] +---- +include::./src/test/resources/testsuite/independent/doc/list_map_filter.ql[] +---- + +=== Java8 Syntax Compatibility + +QLExpress can be compatible with common Java8 syntax. + +Such as link:#for-each-loop[for each loops], Stream API, functional interfaces, etc. + +==== Stream API + +You can directly use stream APIs in Java collections to operate on collections. + +Because these stream APIs are all from methods in Java, refer to link:#security-strategy[Security Strategy] to open security options for the following scripts to execute normally. + +[source,java] +---- +include::./src/test/resources/testsuite/java/stream/java_stream.ql[] +---- + +==== Functional Interfaces + +Java8 introduced functional interfaces like Function, Consumer, Predicate, etc. QLExpress's link:#lambda-expressions[Lambda Expressions] can be assigned to these interfaces or used as method parameters that receive these interfaces: + +[source,java] +---- +include::./src/test/resources/testsuite/java/lambda/java_functional_interface.ql[] +---- + +== Appendix I Upgrade Guide + +QLExpress's previous version had significant gaps with the industry in various features due to years of iteration stagnation. + +One of QLExpress4's goals is to make up for these gaps at once, so it chose to make bold upgrades while intentionally abandoning some compatibility. Of course, basic functionality and experience still align with the previous version. + +If your system already uses the old version of QLExpress, you must conduct a comprehensive regression test before upgrading to ensure all these scripts can execute normally in the new version before upgrading. + +If you don't have time or methods to verify them one by one, then upgrading is not recommended. + +If it's a new system, it's recommended to directly adopt QLExpress4. QLExpress4's ecosystem will become increasingly perfect in the future, while version 3 will be gradually abandoned. + +Below lists the main differences between the new and old versions to help users upgrade existing scripts. If anything is missing, feedback is welcome: + +=== Default Security Strategy + +If you completely use default options, accessing Java object fields (`o.field`) or calling member methods (`o.method()`) will throw `FIELD_NOT_FOUND` and `METHOD_NOT_FOUND` errors respectively. + +This is because version 3 could unrestrictedly access any fields and methods in Java application systems through reflection, which is considered unsafe in embedded scripts. + +If you want to be compatible with version 3's behavior, when creating `Express4Runner`, you need to set the security strategy to "open", refer to the following code: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=securityStrategyOpen] +---- + +For details, refer to the link:#security-strategy[Security Strategy] chapter. + +=== Defining Mappings + +QLExpress old version supported quickly creating mappings through `NewMap(key:value)` syntax. Although not discussed in detail in documentation, many users learned and used this syntax through unit tests and inquiries. + +However, this syntax was too customized and differed greatly from industry standards, so it was removed in the new version. + +The new version natively supports JSON syntax and directly adopts JSON dictionary format (`{key:value}`) to quickly create mappings, making it more intuitive. + +For details, refer to link:#convenient-syntax-elements[Convenient Syntax Elements] + +=== Global Variable Context Pollution + +QLExpress supports passing in a global context when executing scripts, i.e., the context parameter. + +In the old version, if global variables were defined in scripts, these variables would also be written to the context. After script execution, you could obtain the values of global variables defined in scripts through the context. + +An old version example is as follows: + +[source,java] +---- +// only for QLExpress 3.x + +String express = "a=3;a+1"; +ExpressRunner runner = new ExpressRunner(false, true); +DefaultContext context = new DefaultContext<>(); + +Object res = runner.execute(express, context, null, true, true); +// The result of the script execution should be 4 (a+1) +Assert.assertEquals(4, res); +// The variable 'a' defined in the script is also stored in the context +Assert.assertEquals(3, context.get("a")); +---- + +Based on research and feedback, we believe this would cause the global context to be "polluted" by scripts, posing security risks. + +Therefore, in QLExpress4, global variables are not written to the context by default. + +If you want to be compatible with version 3's features, you need to set the `polluteUserContext` option to `true`, refer to the following code: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=polluteUserContext] +---- + +=== Semicolons Can Be Omitted + +"Omittable semicolons" is already a standard feature of modern scripting languages, and QLExpress4 has also followed this trend - semicolons can be omitted. + +For details, refer to the link:#semicolons[Semicolons] chapter. + +=== Strict Newline Mode + +Since QLExpress4 supports omitting semicolons, the interpreter needs to use newlines to determine whether a statement has ended. Therefore, QLExpress4 has stricter requirements for newlines than QLExpress3. + +The following script is valid in QLExpress3 but not in QLExpress4: + +[source,java] +---- +// Valid in QLExpress3, but not in QLExpress4 +商家应收= + 价格 + - 饭卡商家承担 + + 平台补贴 +---- + +In QLExpress4, the above script will be parsed as two separate statements: +1. `商家应收 = 价格` +2. `- 饭卡商家承担 + 平台补贴` (the second statement will result in a syntax error) + +To achieve the same effect in QLExpress4, you need to place the operator at the end of the line rather than at the beginning: + +[source,java] +---- +// Correct way in QLExpress4 +商家应收= + 价格 - + 饭卡商家承担 + + 平台补贴 +---- + +This way, the interpreter knows that the current line's expression is not yet complete and will continue reading the next line. + +If you need to be compatible with QLExpress3's newline behavior, you can set the `strictNewLines` option to `false`: + +[source,java] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=notStrictNewLinesTest] +---- + +Note: Non-strict newline mode causes the interpreter to ignore all newlines, which may affect code readability and the accuracy of error messages. It is recommended to use this only when you need to be compatible with legacy code. + +=== Obtaining char Type + +In QLExpress 3, single characters wrapped in single quotes were parsed as char type, not String. + +This caused much confusion for users, for example, the result of `"a"=='a'` would be `false`. + +So later in QLExpress 3, the `ExpressRunner.setIgnoreConstChar` option was added. When set to `true`, all characters wrapped in single quotes and double quotes would be parsed as String type. However, this option was turned off by default and required users to manually enable it. + +Considering that script users rarely use the `char` primitive type, we directly removed this option in QLExpress 4. All characters wrapped in single quotes and double quotes are now parsed as String type. + +If you still need to use `char` type in scripts, you can obtain it through two methods: + +* Type casting: `(char) 'a'` +* Type declaration: `char a = 'a'` + +== Appendix II How to Contribute? + +QLExpress is completely open to community changes. Any suggestions and modifications will be welcome, and after discussion, reasonable ones will be accepted into the main branch. + +First, you need to clone the code to local. Before formally modifying the code, you need to prepare as follows: + +1. Execute `mvn compile` in the project root directory: When the project is first downloaded locally, there will be many classes not found, so you need to generate Antlr4 runtime code first +2. Configure code formatting: QLExpress project has unified code format specifications, and automatic formatting needs to be configured before git commits during development + +Create a new file `.git/hooks/pre-commit` in the project directory with the following content: + +[source,bash] +---- +#!/bin/sh +mvn spotless:apply +git add -u +exit 0 +---- + +This way, before each git commit, maven's spotless plugin will automatically execute code formatting. For specific code format configuration, see link:spotless_eclipse_formatter.xml[] + +3. Run unit tests: After completing code development, run all unit tests locally to ensure code quality + - JDK 8 environment: Execute `mvn test` + - JDK 9 and above environment: Execute `mvn test -DargLine="--add-opens java.base/java.util.stream=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED"` + +== Appendix III QLExpress4 Performance Improvements + +link:https://www.yuque.com/xuanheng-ffjti/iunlps/pgfzw46zel2xfnie?singleDoc#%20%E3%80%8AQLExpress3%E4%B8%8E4%E6%80%A7%E8%83%BD%E5%AF%B9%E6%AF%94%E3%80%8B[QLExpress4 vs 3 Performance Comparison] + +Summary: In common scenarios, without compilation cache, QLExpress4 can achieve nearly 10x performance improvement over version 3; with compilation cache, there's also a 1x performance improvement. + +== Appendix IV Developer Contact Information + + * Email: + ** qinyuan.dqy@alibaba-inc.com + ** yumin.pym@taobao.com + ** 704643716@qq.com + * WeChat: + ** xuanheng: dqy932087612 + ** binggou: pymbupt + ** linxiang: tkk33362 + * DingTalk Support Group + +image::images/qlexpress_support_group_qr_2026.jpg[] diff --git a/README-EN.adoc b/README-EN.adoc new file mode 100644 index 0000000..1d72485 --- /dev/null +++ b/README-EN.adoc @@ -0,0 +1,1893 @@ +:toc: + += QLExpress + +image::images/logo.png[] + +link:README.adoc[【中文版】] | [English] + +image::https://api.star-history.com/svg?repos=alibaba/QLExpress&type=Date[Star History Chart] + +== Background Introduction + +QLExpress is an embedded Java dynamic scripting tool evolved from Alibaba's e-commerce business rules. It has strong influence within Alibaba Group, and was open-sourced in 2012 to continuously optimize itself and promote the spirit of open source contribution. + +Based on basic expression calculation, it has the following features: + +* Flexible customization capabilities - through Java API custom functions and operators, you can quickly implement business rule DSLs +* Compatible with the latest Java syntax, making it easy for Java programmers to get familiar quickly. Business personnel familiar with C-like languages will also find it very convenient to use +* Native JSON support for quickly defining complex data structures +* Friendly error reporting - whether compilation or runtime errors, it can precisely and friendly indicate error locations +* Unique expression tracing functionality that can trace the calculated values at intermediate nodes of expressions, helping business personnel or AI perform root cause analysis of online rule calculation results +* Secure by default - scripts are not allowed to interact with application code by default, but if interaction is needed, you can also define secure interaction methods yourself +* Interpreted execution, doesn't occupy JVM metaspace, can enable caching to improve interpretation performance +* Lightweight code with minimal dependencies, suitable for all Java runtime environments + +QLExpress4, as the latest evolution of QLExpress, rewrote the parsing engine based on Antlr4, further enhancing the original advantages and fully embracing functional programming, with further improvements in both performance and expressive power. + +Usage scenarios: + +* E-commerce coupon rule configuration: Quickly implement coupon rule DSLs through QLExpress custom functions and operators, allowing operations personnel to dynamically configure according to requirements +* Form building control association rule configuration: Form building platforms allow users to drag and drop controls to build custom forms, using QLExpress scripts to configure associations between different controls +* Workflow engine condition rule configuration +* Advertising system billing rule configuration + +\...\... + +== New Version Features + +The new version is not a simple functional refactoring of the old version, but our exploration of the next generation rule expression engine based on insights into user needs. It has many very practical but missing important features in other engines. + +=== Expression Calculation Tracing + +After business personnel complete rule script configuration, it's difficult to perceive their online execution situation. For example, in e-commerce promotion rules requiring users to satisfy the rule `isVip && not logged in for more than 10 days`. How many online users are blocked by the vip condition, and how many users are blocked by the login condition? This is just a simple rule with only two conditions, but the actual online situation is much more complex. + +Tracing online rule execution not only helps business personnel understand the actual online situation and troubleshoot and fix problems, but the accumulated data is also very valuable and can be used for subsequent rule optimization and business decisions. Below is a simplified product diagram of a rule platform performing root cause analysis and annotation decisions on rules based on QLExpress4's expression tracing capability: + +image::images/order_rules_cn.png[] + +The principle of root cause analysis lies in using QLExpress4's expression tracing capability to obtain the value of each intermediate result during expression calculation, and accordingly determine the cause of the final execution result. + +For specific usage methods, refer to: link:#expression-calculation-tracing-1[Expression Calculation Tracing] + +=== Native JSON Syntax Support + +QLExpress4 natively supports JSON syntax and can quickly define complex data structures. + +JSON arrays represent lists (List), while JSON objects represent mappings (Map), and complex objects can also be directly defined. + +Products can implement JSON mapping rules based on this feature, allowing users to conveniently define mapping relationships from one model to another. Below is a simplified product diagram of a rule platform implementing model mapping based on this capability: + +image::images/json_map.png[] + +For specific usage methods, refer to: link:#convenient-syntax-elements[Convenient Syntax Elements] + +=== Convenient String Processing + +QLExpress4 has targeted enhancements to string processing capabilities, allowing expressions to be directly embedded in strings through `$\{expression}`. + +For specific usage methods, refer to: link:#dynamic-strings[Dynamic Strings] + +=== Attachment Pass-through + +Normally, all information needed for script execution is in the `context`. Keys in the context can be referenced as variables in scripts and ultimately passed to custom functions or operators. + +However, for security or convenience reasons, some information is not wanted to be referenced by users through variables, such as tenant names, passwords, etc. + +At this time, this information can be passed to custom functions or operators through attachments. + +For specific usage methods, refer to: link:#adding-custom-functions-and-operators[Adding Custom Functions and Operators], where the `hello` custom function returns different welcome messages according to different tenants in the attachment. + +=== Functional Programming + +Functions are elevated to first-class citizens in QLExpress4, can be used as variables, and can also be returned as function return values. They can also be easily combined with common functional APIs in Java (such as Stream). + +Here's a simple QLExpress example script: + +[source,java] +---- +add = (a, b) -> { + return a + b; +}; +i = add(1,2); +assert(i == 3); +---- + +For more usage methods, refer to: + +* link:#lambda-expressions[Lambda Expressions] +* link:#list-filtering-and-mapping[List Filtering and Mapping] +* link:#stream-api[Stream API] +* link:#functional-interfaces[Functional Interfaces] + +=== Semicolon Simplification + +QLExpress4 supports omitting semicolons, making expressions more concise. For details, refer to link:#semicolons[Semicolons] + +== API Quick Start + +=== Adding Dependencies + +[source,xml] +---- + + com.alibaba + qlexpress4 + 4.0.7 + +---- + +Requirements + +* JDK 8 or higher + + +=== First QLExpress Program + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + Map context = new HashMap<>(); + context.put("a", 1); + context.put("b", 2); + context.put("c", 3); + Object result = express4Runner.execute("a + b * c", context, QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals(7, result); +---- + +More expression execution methods can be found in the documentation link:docs/execute-en.adoc[Expression Execution] + +=== Adding Custom Functions and Operators + +The simplest way is to quickly define function/operator logic through Java Lambda expressions: + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + // custom function + express4Runner.addVarArgsFunction("join", + params -> Arrays.stream(params).map(Object::toString).collect(Collectors.joining(","))); + Object resultFunction = + express4Runner.execute("join(1,2,3)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals("1,2,3", resultFunction); + + // custom operator + express4Runner.addOperatorBiFunction("join", (left, right) -> left + "," + right); + Object resultOperator = + express4Runner.execute("1 join 2 join 3", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals("1,2,3", resultOperator); +---- + +If the logic of custom functions is complex, or you need to obtain script context information, you can also implement it by inheriting `CustomFunction`. + +For example, the following `hello` custom function returns different welcome messages according to different tenants: + +[source,java,indent=0] +---- +package com.alibaba.qlexpress4.test.function; + +import com.alibaba.qlexpress4.runtime.Parameters; +import com.alibaba.qlexpress4.runtime.QContext; +import com.alibaba.qlexpress4.runtime.function.CustomFunction; + +public class HelloFunction implements CustomFunction { + @Override + public Object call(QContext qContext, Parameters parameters) + throws Throwable { + String tenant = (String)qContext.attachment().get("tenant"); + return "hello," + tenant; + } +} +---- + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + express4Runner.addFunction("hello", new HelloFunction()); + String resultJack = (String)express4Runner.execute("hello()", + Collections.emptyMap(), + // Additional information(tenant for example) can be brought into the custom function from outside via attachments + QLOptions.builder().attachments(Collections.singletonMap("tenant", "jack")).build()).getResult(); + assertEquals("hello,jack", resultJack); + String resultLucy = + (String)express4Runner + .execute("hello()", + Collections.emptyMap(), + QLOptions.builder().attachments(Collections.singletonMap("tenant", "lucy")).build()) + .getResult(); + assertEquals("hello,lucy", resultLucy); +---- + +QLExpress4 also supports adding custom functions through QLExpress scripts. Note that variables defined outside of functions (such as defineTime in the example) are initialized when the function is defined and will not be recalculated when the function is subsequently called. + +[source,java,indent=0] +---- + Express4Runner express4Runner = + new Express4Runner(InitOptions.builder().securityStrategy(QLSecurityStrategy.open()).build()); + BatchAddFunctionResult addResult = express4Runner.addFunctionsDefinedInScript( + "function myAdd(a,b) {\n" + " return a+b;" + "}\n" + "\n" + "function getCurrentTime() {\n" + + " return System.currentTimeMillis();\n" + "}" + "\n" + "defineTime=System.currentTimeMillis();\n" + + "function defineTime() {\n" + " return defineTime;" + "}\n", + ExpressContext.EMPTY_CONTEXT, + QLOptions.DEFAULT_OPTIONS); + assertEquals(3, addResult.getSucc().size()); + QLResult result = express4Runner.execute("myAdd(1,2)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS); + assertEquals(3, result.getResult()); + + QLResult resultCurTime1 = + express4Runner.execute("getCurrentTime()", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS); + Thread.sleep(1000); + QLResult resultCurTime2 = + express4Runner.execute("getCurrentTime()", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS); + assertNotSame(resultCurTime1.getResult(), resultCurTime2.getResult()); + + /* + * The defineTime variable is defined outside the function and is initialized when the function is defined; + * it is not recalculated afterward, so the value returned is always the time at which the function was defined. + */ + QLResult resultDefineTime1 = + express4Runner.execute("defineTime()", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS); + Thread.sleep(1000); + QLResult resultDefineTime2 = + express4Runner.execute("defineTime()", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS); + assertSame(resultDefineTime1.getResult(), resultDefineTime2.getResult()); +---- + +It is recommended to use Java to define custom functions as much as possible, as this can provide better performance and stability. + +For more ways to customize syntax elements, see the documentation link:docs/custom-item-en.adoc[Custom Syntax Elements]. + +=== Validating Syntax Correctness + +To validate syntax correctness without executing scripts, including operator restriction validation: call `check` and catch exceptions. If `QLSyntaxException` is caught, it indicates syntax errors exist. + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + try { + express4Runner.check("a+b;\n(a+b"); + fail(); + } + catch (QLSyntaxException e) { + assertEquals(2, e.getLineNo()); + assertEquals(4, e.getColNo()); + assertEquals("SYNTAX_ERROR", e.getErrorCode()); + // represents the end of script + assertEquals( + "[Error SYNTAX_ERROR: mismatched input '' expecting ')']\n" + "[Near: a+b; (a+b]\n" + + " ^^^^^\n" + "[Line: 2, Column: 4]", + e.getMessage()); + } +---- + +You can use `CheckOptions` to configure more fine-grained syntax validation rules, supporting the following two options: + +1. `operatorCheckStrategy`: Operator validation strategy, used to restrict which operators can be used in the script +2. `disableFunctionCalls`: Whether to disable function calls, default is false + +Example 1: Using Operator Validation Strategy (Whitelist) + +[source,java,indent=0] +---- + // Create a whitelist of allowed operators + Set allowedOps = new HashSet<>(Arrays.asList("+", "*")); + + // Configure check options with operator whitelist + CheckOptions checkOptions = + CheckOptions.builder().operatorCheckStrategy(OperatorCheckStrategy.whitelist(allowedOps)).build(); + + // Create runner and check script with custom options + Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + runner.check("a + b * c", checkOptions); // This will pass as + and * are allowed +---- + +Example 2: Disabling Function Calls + +[source,java,indent=0] +---- + // Create a runner + Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + + // Create options with function calls disabled + CheckOptions options = CheckOptions.builder().disableFunctionCalls(true).build(); + + // Script with function call + String scriptWithFunctionCall = "Math.max(1, 2)"; + + // Use custom options to check script + try { + runner.check(scriptWithFunctionCall, options); + } + catch (QLSyntaxException e) { + // Will throw exception as function calls are disabled + } +---- + +=== Parsing External Variables Required by Scripts + +Some variables used in scripts are generated within the script, while others need to be passed in from outside through `context`. + +QLExpress4 provides a method to parse all variables that need to be passed in from outside in the script: + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + Set outVarNames = + express4Runner.getOutVarNames("int a = 1, b = 10;\n" + "c = 11\n" + "e = a + b + c + d\n" + "f+e"); + Set expectSet = new HashSet<>(); + expectSet.add("d"); + expectSet.add("f"); + assertEquals(expectSet, outVarNames); +---- + +More script-dependency parsing tools: + +* `getOutFunctions` – parses all functions that must be defined externally +* `getOutVarAttrs` – parses all variables (and the attributes they reference) that must be supplied from outside; an enhanced version of `getOutVarNames` + +=== High-Precision Calculation + +QLExpress internally uses BigDecimal to represent all numbers that cannot be precisely represented by double, to represent calculation precision as much as possible: + +> Example: 0.1 cannot be precisely represented in double + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + Object result = express4Runner.execute("0.1", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertTrue(result instanceof BigDecimal); +---- + +This approach can solve some calculation precision problems: + +For example, 0.1+0.2 is not equal to 0.3 in Java due to precision issues. +QLExpress can automatically identify that 0.1 and 0.2 cannot be precisely represented by double precision, and change to use BigDecimal representation to ensure the result equals 0.3 + +[source,java,indent=0] +---- + assertNotEquals(0.3, 0.1 + 0.2, 0.0); + assertTrue((Boolean)express4Runner.execute("0.3==0.1+0.2", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS) + .getResult()); +---- + +In addition to the default precision guarantee, there's also a `precise` switch. When turned on, all calculations use BigDecimal to prevent problems caused by low-precision numbers passed in from outside: + +[source,java,indent=0] +---- + Map context = new HashMap<>(); + context.put("a", 0.1); + context.put("b", 0.2); + assertFalse((Boolean)express4Runner.execute("0.3==a+b", context, QLOptions.DEFAULT_OPTIONS).getResult()); + // open precise switch + assertTrue((Boolean)express4Runner.execute("0.3==a+b", context, QLOptions.builder().precise(true).build()) + .getResult()); +---- + +=== Security Strategy + +QLExpress4 adopts isolation security strategy by default, not allowing scripts to access Java object fields and methods, which ensures script execution security. If you need to access Java objects, you can configure through different security strategies. + +Assuming the application has the following Java class: + +[source,java,indent=0] +---- +package com.alibaba.qlexpress4.inport; + +/** + * Author: DQinYuan + */ +public class MyDesk { + + private String book1; + + private String book2; + + public String getBook1() { + return book1; + } + + public void setBook1(String book1) { + this.book1 = book1; + } + + public String getBook2() { + return book2; + } + + public void setBook2(String book2) { + this.book2 = book2; + } +} +---- + +The script execution context is set as follows: + +[source,java,indent=0] +---- + MyDesk desk = new MyDesk(); + desk.setBook1("Thinking in Java"); + desk.setBook2("Effective Java"); + Map context = Collections.singletonMap("desk", desk); +---- + +QLExpress4 provides four security strategies: + +==== 1. Isolation Strategy (Default) + +By default, QLExpress4 adopts isolation strategy, not allowing access to any fields and methods: + +[source,java,indent=0] +---- + // default isolation strategy, no field or method can be found + Express4Runner express4RunnerIsolation = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + assertErrorCode(express4RunnerIsolation, context, "desk.book1", "FIELD_NOT_FOUND"); + assertErrorCode(express4RunnerIsolation, context, "desk.getBook2()", "METHOD_NOT_FOUND"); +---- + +==== 2. Blacklist Strategy + +Through blacklist strategy, you can prohibit access to specific fields or methods, while other fields and methods can be accessed normally: + +[source,java,indent=0] +---- + // black list security strategy + Set memberList = new HashSet<>(); + memberList.add(MyDesk.class.getMethod("getBook2")); + Express4Runner express4RunnerBlackList = new Express4Runner( + InitOptions.builder().securityStrategy(QLSecurityStrategy.blackList(memberList)).build()); + assertErrorCode(express4RunnerBlackList, context, "desk.book2", "FIELD_NOT_FOUND"); + Object resultBlack = + express4RunnerBlackList.execute("desk.book1", context, QLOptions.DEFAULT_OPTIONS).getResult(); + Assert.assertEquals("Thinking in Java", resultBlack); +---- + +==== 3. Whitelist Strategy + +Through whitelist strategy, only specified fields or methods are allowed to be accessed, while other fields and methods are prohibited: + +[source,java,indent=0] +---- + // white list security strategy + Express4Runner express4RunnerWhiteList = new Express4Runner( + InitOptions.builder().securityStrategy(QLSecurityStrategy.whiteList(memberList)).build()); + Object resultWhite = + express4RunnerWhiteList.execute("desk.getBook2()", context, QLOptions.DEFAULT_OPTIONS).getResult(); + Assert.assertEquals("Effective Java", resultWhite); + assertErrorCode(express4RunnerWhiteList, context, "desk.getBook1()", "METHOD_NOT_FOUND"); +---- + +==== 4. Open Strategy + +Open strategy allows access to all fields and methods, similar to QLExpress3 behavior, but security risks need to be noted: + +[source,java,indent=0] +---- + // open security strategy + Express4Runner express4RunnerOpen = + new Express4Runner(InitOptions.builder().securityStrategy(QLSecurityStrategy.open()).build()); + Assert.assertEquals("Thinking in Java", + express4RunnerOpen.execute("desk.book1", context, QLOptions.DEFAULT_OPTIONS).getResult()); + Assert.assertEquals("Effective Java", + express4RunnerOpen.execute("desk.getBook2()", context, QLOptions.DEFAULT_OPTIONS).getResult()); +---- + +> Note: While open strategy provides maximum flexibility, it also brings security risks. It's recommended to use only in trusted environments and not recommended for processing end-user input scripts. + +==== Strategy Recommendations + +It's recommended to directly adopt the default strategy and not directly call Java object fields and methods in scripts. Instead, provide system functionality to embedded scripts through custom functions and operators (refer to link:#adding-custom-functions-and-operators[Adding Custom Functions and Operators]). This can ensure both script security and flexibility, with better user experience. + +If you do need to call Java object fields and methods, at least use whitelist strategy to provide limited access permissions to scripts. + +As for blacklist and open strategies, they're not recommended for external input script scenarios unless you ensure each script will be reviewed. + +=== Calling Java Classes in Applications + +> Requires relaxing security strategy, not recommended for end-user input + +Assuming the application has the following Java class (`com.alibaba.qlexpress4.QLImportTester`): + +[source,java,indent=0] +---- +package com.alibaba.qlexpress4; + +public class QLImportTester { + + public static int add(int a, int b) { + return a + b; + } + +} +---- + +In QLExpress, there are two calling methods. + +==== 1. Using `import` Statement in Scripts to Import Classes and Use + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.builder() + // open security strategy, which allows access to all Java classes within the application. + .securityStrategy(QLSecurityStrategy.open()) + .build()); + // Import Java classes using the import statement. + Map params = new HashMap<>(); + params.put("a", 1); + params.put("b", 2); + Object result = + express4Runner + .execute("import com.alibaba.qlexpress4.QLImportTester;" + "QLImportTester.add(a,b)", + params, + QLOptions.DEFAULT_OPTIONS) + .getResult(); + Assert.assertEquals(3, result); +---- + +==== 2. Default Import When Creating `Express4Runner`, No Additional `import` Statement Needed in Scripts + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.builder() + .addDefaultImport( + Collections.singletonList(ImportManager.importCls("com.alibaba.qlexpress4.QLImportTester"))) + .securityStrategy(QLSecurityStrategy.open()) + .build()); + Object result = + express4Runner.execute("QLImportTester.add(1,2)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS) + .getResult(); + Assert.assertEquals(3, result); +---- + +In addition to using `ImportManager.importCls` to import individual classes, there are other more convenient import methods: + + * `ImportManager.importPack` directly imports all classes under a package path, such as `ImportManager.importPack("java.util")` will import all classes under the `java.util` package. QLExpress defaults to importing the following packages: + ** `ImportManager.importPack("java.lang")` + ** `ImportManager.importPack("java.util")` + ** `ImportManager.importPack("java.math")` + ** `ImportManager.importPack("java.util.stream")` + ** `ImportManager.importPack("java.util.function")` + * `ImportManager.importInnerCls` imports all inner classes in a given class path + +=== Custom ClassLoader + +QLExpress4 supports specifying class loaders through custom `ClassSupplier`, which is very useful in scenarios like plugin architecture and modular applications. Through custom class loaders, QLExpress scripts can access classes in specific ClassLoaders. + +The following example shows how to integrate with the link:https://pf4j.org/[PF4J] plugin framework to allow QLExpress scripts to access classes in plugins: + +[source,java,indent=0] +---- + // Specify plugin directory (test-plugins directory under test resources) + Path pluginsDir = new File("src/test/resources/test-plugins").toPath(); + PluginManager pluginManager = new DefaultPluginManager(pluginsDir); + pluginManager.loadPlugins(); + pluginManager.startPlugins(); + + // Get the PluginClassLoader of the first plugin + PluginWrapper plugin = pluginManager.getPlugins().get(0); + ClassLoader pluginClassLoader = plugin.getPluginClassLoader(); + + // Custom class supplier using plugin ClassLoader + ClassSupplier pluginClassSupplier = clsName -> { + try { + return Class.forName(clsName, true, pluginClassLoader); + } + catch (ClassNotFoundException | NoClassDefFoundError e) { + return null; + } + }; + + InitOptions options = InitOptions.builder() + .securityStrategy(QLSecurityStrategy.open()) + .classSupplier(pluginClassSupplier) + .build(); + Express4Runner runner = new Express4Runner(options); + + String script = "import com.alibaba.qlexpress4.pf4j.TestPluginInterface; TestPluginInterface.TEST_CONSTANT"; + Object result = runner.execute(script, Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); + + Assert.assertEquals("Hello from PF4J Plugin!", result.toString()); +---- + +Typical application scenarios for custom ClassSupplier: + +* **Plugin Architecture**: Allow scripts to access classes and interfaces defined in plugins +* **Modular Applications**: In modular frameworks like OSGi, allow scripts to access classes in specific modules +* **Dynamic Class Loading**: Load classes from remote repositories or dynamically generated bytecode +* **Class Isolation**: Use different ClassLoaders to achieve class isolation + +=== Expression Caching + +Through the `cache` option, you can enable expression caching, so the same expressions won't be recompiled, greatly improving performance. + +Note that this cache has no size limit and is only suitable for use when expressions are in limited quantities: + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + // open cache switch + express4Runner.execute("1+2", new HashMap<>(), QLOptions.builder().cache(true).build()); +---- + +However, when scripts are executed for the first time, they're still relatively slow because there's no cache. + +You can cache scripts before first execution using the following method to ensure first execution speed: + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + express4Runner.parseToDefinitionWithCache("a+b"); +---- + +Note that this cache has an unlimited size; be sure to control its size in your application. You can periodically clear the compilation cache by calling the `clearCompileCache` method. + +=== Clearing DFA Cache + +QLExpress uses ANTLR4 as its parsing engine. ANTLR4 builds a DFA (Deterministic Finite Automaton) cache at runtime to accelerate subsequent syntax parsing. This cache consumes a certain amount of memory. + +In some memory-sensitive scenarios, you can call the `clearDFACache` method to clear the DFA cache and release memory. + +> **IMPORTANT WARNING**: Clearing the DFA cache 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 option to avoid recompilation. Example code: + +[source,java,indent=0] +---- + /* + * 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(exampleExpress); + 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(exampleExpress, ExpressContext.EMPTY_CONTEXT, QLOptions.builder().cache(true).build()); + } +---- + +By following this approach, you can minimize memory usage while maintaining performance. + +=== Setting Timeout + +You can set a timeout for scripts to prevent infinite loops or other reasons from causing excessive consumption of application resources. + +The following example code sets a 10ms timeout for the script: + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + try { + express4Runner.execute("while (true) {\n" + " 1+1\n" + "}", + Collections.emptyMap(), + QLOptions.builder().timeoutMillis(10L).build()); + fail("should timeout"); + } + catch (QLTimeoutException e) { + assertEquals(QLErrorCodes.SCRIPT_TIME_OUT.name(), e.getErrorCode()); + } +---- + +> Note: For system performance considerations, QLExpress's detection of timeout is not accurate. Especially for timeouts occurring in Java code callbacks (such as custom functions or operators), they won't be detected immediately. Only after execution is complete and back to QLExpress runtime will they be detected and execution interrupted. + +=== Extension Functions + +Using QLExpress's extension function capability, you can add additional member methods to Java classes. + +Extension functions are implemented based on QLExpress runtime, so they're only effective in QLExpress scripts. + +The following example code adds a `hello()` extension function to the String class: + +[source,java,indent=0] +---- + ExtensionFunction helloFunction = new ExtensionFunction() { + @Override + public Class[] getParameterTypes() { + return new Class[0]; + } + + @Override + public String getName() { + return "hello"; + } + + @Override + public Class getDeclaringClass() { + return String.class; + } + + @Override + public Object invoke(Object obj, Object[] args) { + String originStr = (String)obj; + return "Hello," + originStr; + } + }; + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + express4Runner.addExtendFunction(helloFunction); + Object result = + express4Runner.execute("'jack'.hello()", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals("Hello,jack", result); + + // simpler way to define extension function + express4Runner.addExtendFunction("add", + Number.class, + params -> ((Number)params[0]).intValue() + ((Number)params[1]).intValue()); + QLResult resultAdd = express4Runner.execute("1.add(2)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS); + assertEquals(3, resultAdd.getResult()); + +---- + +=== Java Class Object, Field, and Method Aliases + +QLExpress supports defining one or more aliases for objects, fields, or methods through the `QLAlias` annotation, making it convenient for non-technical personnel to use expressions to define rules. + +In the following example, the final order amount is calculated based on whether the user is a vip. + +User class definition: + +[source,java,indent=0] +---- +package com.alibaba.qlexpress4.test.qlalias; + +import com.alibaba.qlexpress4.annotation.QLAlias; + +@QLAlias("用户") +public class User { + + @QLAlias("是vip") + private boolean vip; + + @QLAlias("用户名") + private String name; + + public boolean isVip() { + return vip; + } + + public void setVip(boolean vip) { + this.vip = vip; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} +---- + +Order class definition: + +[source,java,indent=0] +---- +package com.alibaba.qlexpress4.test.qlalias; + +import com.alibaba.qlexpress4.annotation.QLAlias; + +@QLAlias("订单") +public class Order { + + @QLAlias("订单号") + private String orderNum; + + @QLAlias("金额") + private int amount; + + public String getOrderNum() { + return orderNum; + } + + public void setOrderNum(String orderNum) { + this.orderNum = orderNum; + } + + public int getAmount() { + return amount; + } + + public void setAmount(int amount) { + this.amount = amount; + } +} +---- + +Calculate final order amount through QLExpress script rules: + +[source,java,indent=0] +---- + Order order = new Order(); + order.setOrderNum("OR123455"); + order.setAmount(100); + + User user = new User(); + user.setName("jack"); + user.setVip(true); + + // Calculate the Final Order Amount + Express4Runner express4Runner = + new Express4Runner(InitOptions.builder().securityStrategy(QLSecurityStrategy.open()).build()); + Number result = (Number)express4Runner + .executeWithAliasObjects("用户.是vip? 订单.金额 * 0.8 : 订单.金额", QLOptions.DEFAULT_OPTIONS, order, user) + .getResult(); + assertEquals(80, result.intValue()); +---- + +=== Keyword, Operator, and Function Aliases + +To further facilitate non-technical personnel in writing rules, QLExpress provides `addAlias` to add aliases to original keywords, operators, and functions, making the entire script expression more natural language-like. + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + // add custom function zero + express4Runner.addFunction("zero", (String ignore) -> 0); + + // keyword alias + assertTrue(express4Runner.addAlias("如果", "if")); + assertTrue(express4Runner.addAlias("则", "then")); + assertTrue(express4Runner.addAlias("否则", "else")); + assertTrue(express4Runner.addAlias("返回", "return")); + // operator alias + assertTrue(express4Runner.addAlias("大于", ">")); + // function alias + assertTrue(express4Runner.addAlias("零", "zero")); + + Map context = new HashMap<>(); + context.put("语文", 90); + context.put("数学", 90); + context.put("英语", 90); + + Object result = express4Runner + .execute("如果 (语文 + 数学 + 英语 大于 270) 则 {返回 1;} 否则 {返回 零();}", context, QLOptions.DEFAULT_OPTIONS) + .getResult(); + assertEquals(0, result); +---- + +Keywords that support setting aliases include: + + * if + * then + * else + * for + * while + * break + * continue + * return + * function + * macro + * new + * null + * true + * false + +> Note: Some familiar usages are actually operators, not keywords, such as the `in` operator. All operators and functions support aliases by default + +=== Macros + +Macros are a powerful code reuse mechanism in QLExpress that allows users to define reusable script fragments and call them when needed. Unlike simple text replacement, QLExpress macros are implemented based on instruction replay mechanism, providing better performance and semantic accuracy. + +Macros are particularly suitable for the following scenarios: + +* **Code Reuse**: Encapsulate commonly used script fragments into macros to avoid repeatedly writing the same logic +* **Business Rule Templates**: Define standard business rule templates such as price calculation, permission checking, etc. +* **Flow Control**: Encapsulate complex control flows such as conditional judgment, loop logic, etc. +* **DSL Construction**: As basic components for building domain-specific languages + +Macros can be defined in two ways: + +**1. Using `macro` keyword in scripts** + +[source,java] +---- +macro add { + c = a + b; +} + +a = 1; +b = 2; +add; +assert(c == 3); +---- + +**2. Adding through Java API** + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + express4Runner.addMacro("rename", "name='haha-'+name"); + Map context = Collections.singletonMap("name", "wuli"); + Object result = express4Runner.execute("rename", context, QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals("haha-wuli", result); + + // replace macro + express4Runner.addOrReplaceMacro("rename", "name='huhu-'+name"); + Object result1 = express4Runner.execute("rename", context, QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals("huhu-wuli", result1); +---- + +Differences between macros and functions: + +[cols="1,1,1"] +|=== +| Feature | Macro | Function +| Parameter Passing | No parameters, relies on context variables | Supports parameter passing +| Performance | Direct instruction insertion, no call overhead | Has function call overhead +| Scope | Shares caller's scope | Independent scope +| Applicable Scenarios | Code fragment reuse | Logic encapsulation and parameterization +|=== + +Macros are particularly suitable for code fragment reuse scenarios that don't require parameter passing and mainly rely on context variables, while functions are more suitable for scenarios requiring parameterization and independent scope. + +**Changes in macro features in QLExpress4 compared to version 3**: + + * Version 4's macro implementation is closer to the definition of macros in common programming languages, equivalent to inserting predefined code fragments at the macro's location, sharing the same scope as the call point. `return`, `continue`, and `break` in macros can affect the control flow of the caller. However, version 3's implementation is actually closer to parameterless function calls. + * Version 4's macros cannot be used as variables, only when standing alone as a line statement can they be macro-replaced. Because macros can be arbitrary scripts, not necessarily expressions with return values, using them as variables would have semantic issues. Version 3's macros are essentially parameterless function calls, so they're often used as variables. + +If you want to be compatible with version 3's macro features, it's recommended to use link:#dynamic-variables[Dynamic Variables] + +=== Dynamic Variables + +Regular "static variables" are fixed values associated with keys in the context. Dynamic variables can be expressions calculated from other variables. Dynamic variables support nesting, meaning dynamic variables can depend on another dynamic variable for calculation. + +Example: + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + + Map staticContext = new HashMap<>(); + staticContext.put("语文", 88); + staticContext.put("数学", 99); + staticContext.put("英语", 95); + + QLOptions defaultOptions = QLOptions.DEFAULT_OPTIONS; + DynamicVariableContext dynamicContext = + new DynamicVariableContext(express4Runner, staticContext, defaultOptions); + dynamicContext.put("平均成绩", "(语文+数学+英语)/3.0"); + dynamicContext.put("是否优秀", "平均成绩>90"); + + // dynamic var + assertTrue((Boolean)express4Runner.execute("是否优秀", dynamicContext, defaultOptions).getResult()); + assertEquals(94, + ((Number)express4Runner.execute("平均成绩", dynamicContext, defaultOptions).getResult()).intValue()); + // static var + assertEquals(187, + ((Number)express4Runner.execute("语文+数学", dynamicContext, defaultOptions).getResult()).intValue()); +---- + +=== Expression Calculation Tracing + +If relevant options are enabled, QLExpress4 will return an expression trace tree along with the rule script calculation result. The structure of the expression trace tree is similar to a syntax tree, with the difference that it records intermediate results of this execution at each node. + +For example, for the expression `!true || myTest(a, 1)`, the structure of the expression trace tree is roughly as follows: + +[source] +---- + || true + / \ + ! false myTest + / / \ + true a 10 1 +---- + +It can be applied to various scenarios: + + * Help business personnel analyze and troubleshoot rule calculation results + * Sample and categorize rules judged as false online + * AI automatic diagnosis and repair of rules + +Node calculation results are placed in the `value` field of the `ExpressionTrace` object. If short-circuit occurs in the middle causing some expressions to not be calculated, the `evaluated` field of the `ExpressionTrace` object will be set to false. Code example: + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.builder().traceExpression(true).build()); + express4Runner.addFunction("myTest", (Predicate)i -> i > 10); + + Map context = new HashMap<>(); + context.put("a", true); + QLResult result = express4Runner + .execute("a && (!myTest(11) || false)", context, QLOptions.builder().traceExpression(true).build()); + Assert.assertFalse((Boolean)result.getResult()); + + List expressionTraces = result.getExpressionTraces(); + Assert.assertEquals(1, expressionTraces.size()); + ExpressionTrace expressionTrace = expressionTraces.get(0); + Assert.assertEquals("OPERATOR && false\n" + " | VARIABLE a true\n" + " | OPERATOR || false\n" + + " | OPERATOR ! false\n" + " | FUNCTION myTest true\n" + " | VALUE 11 11\n" + + " | VALUE false false\n", expressionTrace.toPrettyString(0)); + + // short circuit + context.put("a", false); + QLResult resultShortCircuit = express4Runner.execute("(a && true) && (!myTest(11) || false)", + context, + QLOptions.builder().traceExpression(true).build()); + Assert.assertFalse((Boolean)resultShortCircuit.getResult()); + ExpressionTrace expressionTraceShortCircuit = resultShortCircuit.getExpressionTraces().get(0); + Assert.assertEquals( + "OPERATOR && false\n" + " | OPERATOR && false\n" + " | VARIABLE a false\n" + " | VALUE true \n" + + " | OPERATOR || \n" + " | OPERATOR ! \n" + " | FUNCTION myTest \n" + + " | VALUE 11 \n" + " | VALUE false \n", + expressionTraceShortCircuit.toPrettyString(0)); + Assert.assertTrue(expressionTraceShortCircuit.getChildren().get(0).isEvaluated()); + Assert.assertFalse(expressionTraceShortCircuit.getChildren().get(1).isEvaluated()); + + // in + QLResult resultIn = express4Runner + .execute("'ab' in ['cc', 'dd', 'ff']", context, QLOptions.builder().traceExpression(true).build()); + Assert.assertFalse((Boolean)resultIn.getResult()); + ExpressionTrace expressionTraceIn = resultIn.getExpressionTraces().get(0); + Assert + .assertEquals( + "OPERATOR in false\n" + " | VALUE 'ab' ab\n" + " | LIST [ [cc, dd, ff]\n" + " | VALUE 'cc' cc\n" + + " | VALUE 'dd' dd\n" + " | VALUE 'ff' ff\n", + expressionTraceIn.toPrettyString(0)); +---- + +> Note: You must set the `InitOptions.traceExpression` option to true when creating `Express4Runner`, and set `QLOptions.traceExpression` to true when executing scripts for this feature to take effect. + +You can also get all expression trace points without executing scripts: + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + TracePointTree tracePointTree = express4Runner.getExpressionTracePoints("1+3+5*ab+9").get(0); + Assert.assertEquals("OPERATOR +\n" + " | OPERATOR +\n" + " | OPERATOR +\n" + " | VALUE 1\n" + + " | VALUE 3\n" + " | OPERATOR *\n" + " | VALUE 5\n" + " | VARIABLE ab\n" + + " | VALUE 9\n", tracePointTree.toPrettyString(0)); +---- + +Supported expression trace point types and corresponding child node meanings are as follows: + +[cols="1,1,1"] +|=== +| Node Type | Node Meaning | Child Node Meaning +| OPERATOR | Operator | Both operands +| FUNCTION | Function | Function parameters +| METHOD | Method | Method parameters +| FIELD | Field | Target object for field access +| LIST | List | List elements +| MAP | Field | None +| IF | Conditional branch | Condition expression, then logic block and else logic block +| RETURN | Return statement | Return expression +| VARIABLE | Variable | None +| VALUE | Literal value | None +| DEFINE_FUNCTION | Define function | None +| DEFINE_MACRO | Define macro | None +| PRIMARY | Other composite values not yet drilled down (such as dictionaries, if, etc.) | None +| STATEMENT | Other composite statements not yet drilled down (such as while, for, etc.) | None +|=== + +=== Spring Integration + +QLExpress doesn't need special integration with Spring, just a `Express4Runner` singleton is sufficient. + +The "integration" example provided here allows direct reference to any Spring Bean in QLExpress scripts. + +While this approach is very convenient, it gives scripts too much permission and freedom. It's no longer recommended. It's still recommended to only put objects that users are allowed to access in the context. + +Core integration components: + +* link:src/test/java/com/alibaba/qlexpress4/spring/QLSpringContext.java[QLSpringContext]: Implements the `ExpressContext` interface, providing access capability to the Spring container. It first looks for variables from the passed context, and if not found, tries to get beans with the same name from the Spring container. +* link:src/test/java/com/alibaba/qlexpress4/spring/QLExecuteService.java[QLExecuteService]: Encapsulates QLExpress execution logic, integrates with Spring container, convenient for use in Spring applications. + +Assuming there's a Spring Bean named `helloService`: + +[source,java,indent=0] +---- +package com.alibaba.qlexpress4.spring; + +import org.springframework.stereotype.Service; + +/** + * Spring Bean example service class + */ +@Service +public class HelloService { + + /** + * Hello method that returns a greeting string + * @return greeting string + */ + public String hello(String name) { + return "Hello, " + name + "!"; + } +} +---- + +Call this Bean in scripts: + +[source,java,indent=0] +---- +package com.alibaba.qlexpress4.spring; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import java.util.HashMap; +import java.util.Map; + +/** + * HelloService unit test class + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = SpringTestConfig.class) +public class SpringDemoTest { + + @Autowired + private QLExecuteService qlExecuteService; + + @Test + public void qlExecuteWithSpringContextTest() { + Map context = new HashMap<>(); + context.put("name", "Wang"); + String result = (String)qlExecuteService.execute("helloService.hello(name)", context); + Assert.assertEquals("Hello, Wang!", result); + } +} +---- + + +== Syntax Introduction + +QLExpress4 is compatible with Java8 syntax while also providing many more flexible and loose syntax patterns to help users write expressions more quickly. + +Based on expression-first syntax design, complex conditional judgment statements can also be directly used as expressions. + +Code snippets appearing in this chapter are all qlexpress scripts. +`assert` is an assertion method injected by the test framework into the engine, ensuring its parameter is `true`. +`assertErrCode` ensures that the lambda parameter expression execution will definitely throw a QLException containing the second parameter error code. + +=== Variable Declaration + +Supports both static typing and dynamic typing: + + * When declaring variables without writing types, the variable is dynamic type and also an assignment expression + * When declaring variables with types, it's static type, and at this time it's a variable declaration statement + +[source,java] +---- +// Dynamic Typeing +a = 1; +a = "1"; +// Static Typing +int b = 2; +// throw QLException with error code INCOMPATIBLE_ASSIGNMENT_TYPE when assign with incompatible type String +assertErrorCode(() -> b = "1", "INCOMPATIBLE_ASSIGNMENT_TYPE") + +---- + +=== Convenient Syntax Elements + +Common syntax elements like lists (List), mappings (Map), etc. all have very convenient construction syntax sugar in QLExpress: + +[source,java] +---- +// list +l = [1,2,3] +assert(l[0]==1) +assert(l[-1]==3) +// Underlying data type of list is ArrayList in Java +assert(l instanceof ArrayList) +// map +m = { + "aa": 10, + "bb": { + "cc": "cc1", + "dd": "dd1" + } +} +assert(m['aa']==10) +// Underlying data type of map is ArrayList in Java +assert(m instanceof LinkedHashMap) +// empty map +emMap = {:} +emMap['haha']='huhu' +assert(emMap['haha']=='huhu') +---- + +Through the `*.` operator, you can quickly process lists and mappings, such as taking properties of list elements, or getting key lists and value lists of mappings: + +[source,java] +---- +list = [ + { + "name": "Li", + "age": 10 + }, + { + "name": "Wang", + "age": 15 + } +] + +// get field from list +assert(list*.age==[10,15]) + +mm = { + "aaa": 1, + "bbb": 2 +} + +// get map key value list +assert(mm*.key==["aaa", "bbb"]) +assert(mm*.value==[1, 2]) +---- + +In mappings, by specifying the fully qualified name of the type through the `@class` key, you can directly use JSON to create complex Java objects. For example, the following MyHome is a Java class with complex nested types: + +[source,java] +---- +package com.alibaba.qlexpress4.inport; + +/** + * Author: DQinYuan + */ +public class MyHome { + + private String sofa; + + private String chair; + + private MyDesk myDesk; + + private String bed; + + public String getSofa() { + return sofa; + } + + public void setSofa(String sofa) { + this.sofa = sofa; + } + + public String getChair() { + return chair; + } + + public MyDesk getMyDesk() { + return myDesk; + } + + public void setMyDesk(MyDesk myDesk) { + this.myDesk = myDesk; + } + + public void setChair(String chair) { + this.chair = chair; + } + + public String getBed() { + return bed; + } +} +---- + +You can conveniently create it through the following QLExpress script: + +> Note: This feature requires opening security options as referenced in link:#security-strategy[Security Strategy] to execute normally. + +[source,java] +---- +myHome = { + '@class': 'com.alibaba.qlexpress4.inport.MyHome', + 'sofa': 'a-sofa', + 'chair': 'b-chair', + 'myDesk': { + 'book1': 'Then Moon and Sixpence', + '@class': 'com.alibaba.qlexpress4.inport.MyDesk' + }, + // ignore field that don't exist + 'notexist': 1234 +} +assert(myHome.getSofa()=='a-sofa') +assert(myHome instanceof com.alibaba.qlexpress4.inport.MyHome) +assert(myHome.getMyDesk().getBook1()=='Then Moon and Sixpence') +assert(myHome.getMyDesk() instanceof com.alibaba.qlexpress4.inport.MyDesk) +---- + +=== Numbers + +For numbers without declared types, +QLExpress will automatically select the most appropriate one from data types like int, long, BigInteger, double, BigDecimal based on their range: + +[source,java] +---- +assert(2147483647 instanceof Integer); +assert(9223372036854775807 instanceof Long); +assert(18446744073709552000 instanceof BigInteger); +// 0.25 can be precisely presented with double +assert(0.25 instanceof Double); +assert(2.7976931348623157E308 instanceof BigDecimal); +---- + +Therefore, when writing custom functions or operators, it's recommended to use Number type for receiving, because numeric types cannot be determined in advance. + +=== Dynamic Strings + +Dynamic strings are a new capability introduced in QLExpress version 4 to enhance string processing capabilities. + +Supports inserting expression calculations in strings through the `$\{expression}` format: + +> If you want to keep `$\{expression}` as-is in strings, you can escape `$` using `\$` + +[source,java] +---- +a = 123 +assert("hello,${a-1}" == "hello,122") + +// escape $ with \$ +assert("hello,\${a-1}" == "hello,\${a-1}") + +b = "test" +assert("m xx ${ + if (b like 't%') { + 'YYY' + } +}" == "m xx YYY") +---- + +If you want QLExpress4 strings to maintain compatibility with version 3 and not process interpolation expressions, you can directly turn off this feature when creating `Express4Runner`: + +[source,java,indent=0] +---- + Express4Runner express4RunnerDisable = new Express4Runner( + // disable string interpolation + InitOptions.builder().interpolationMode(InterpolationMode.DISABLE).build()); + Assert.assertEquals("Hello,${ a + 1 }", + express4RunnerDisable.execute("\"Hello,${ a + 1 }\"", context, QLOptions.DEFAULT_OPTIONS).getResult()); + Assert.assertEquals("Hello,${lll", + express4RunnerDisable.execute("\"Hello,${lll\"", context, QLOptions.DEFAULT_OPTIONS).getResult()); + Assert.assertEquals("Hello,aaa $ lll\"\n\b", + express4RunnerDisable.execute("\"Hello,aaa $ lll\\\"\n\b\"", context, QLOptions.DEFAULT_OPTIONS) + .getResult()); +---- + +=== Template Rendering + +Leveraging dynamic strings, QLExpress4 can also be used as a lightweight template engine. + +You don't need to manually add string quotes in the script. Just call `executeTemplate` to render the template string: + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + Map context = new HashMap<>(); + context.put("a", 1); + context.put("b", 2); + context.put("c", "test"); + QLResult simpleTemplate = express4Runner.executeTemplate("a ${a};b ${b+2}", context, QLOptions.DEFAULT_OPTIONS); + Assert.assertEquals("a 1;b 4", simpleTemplate.getResult()); + QLResult conditionTemplate = + express4Runner.executeTemplate("m xx ${\n" + " if (c like 't%') {\n" + " 'YYY'\n" + " }\n" + "}", + context, + QLOptions.DEFAULT_OPTIONS); + Assert.assertEquals("m xx YYY", conditionTemplate.getResult()); + QLResult multiLineTemplate = express4Runner.executeTemplate("m\n ${a}\n c", context, QLOptions.DEFAULT_OPTIONS); + Assert.assertEquals("m\n 1\n c", multiLineTemplate.getResult()); + QLResult escapeStringTemplate = + express4Runner.executeTemplate("m \n\"haha\" d\"", context, QLOptions.DEFAULT_OPTIONS); + Assert.assertEquals("m \n\"haha\" d\"", escapeStringTemplate.getResult()); +---- + +=== Placeholders + +Placeholders are used to extract values of arbitrary keys from the context. + +Global variables can also extract values from the context, but are limited by QLExpress keywords and syntax, so the keys that can be extracted are limited. +For example, the value corresponding to key "0" in the context cannot be extracted through variables, because 0 is not a legal variable in QLExpress, but a numeric constant. +At this time, you can use the default placeholder `$\{0}` to extract it. + +> Note the distinction from interpolation in dynamic strings. Placeholders are written outside strings. Dynamic string interpolation is `$\{expression}`, where expressions are written by default, and the running result of `"${0}"` is `"0"`. Placeholders are `$\{placeholder}`, where keys in the context are written by default, and the running result of `${0}` is the value corresponding to key "0" in the context. + +QLExpress uses `${placeholder}` format placeholders by default, where: + +* `${` is the start marker +* `}` is the end marker +* `placeholder` is the placeholder content, corresponding to the key in the context + +In addition to default placeholders, QLExpress also supports customizing the start and end markers of placeholders: + +[source,java,indent=0] +---- + Express4Runner express4Runner = + new Express4Runner(InitOptions.builder().selectorStart("#[").selectorEnd("]").build()); + + Map context = new HashMap<>(); + context.put("0", "World"); + + QLResult result = express4Runner.execute("'Hello ' + #[0]", context, QLOptions.DEFAULT_OPTIONS); + assertEquals("Hello World", result.getResult()); +---- + +Custom placeholders are not arbitrary, with the following restrictions: + +* **Start Marker Restriction**: `selectorStart` must be one of the following four formats: + ** `${` (default) + ** `$[` + ** `#{` + ** `#[` +* **End Marker Restriction**: `selectorEnd` must be a string of 1 or more characters + +=== Semicolons + +Expression statements can omit ending semicolons, and the return value of the entire script is the calculation result of the last expression. + +The return value of the following script is 2: + +[source,java] +---- +a = 1 +b = 2 +// last express +1+1 +---- + +Equivalent to the following写法: + +[source,java] +---- +a = 1 +b = 2 +// return statment +return 1+1; +---- + +Because semicolons can be omitted, QLExpress4's handling of line breaks is stricter compared to version 3 or Java language. If you want to split multi-line expressions into multiple lines, it's recommended to keep operators on the current line and move the right operand to the next line. + +The following multi-line expression will report a syntax error (counterexample): + +[source,java] +---- +// syntax error +a ++ b +---- + +The following is a correct line break example (positive example): + +[source,java] +---- +a + +b +---- + +Other syntax habits remain consistent with Java. + +=== Expressions + +QLExpress adopts expression-first design, where almost everything is an expression except for import, return, and loop structures. + +if statements are also expressions: + +[source,java] +---- +assert(if (11 == 11) { + 10 +} else { + 20 + 2 +} + 1 == 11) +---- + +try catch structures are also expressions: + +[source,java] +---- +assert(1 + try { + 100 + 1/0 +} catch(e) { + // Throw a zero-division exception + 11 +} == 12) +---- + +=== Short-Circuit Evaluation + +Similar to Java, `&&` and `||` logical operations are all short-circuit evaluations. + +For example, the expression `false && (1/0)` won't cause a division by zero error because `&&` short-circuits at the initial `false`. + +Short-circuit evaluation is enabled by default, and the engine also provides options to turn off short-circuit for a specific execution: + +> One scenario for turning off short-circuit is to ensure full warm-up of expressions + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + // execute when enable short circuit (default) + // `1/0` is short-circuited by the preceding `false`, so it won't throw an error. + assertFalse((Boolean)express4Runner.execute("false && (1/0)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS) + .getResult()); + try { + // execute when disable short circuit + express4Runner.execute("false && (1/0)", + Collections.emptyMap(), + QLOptions.builder().shortCircuitDisable(true).build()); + fail(); + } + catch (QLException e) { + Assert.assertEquals("INVALID_ARITHMETIC", e.getErrorCode()); + Assert.assertEquals("Division by zero", e.getReason()); + } +---- + + +=== Control Structures + +==== if Branch + +In addition to being fully compatible with Java's `if` syntax, it also supports rule engine-like `if ... then ... else ...` syntax, where `then` can be treated as an omittable keyword: + +[source,java] +---- +a = 11; +// if ... else ... +assert(if (a >= 0 && a < 5) { + true +} else if (a >= 5 && a < 10) { + false +} else if (a >= 10 && a < 15) { + true +} == true) + +// if ... then ... else ... +r = if (a == 11) then true else false +assert(r == true) +---- + +==== while Loop + +[source,java] +---- +i = 0; +while (i < 5) { + if (++i == 2) { + break; + } +} +assert(i==2) +---- + +==== for Loop + +[source,java] +---- +l = []; +for (int i = 3; i < 6; i++) { + l.add(i); +} +assert(l==[3,4,5]) +---- + +==== for-each Loop + +[source,java] +---- +sum = 0; +for (i: [0,1,2,3,4]) { + if (i == 2) { + continue; + } + sum += i; +} +assert(sum==8) +---- + +==== try-catch + +[source,java] +---- +assert(try { + 100 + 1/0 +} catch(e) { + // Throw a zero-division exception + 11 +} == 11) +---- + +=== Function Definition + +[source,java] +---- +function sub(a, b) { + return a-b; +} +assert(sub(3,1)==2) +---- + +=== Lambda Expressions + +In QLExpress4, Lambda expressions as first-class citizens can be passed as variables or returned. + +[source,java] +---- +add = (a, b) -> { + return a + b; +} +assert(add(1,2)==3) +---- + +=== List Filtering and Mapping + +Supports direct functional filtering and mapping of list types through filter, map methods. + +Implemented by adding link:#extension-functions[Extension Functions] to list types, note the distinction from methods with the same names in Stream API. + +Compared to Stream API, it can directly operate on lists, and return values are also directly lists, making it more convenient. + +[source,java] +---- +l = ["a-111", "a-222", "b-333", "c-888"] +assert(l.filter(i -> i.startsWith("a-")) + .map(i -> i.split("-")[1]) == ["111", "222"]) +---- + +=== Java8 Syntax Compatibility + +QLExpress can be compatible with common Java8 syntax. + +Such as link:#for-each-loop[for each loops], Stream API, functional interfaces, etc. + +==== Stream API + +You can directly use stream APIs in Java collections to operate on collections. + +Because these stream APIs are all from methods in Java, refer to link:#security-strategy[Security Strategy] to open security options for the following scripts to execute normally. + +[source,java] +---- +l = ["a-111", "a-222", "b-333", "c-888"] + +l2 = l.stream() + .filter(i -> i.startsWith("a-")) + .map(i -> i.split("-")[1]) + .collect(Collectors.toList()); +assert(l2 == ["111", "222"]); +---- + +==== Functional Interfaces + +Java8 introduced functional interfaces like Function, Consumer, Predicate, etc. QLExpress's link:#lambda-expressions[Lambda Expressions] can be assigned to these interfaces or used as method parameters that receive these interfaces: + +[source,java] +---- +Runnable r = () -> a = 8; +r.run(); +assert(a == 8); + +Supplier s = () -> "test"; +assert(s.get() == 'test'); + +Consumer c = (a) -> b = a + "-te"; +c.accept("ccc"); +assert(b == 'ccc-te'); + +Function f = a -> a + 3; +assert(f.apply(1) == 4); + +Function f1 = (a, b) -> a + b; +assert(f1.apply("test-") == "test-null"); +---- + +== Appendix I Upgrade Guide + +QLExpress's previous version had significant gaps with the industry in various features due to years of iteration stagnation. + +One of QLExpress4's goals is to make up for these gaps at once, so it chose to make bold upgrades while intentionally abandoning some compatibility. Of course, basic functionality and experience still align with the previous version. + +If your system already uses the old version of QLExpress, you must conduct a comprehensive regression test before upgrading to ensure all these scripts can execute normally in the new version before upgrading. + +If you don't have time or methods to verify them one by one, then upgrading is not recommended. + +If it's a new system, it's recommended to directly adopt QLExpress4. QLExpress4's ecosystem will become increasingly perfect in the future, while version 3 will be gradually abandoned. + +Below lists the main differences between the new and old versions to help users upgrade existing scripts. If anything is missing, feedback is welcome: + +=== Default Security Strategy + +If you completely use default options, accessing Java object fields (`o.field`) or calling member methods (`o.method()`) will throw `FIELD_NOT_FOUND` and `METHOD_NOT_FOUND` errors respectively. + +This is because version 3 could unrestrictedly access any fields and methods in Java application systems through reflection, which is considered unsafe in embedded scripts. + +If you want to be compatible with version 3's behavior, when creating `Express4Runner`, you need to set the security strategy to "open", refer to the following code: + +[source,java,indent=0] +---- + // open security strategy + Express4Runner express4RunnerOpen = + new Express4Runner(InitOptions.builder().securityStrategy(QLSecurityStrategy.open()).build()); + Assert.assertEquals("Thinking in Java", + express4RunnerOpen.execute("desk.book1", context, QLOptions.DEFAULT_OPTIONS).getResult()); + Assert.assertEquals("Effective Java", + express4RunnerOpen.execute("desk.getBook2()", context, QLOptions.DEFAULT_OPTIONS).getResult()); +---- + +For details, refer to the link:#security-strategy[Security Strategy] chapter. + +=== Defining Mappings + +QLExpress old version supported quickly creating mappings through `NewMap(key:value)` syntax. Although not discussed in detail in documentation, many users learned and used this syntax through unit tests and inquiries. + +However, this syntax was too customized and differed greatly from industry standards, so it was removed in the new version. + +The new version natively supports JSON syntax and directly adopts JSON dictionary format (`{key:value}`) to quickly create mappings, making it more intuitive. + +For details, refer to link:#convenient-syntax-elements[Convenient Syntax Elements] + +=== Global Variable Context Pollution + +QLExpress supports passing in a global context when executing scripts, i.e., the context parameter. + +In the old version, if global variables were defined in scripts, these variables would also be written to the context. After script execution, you could obtain the values of global variables defined in scripts through the context. + +An old version example is as follows: + +[source,java] +---- +// only for QLExpress 3.x + +String express = "a=3;a+1"; +ExpressRunner runner = new ExpressRunner(false, true); +DefaultContext context = new DefaultContext<>(); + +Object res = runner.execute(express, context, null, true, true); +// The result of the script execution should be 4 (a+1) +Assert.assertEquals(4, res); +// The variable 'a' defined in the script is also stored in the context +Assert.assertEquals(3, context.get("a")); +---- + +Based on research and feedback, we believe this would cause the global context to be "polluted" by scripts, posing security risks. + +Therefore, in QLExpress4, global variables are not written to the context by default. + +If you want to be compatible with version 3's features, you need to set the `polluteUserContext` option to `true`, refer to the following code: + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + QLOptions populateOption = QLOptions.builder().polluteUserContext(true).build(); + Map populatedMap = new HashMap<>(); + populatedMap.put("b", 10); + express4Runner.execute("a = 11;b = a", populatedMap, populateOption); + assertEquals(11, populatedMap.get("a")); + assertEquals(11, populatedMap.get("b")); + + // no population + Map noPopulatedMap1 = new HashMap<>(); + express4Runner.execute("a = 11", noPopulatedMap1, QLOptions.DEFAULT_OPTIONS); + assertFalse(noPopulatedMap1.containsKey("a")); + + Map noPopulatedMap2 = new HashMap<>(); + noPopulatedMap2.put("a", 10); + assertEquals(19, express4Runner.execute("a = 19;a", noPopulatedMap2, QLOptions.DEFAULT_OPTIONS).getResult()); + assertEquals(10, noPopulatedMap2.get("a")); +---- + +=== Semicolons Can Be Omitted + +"Omittable semicolons" is already a standard feature of modern scripting languages, and QLExpress4 has also followed this trend - semicolons can be omitted. + +For details, refer to the link:#semicolons[Semicolons] chapter. + +=== Strict Newline Mode + +Since QLExpress4 supports omitting semicolons, the interpreter needs to use newlines to determine whether a statement has ended. Therefore, QLExpress4 has stricter requirements for newlines than QLExpress3. + +The following script is valid in QLExpress3 but not in QLExpress4: + +[source,java] +---- +// Valid in QLExpress3, but not in QLExpress4 +商家应收= + 价格 + - 饭卡商家承担 + + 平台补贴 +---- + +In QLExpress4, the above script will be parsed as two separate statements: +1. `商家应收 = 价格` +2. `- 饭卡商家承担 + 平台补贴` (the second statement will result in a syntax error) + +To achieve the same effect in QLExpress4, you need to place the operator at the end of the line rather than at the beginning: + +[source,java] +---- +// Correct way in QLExpress4 +商家应收= + 价格 - + 饭卡商家承担 + + 平台补贴 +---- + +This way, the interpreter knows that the current line's expression is not yet complete and will continue reading the next line. + +If you need to be compatible with QLExpress3's newline behavior, you can set the `strictNewLines` option to `false`: + +[source,java] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.builder().strictNewLines(false).build()); + String script = "商家应收=\n 价格\n - 饭卡商家承担\n + 平台补贴"; + Map context = new HashMap<>(); + context.put("价格", 10); + context.put("饭卡商家承担", 3); + context.put("平台补贴", 5); + QLResult result = express4Runner.execute(script, context, QLOptions.DEFAULT_OPTIONS); + Assert.assertEquals(12, ((Number)result.getResult()).intValue()); +---- + +Note: Non-strict newline mode causes the interpreter to ignore all newlines, which may affect code readability and the accuracy of error messages. It is recommended to use this only when you need to be compatible with legacy code. + +=== Obtaining char Type + +In QLExpress 3, single characters wrapped in single quotes were parsed as char type, not String. + +This caused much confusion for users, for example, the result of `"a"=='a'` would be `false`. + +So later in QLExpress 3, the `ExpressRunner.setIgnoreConstChar` option was added. When set to `true`, all characters wrapped in single quotes and double quotes would be parsed as String type. However, this option was turned off by default and required users to manually enable it. + +Considering that script users rarely use the `char` primitive type, we directly removed this option in QLExpress 4. All characters wrapped in single quotes and double quotes are now parsed as String type. + +If you still need to use `char` type in scripts, you can obtain it through two methods: + +* Type casting: `(char) 'a'` +* Type declaration: `char a = 'a'` + +== Appendix II How to Contribute? + +QLExpress is completely open to community changes. Any suggestions and modifications will be welcome, and after discussion, reasonable ones will be accepted into the main branch. + +First, you need to clone the code to local. Before formally modifying the code, you need to prepare as follows: + +1. Execute `mvn compile` in the project root directory: When the project is first downloaded locally, there will be many classes not found, so you need to generate Antlr4 runtime code first +2. Configure code formatting: QLExpress project has unified code format specifications, and automatic formatting needs to be configured before git commits during development + +Create a new file `.git/hooks/pre-commit` in the project directory with the following content: + +[source,bash] +---- +#!/bin/sh +mvn spotless:apply +git add -u +exit 0 +---- + +This way, before each git commit, maven's spotless plugin will automatically execute code formatting. For specific code format configuration, see link:spotless_eclipse_formatter.xml[] + +3. Run unit tests: After completing code development, run all unit tests locally to ensure code quality + - JDK 8 environment: Execute `mvn test` + - JDK 9 and above environment: Execute `mvn test -DargLine="--add-opens java.base/java.util.stream=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED"` + +== Appendix III QLExpress4 Performance Improvements + +link:https://www.yuque.com/xuanheng-ffjti/iunlps/pgfzw46zel2xfnie?singleDoc#%20%E3%80%8AQLExpress3%E4%B8%8E4%E6%80%A7%E8%83%BD%E5%AF%B9%E6%AF%94%E3%80%8B[QLExpress4 vs 3 Performance Comparison] + +Summary: In common scenarios, without compilation cache, QLExpress4 can achieve nearly 10x performance improvement over version 3; with compilation cache, there's also a 1x performance improvement. + +== Appendix IV Developer Contact Information + + * Email: + ** qinyuan.dqy@alibaba-inc.com + ** yumin.pym@taobao.com + ** 704643716@qq.com + * WeChat: + ** xuanheng: dqy932087612 + ** binggou: pymbupt + ** linxiang: tkk33362 + * DingTalk Support Group + +image::images/qlexpress_support_group_qr_2026.jpg[] diff --git a/README-source.adoc b/README-source.adoc new file mode 100644 index 0000000..e376c52 --- /dev/null +++ b/README-source.adoc @@ -0,0 +1,1109 @@ +:toc: + += QLExpress + +image::images/logo.png[] + +【中文版】| link:README-EN.adoc[[English\]] + +image::https://api.star-history.com/svg?repos=alibaba/QLExpress&type=Date[Star History Chart] + +== 背景介绍 + +由阿里的电商业务规则演化而来的嵌入式Java动态脚本工具,在阿里集团有很强的影响力,同时为了自身不断优化、发扬开源贡献精神,于2012年开源。 + +在基本的表达式计算的基础上,还有以下特色: + +* 灵活的自定义能力,通过 Java API 自定义函数和操作符,可以快速实现业务规则的 DSL +* 兼容最新的 Java 语法,方便 Java 程序员快速熟悉。熟悉类 C 语言的业务人员使用起来也会非常顺手 +* 原生支持 JSON,快捷定义复杂数据结构 +* 友好的报错提示,无论是编译还是运行时错误,都能精确友好地提示错误位置 +* 独一无二的表达式追踪功能,可以追踪表达式在中间节点计算的值,方便业务人员或者 AI 对线上规则的计算结果进行归因分析 +* 默认安全,脚本默认不允许和应用代码进行交互,如果需要交互,也可以自行定义安全的交互方式 +* 解释执行,不占用 JVM 元空间,可以开启缓存提升解释性能 +* 代码精简,依赖最小,适合所有java的运行环境 + +QLExpress4 作为 QLExpress 的最新演进版本,基于 Antlr4 重写了解析引擎,将原先的优点进一步发扬光大,新增了大量特色功能,彻底拥抱函数式编程,在性能和表达能力上都进行了进一步增强。 + +> 如果项目还是使用旧版本的 QLExpress,可以跳转 link:https://github.com/alibaba/QLExpress/tree/branch_version_3.x.x[branch_version_3.x.x] 维护分支查看旧版文档。如需升级可以参考 link:#附录一-升级指南[附录一-升级指南] + +场景举例: + +* 电商优惠券规则配置:通过 QLExpress 自定义函数和操作符快速实现优惠规则 DSL,供运营人员根据需求自行动态配置 +* 表单搭建控件关联规则配置:表单搭建平台允许用户拖拽控件搭建自定义的表单,利用 QLExpress 脚本配置不同控件间的关联关系 +* 流程引擎条件规则配置 +* 广告系统计费规则配置 + +\...\... + +== 新版特色 + +新版本并不是对旧版本的简单功能重构,而是我们基于对用户需求的洞察,对下一代规则表达式引擎的探索。拥有许多非常实用,但是在其他引擎中缺失的重要功能。 + +=== 表达式计算追踪 + +在业务人员完成规则脚本的配置后,很难对其线上执行情况进行感知。比如电商的促销规则,要求用户满足规则 `isVip && 未登录10天以上`。到底有多少线上用户是被 vip 条件拦截,又有多少用户是因为登录条件被拦截?这还是只是仅仅两个条件的简单规则,实际线上情况则更加复杂。 + +线上规则执行情况的追踪,不仅仅可以帮助业务人员了解线上的实际情况,排查和修复问题。其沉淀的数据也非常有价值,可以用于后续的规则优化和业务决策。以下是某个规则平台,基于 QLExpress4 的表达式追踪能力,对规则进行归因分析与附注的决策的产品简化图: + +image::images/order_rules_cn.png[] + +归因分析的原理在于利用 QLExpress4 的表达式追踪能力,获得表达式在计算过程中每个中间结果的值, 据此判断表达式最终运行结果产生的原因。 + +具体使用方法参考:link:#表达式计算追踪-1[表达式计算追踪] + +=== 原生支持 JSON 语法 + +QLExpress4 原生支持 JSON 语法,可以快捷定义复杂的数据结构。 + +JSON 数组代表列表(List),而 JSON 对象代表映射(Map),也可以直接定义复杂对象。 + +产品上可以基于该特性实现 JSON 映射规则。让用户可以便捷地定义从一个模型向另一个模型的映射关系。以下是某个规则平台,基于该能力实现的模型映射产品简化图: + +image::images/json_map.png[] + +具体使用方法参考:link:#方便语法元素[方便语法元素] + +=== 便捷字符串处理 + +QLExpress4 对字符串处理能力进行针对性的增强,在字符串中可以直接通过 `$\{expression}` 嵌入表达式计算结果。 + +具体使用方法参考:link:#动态字符串[动态字符串] + +=== 附件透传 + +正常情况下,脚本执行需要的全部信息都在 `context` 中。context 中的 key 可以在脚本中作为变量引用,最终传递给自定义函数或者操作符。 + +但是出于安全,或者方便使用等因素考虑。有些信息并不希望用户通过变量引用到,比如租户名,密码等等。 + +此时可以通过附件(attachments)将这部分信息传递给自定义函数或者操作符使用。 + +具体使用方法参考:link:#添加自定义函数与操作符[添加自定义函数与操作符] 其中 `hello` 自定义函数根据附件中租户不同,返回不同的欢迎信息的示例。 + +=== 函数式编程 + +函数被提升为 QLExpress4 中的第一等公民,可以作为变量使用,也可以作为函数的返回值。并且可以很容易地和 Java 中常见的函数式 API(比如 Stream) 结合使用。 + +以下是一个简单的 QLExpress 示例脚本: + +[source,java] +---- +include::./src/test/resources/testsuite/independent/lambda/lambda_doc.ql[] +---- + +更多使用方法参考: + +* link:#lambda-表达式[Lambda表达式] +* link:#列表过滤和映射[列表过滤和映射] +* link:#stream-api[Stream API] +* link:#函数式接口[函数式接口] + +=== 分号简化 + +QLExpress4 支持省略分号,让表达式更加简洁。具体参考 link:#分号[分号] + +== API 快速入门 + +=== 引入依赖 + +[source,xml] +---- + + com.alibaba + qlexpress4 + 4.0.7 + +---- + +环境要求: + +* JDK 8 或更高版本 + +=== 第一个 QLExpress 程序 + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=firstQl] +---- + +更多的表达式执行方式见文档 link:docs/execute.adoc[表达式执行] + +=== 添加自定义函数与操作符 + +最简单的方式是通过 Java Lambda 表达式快速定义函数/操作符的逻辑: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=addFunctionAndOperator] +---- + +如果自定义函数的逻辑比较复杂,或者需要获得脚本的上下文信息,也可以通过继承 `CustomFunction` 的方式实现。 + +比如下面的 `hello` 自定义函数,根据租户不同,返回不同的欢迎信息: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/test/function/HelloFunction.java[] +---- + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=customComplexFunction] +---- + +QLExpress4还支持通过QLExpress脚本添加自定义函数。需要注意的是,在函数外定义的变量(如示例中的defineTime)在函数定义时就已初始化完成,后续调用函数时不会重新计算该变量的值。 + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=addFunctionsDefinedInScript] +---- + +建议尽可能使用Java方式定义自定义函数,这样可以获得更好的性能和稳定性。 + +更多自定义语法元素的方式见文档 link:docs/custom-item.adoc[自定义语法元素] + +=== 校验语法正确性 + +在不执行脚本的情况下,单纯校验语法的正确性,其中包含了操作符的限制校验,调用 `check` 并且捕获异常,如果捕获到 `QLSyntaxException`,则说明存在语法错误 + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=checkSyntax] +---- + +你可以使用 `CheckOptions` 配置更精细的语法校验规则,主要支持以下两个选项: + +1. `operatorCheckStrategy`: 操作符校验策略,用于限制脚本中可以使用的操作符 +2. `disableFunctionCalls`: 是否禁用函数调用,默认为 false + +示例1:使用操作符校验策略(白名单) + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/OperatorLimitTest.java[tag=operatorCheckStrategyExample] +---- + +示例2:禁用函数调用 + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=disableFunctionCallsExample] +---- + +=== 解析脚本所需外部变量 + +脚本中使用的变量有的是脚本内生,有的是需要从外部通过 `context` 传入的。 + +QLExpress4 提供了一个方法,可以解析出脚本中所有需要从外部传入的变量: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=getOutVarNames] +---- + +更多脚本依赖解析工具: + +* `getOutFunctions`: 解析所有需要从外部定义的函数 +* `getOutVarAttrs`:解析所有需要从外部传入变量及其涉及的属性,`getOutVarNames` 的增强版本 + +=== 高精度计算 + +QLExpress 内部会用 BigDecimal 表示所有无法用 double 精确表示数字,来尽可能地表示计算精度: + +> 举例:0.1 在 double 中无法精确表示 + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=bigDecimalForPrecise] +---- + +通过这种方式能够解决一些计算精度问题: + +比如 0.1+0.2 因为精度问题,在 Java 中是不等于 0.3 的。 +而 QLExpress 能够自动识别出 0.1 和 0.2 无法用双精度精确表示,改成用 BigDecimal 表示,确保其结果等于0.3 + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=preciseComparisonWithJava] +---- + +除了默认的精度保证外,还提供了 `precise` 开关,打开后所有的计算都使用BigDecimal,防止外部传入的低精度数字导致的问题: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=preciseSwitch] +---- + +=== 安全策略 + +QLExpress4 默认采用隔离安全策略,不允许脚本访问 Java 对象的字段和方法,这确保了脚本执行的安全性。如果需要访问 Java 对象,可以通过不同的安全策略进行配置。 + +假设应用中有如下的 Java 类: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/inport/MyDesk.java[] +---- + +脚本执行的上下文设置如下: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=securityStrategyContextSetup] +---- + +QLExpress4 提供了四种安全策略: + +==== 1. 隔离策略(默认) + +默认情况下,QLExpress4 采用隔离策略,不允许访问任何字段和方法: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=securityStrategyIsolation] +---- + +==== 2. 黑名单策略 + +通过黑名单策略,可以禁止访问特定的字段或方法,其他字段和方法可以正常访问: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=securityStrategyBlackList] +---- + +==== 3. 白名单策略 + +通过白名单策略,只允许访问指定的字段或方法,其他字段和方法都会被禁止: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=securityStrategyWhiteList] +---- + +==== 4. 开放策略 + +开放策略允许访问所有字段和方法,类似于 QLExpress3 的行为,但需要注意安全风险: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=securityStrategyOpen] +---- + +> 注意:开放策略虽然提供了最大的灵活性,但也带来了安全风险。建议只在受信任的环境中使用,不建议用于处理终端用户输入的脚本。 + +==== 策略建议 + +建议直接采用默认策略,在脚本中不要直接调用 Java 对象的字段和方法。而是通过自定义函数和操作符的方式(参考 link:#添加自定义函数与操作符[添加自定义函数与操作符]),对嵌入式脚本提供系统功能。这样能同时保证脚本的安全性和灵活性,用户体验还更好。 + +如果确实需要调用 Java 对象的字段和方法,至少应该使用白名单策略,只提供脚本有限的访问权限。 + +至于黑名单和开放策略,不建议在外部输入脚本的场景使用,除非确保每个脚本都会经过审核。 + +=== 调用应用中的 Java 类 + +> 需要放开安全策略,不建议用于终端用户输入 + +假设应用中有如下的 Java 类(`com.alibaba.qlexpress4.QLImportTester`): + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/QLImportTester.java[] +---- + +在 QLExpress 中有如下两种调用方式。 + +==== 1. 在脚本中使用 `import` 语句导入类并且使用 + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=importJavaCls] +---- + +==== 2. 在创建 `Express4Runner` 时默认导入该类,此时脚本中就不需要额外的 `import` 语句 + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=defaultImport] +---- + +除了用 `ImportManager.importCls` 导入单个类外,还有其他更方便的导入方式: + + * `ImportManager.importPack` 直接导入包路径下的所有类,比如 `ImportManager.importPack("java.util")` 会导入 `java.util` 包下的所有类,QLExpress 默认就会导入下面的包 + ** `ImportManager.importPack("java.lang")` + ** `ImportManager.importPack("java.util")` + ** `ImportManager.importPack("java.math")` + ** `ImportManager.importPack("java.util.stream")` + ** `ImportManager.importPack("java.util.function")` + * `ImportManager.importInnerCls` 导入给定类路径里的所有内部类 + +=== 自定义 ClassLoader + +QLExpress4 支持通过自定义 `ClassSupplier` 来指定类加载器,这在插件化架构、模块化应用等场景中非常有用。通过自定义类加载器,可以让 QLExpress 脚本访问特定 ClassLoader 中的类。 + +下面的示例展示了如何与 link:https://pf4j.org/[PF4J] 插件框架集成,让 QLExpress 脚本能够访问插件中的类: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/pf4j/Pf4jClassSupplierTest.java[tag=pluginClassSupplier] +---- + +自定义 ClassSupplier 的典型应用场景: + +* **插件化架构**:让脚本能够访问插件中定义的类和接口 +* **模块化应用**:在 OSGi 等模块化框架中,让脚本访问特定模块的类 +* **动态类加载**:从远程仓库或动态生成的字节码中加载类 +* **类隔离**:使用不同的 ClassLoader 来实现类的隔离 + +=== 表达式缓存 + +通过 `cache` 选项可以开启表达式缓存,这样相同的表达式就不会重新编译,能够大大提升性能。 + +注意该缓存没有限制大小,只适合在表达式为有限数量的情况下使用: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=cacheSwitch] +---- + +但是当脚本首次执行时,因为没有缓存,依旧会比较慢。 + +可以通过下面的方法在首次执行前就将脚本缓存起来,保证首次执行的速度: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=parseToCache] +---- + +注意该缓存的大小是无限的,业务上注意控制大小,可以调用 `clearCompileCache` 方法定期清空编译缓存。 + +=== 清除 DFA 缓存 + +QLExpress 使用 ANTLR4 作为解析引擎,ANTLR4 在运行时会构建 DFA (确定有限状态自动机) 缓存来加速后续的语法解析。这个缓存会占用一定的内存空间。 + +在某些内存敏感的场景下,可以通过调用 `clearDFACache` 方法来清除 DFA 缓存,释放内存。 + +> **重要警告**: 清除 DFA 缓存会导致编译性能大幅下降,正常情况下不建议使用此方法。 + +==== 适用场景 + +* **内存敏感型应用**: 当内存使用是关键考虑因素,且可以容忍较慢的编译时间时 +* **脚本不经常变更**: 当脚本相对稳定且不会频繁重新编译时 + +==== 最佳实践 + +在解析并缓存表达式后立即调用此方法,并确保后续所有执行都打开缓存选项以避免重新编译。示例代码如下: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/ClearDfaCacheTest.java[tag=clearDFACacheBestPractice] +---- + +通过这种方式,可以在保证性能的同时,最大限度地降低内存占用。 + +=== 设置超时时间 + +可以给脚本设置一个超时时间,防止其中存在死循环或者其他原因导致应用资源被过量消耗。 + +下面的示例代码给脚本给脚本设置了一个 10ms 的超时时间: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=scripTimeout] +---- + +> 注意,出于系统性能的考虑,QLExpress 对于超时时间的检测是不准确的。特别是在回调Java代码中(比如自定义函数或者操作符)出现的超时,不会立刻被检测到。只有在执行完,回到 QLExpress 运行时后才会被检测到并中断执行。 + +=== 扩展函数 + +利用 QLExpress 提供的扩展函数能力,可以给Java类中添加额外的成员方法。 + +扩展函数是基于 QLExpress 运行时实现的,因此仅仅在 QLExpress 脚本中有效。 + +下面的示例代码给 String 类添加了一个 `hello()` 扩展函数: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=extensionFunction] +---- + +=== Java类的对象,字段和方法别名 + +QLExpress 支持通过 `QLAlias` 注解给对象,字段或者方法定义一个或多个别名,方便非技术人员使用表达式定义规则。 + +下面的例子中,根据用户是否 vip 计算订单最终金额。 + +用户类定义: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/test/qlalias/User.java[] +---- + +订单类定义: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/test/qlalias/Order.java[] +---- + +通过 QLExpress 脚本规则计算最终订单金额: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=qlAlias] +---- + +=== 关键字,操作符和函数别名 + +为了进一步方面非技术人员编写规则,QLExpress 提供 `addAlias` 给原始关键字,操作符和函数增加别名。让整个脚本的表述更加贴近自然语言。 + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=addAlias] +---- + +支持设置别名的关键字有: + + * if + * then + * else + * for + * while + * break + * continue + * return + * function + * macro + * new + * null + * true + * false + +> 注意:部分大家熟悉的用法其实是操作符,而不是关键字,比如 `in` 操作符。而所有的操作符和函数默认就是支持别名的 + +=== 宏 + +宏是QLExpress中一个强大的代码复用机制,它允许用户定义一段可重用的脚本片段,并在需要时进行调用。与简单的文本替换不同,QLExpress的宏是基于指令回放的机制实现的,具有更好的性能和语义准确性。 + +宏特别适用于以下场景: + +* **代码复用**:将常用的脚本片段封装成宏,避免重复编写相同的逻辑 +* **业务规则模板**:定义标准的业务规则模板,如价格计算、权限检查等 +* **流程控制**:封装复杂的控制流程,如条件判断、循环逻辑等 +* **DSL构建**:作为构建领域特定语言的基础组件 + +宏可以通过两种方式定义: + +**1. 在脚本中使用 `macro` 关键字定义** + +[source,java] +---- +include::./src/test/resources/testsuite/independent/macro/macro.ql[tag=addMacroInScript] +---- + +**2. 通过Java API添加** + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=addMacro] +---- + +宏与函数的区别: + +[cols="1,1,1"] +|=== +| 特性 | 宏 | 函数 +| 参数传递 | 无参数,依赖上下文变量 | 支持参数传递 +| 性能 | 指令直接插入,无调用开销 | 有函数调用开销 +| 作用域 | 共享调用者作用域 | 独立的作用域 +| 适用场景 | 代码片段复用 | 逻辑封装和参数化 +|=== + +宏特别适合那些不需要参数传递、主要依赖上下文变量的代码片段复用场景,而函数更适合需要参数化和独立作用域的场景。 + +**QLExpress4 相比 3 版本,宏特性的变化**: + + * 4 的宏实现更加接近通常编程语言中宏的定义,相当于将预定义的代码片段插入到宏所在的位置,与调用点位于同一作用域,宏中的 `return`, `contine` 和 `break` 等可以影响调用方的控制流。但是 3 中的实现其实更加接近无参函数调用。 + * 4 的宏无法作为变量使用,只有单独作为一行语句时才能被宏替换。因为宏可以是任意脚本,不一定是有返回值的表达式,作为变量时会存在语义问题。3 的宏本质是一个无参函数调用,所以常常被作为变量使用 + +如果想兼容 3 中的宏特性,建议使用 link:#动态变量[动态变量] + +=== 动态变量 + +常规的 “静态变量”,是 context 中和 key 关联的固定的值。而动态变量可以是一个表达式,由另外一些变量计算而得。动态变量支持嵌套,即动态变量可以依赖另一个动态变量计算得到。 + +示例如下: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=dynamicVar] +---- + +=== 表达式计算追踪 + +如果打开相关选项,QLExpress4 就会在返回规则脚本计算结果的同时,返回一颗表达式追踪树。表达式追踪树的结构类似语法树,不同之处在于,它会在每个节点上记录本次执行的中间结果。 + +比如对于表达式 `!true || myTest(a, 1)`,表达式追踪树的结构大概如下: + +[source] +---- + || true + / \ + ! false myTest + / / \ + true a 10 1 +---- + +可应用于多种场景: + + * 方便业务人员对规则的计算结果进行分析排查 + * 对线上判断为 false 的规则进行采样归类 + * AI 自动诊断和修复规则 + +节点计算结果会被放置到 `ExpressionTrace` 对象的 `value` 字段中。如果中间发生短路导致部分表达式未被计算,则 `ExpressionTrace` 对象的 `evaluated` 字段会被设置为 false。代码示例如下: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=expressionTrace] +---- + +> 注意,必须在新建 `Express4Runner` 时将 `InitOptions.traceExpression` 选项设置为 true,同时在执行脚本时将 `QLOptions.traceExpression` 设置为 true,该功能才能生效。 + +也可以在不执行脚本的情况下获得所有表达式追踪点: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=getExpressionTracePoints] +---- + +支持的表达式追踪点类型以及对应子节点的含义如下: + +[cols="1,1,1"] +|=== +| 节点类型 | 节点含义 | 子节点含义 +| OPERATOR | 操作符 | 两侧操作数 +| FUNCTION | 函数 | 函数参数 +| METHOD | 方法 | 方法参数 +| FIELD | 字段 | 取字段的目标对象 +| LIST | 列表 | 列表元素 +| MAP | 字段 | 无 +| IF | 条件分支 | condition表达式,then逻辑块和else逻辑块 +| RETURN | 返回语句 | 返回表达式 +| VARIABLE | 变量 | 无 +| VALUE | 字面值 | 无 +| DEFINE_FUNCTION | 定义函数 | 无 +| DEFINE_MACRO | 定义宏 | 无 +| PRIMARY | 暂时未继续下钻的其他复合值(比如字典,if等等)| 无 +| STATEMENT | 暂未继续下钻的其他复合语句(比如 while, for 等等)| 无 +|=== + +=== 与 Spring 集成 + +QLExpress 并不需要专门与 Spring 集成,只需要一个 `Express4Runner` 单例,即可使用。 + +这里提供的 “集成” 示例,可以在 QLExpress 脚本中直接引用任意 Spring Bean。 + +这种方式虽然很方便,但是脚本权限过大,自由度太高。不再推荐使用,还是建议在 context 只放入允许用户访问的对象。 + +核心集成组件: + +* link:src/test/java/com/alibaba/qlexpress4/spring/QLSpringContext.java[QLSpringContext]: 实现了 `ExpressContext` 接口,提供了对 Spring 容器的访问能力。它会优先从传入的 context 中查找变量,如果找不到则尝试从 Spring 容器中获取同名的 Bean。 +* link:src/test/java/com/alibaba/qlexpress4/spring/QLExecuteService.java[QLExecuteService]: 封装了 QLExpress 的执行逻辑,集成了 Spring 容器,方便在 Spring 应用中使用。 + +假设存在一个 Spring Bean, 名为 `helloService`: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/spring/HelloService.java[] +---- + +在脚本中调用该 Bean: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/spring/SpringDemoTest.java[] +---- + + +== 语法入门 + +QLExpress4 兼容 Java8 语法的同时,也提供了很多更加灵活宽松的语法模式,帮助用户更快捷地编写表达式。 + +基于表达式优先的语法设计,复杂的条件判断语句也可以直接当作表达式使用。 + +在本章节中出现的代码片段都是 qlexpress 脚本, +`assert` 是测试框架往引擎中注入的断言方法,会确保其参数为 `true`。 +`assertErrCode` 会确保其 lambda 参数表达式的执行一定会抛出含第二个参数 error code 的 QLException。 + +=== 变量声明 + +同时支持静态类型和动态类型: + + * 变量声明时不写类型,则变量是动态类型,也同时是一个赋值表达式 + * 变量声明如果写类型,则是静态类型,此时是一个变量声明语句 + +[source,java] +---- +include::./src/test/resources/testsuite/independent/doc/dynamic_typing.ql[] +---- + +=== 方便语法元素 + +列表(List),映射(Map)等常用语法元素在 QLExpress 中都有非常方便的构造语法糖: + +[source,java] +---- +include::./src/test/resources/testsuite/independent/doc/convenient_syntax_elements.ql[] +---- + +通过 `*.` 操作符,可以快捷地对列表和映射进行处理,比如对列表元素进行取属性,或者获得映射的 key 列表和 value 列表: + +[source,java] +---- +include::./src/test/resources/testsuite/independent/spread/list_spread.ql[tag=spreadExample] +---- + +在映射中通过 `@class` key 指定类型的全限定名,就可以直接使用 JSON 创建复杂Java对象。比如下面的 MyHome , 是一个含有复杂嵌套类型 Java 类: + +[source,java] +---- +include::./src/test/java/com/alibaba/qlexpress4/inport/MyHome.java[] +---- + +可以通过下面的 QLExpress 脚本,便捷创建: + +> 注意,该特性需要参考 link:#安全策略[安全策略] 打开安全选项,才能正常执行。 + +[source,java] +---- +include::./src/test/resources/testsuite/java/map/classified_json.ql[tag=classifiedJson] +---- + +=== 数字 + +对于未声明类型的数字, +QLExpress会根据其所属范围自动从 int, long, BigInteger, double, BigDecimal 等数据类型中选择一个最合适的: + +[source,java] +---- +include::./src/test/resources/testsuite/java/number/number_auto_type.ql[] +---- + +因此在自定义函数或者操作符时,建议使用 Number 类型进行接收,因为数字类型是无法事先确定的。 + +=== 动态字符串 + +动态字符串是 QLExpress 为了增强字符串处理能力,在 4 版本新引入的能力。 + +支持 `$\{expression}` 的格式在字符串中插入表达式计算: + +> 如果想在字符串中原样保持 `$\{expression}`,可以使用 `\$` 对 `$` 进行转义 + +[source,java] +---- +include::./src/test/resources/testsuite/independent/doc/dynamic_string.ql[] +---- + +如果还想让 QLExpress4 的字符串和 3 保持兼容性,不对插值表达式进行处理,可以在新建 `Express4Runner` 时直接关闭该特性: + +[source,java] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=disableInterpolation] +---- + +=== 模板语言 + +利用动态字符串能力,QLExpress4 也可以作为一个轻量级模板引擎使用。 + +无需在脚本中手动添加字符串引号,直接调用 `executeTemplate` 渲染模板字符串: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=templateEngine] +---- + +=== 占位符 + +占位符用于从 context 中提取任意 key 的值。 + +全局变量也可以从 context 中提取值,但是收到 QLExpress 关键词和语法的限制,能提取的 key 有限。 +比如 context 中 "0" key 对应的值就无法通过变量提取,因为 0 不是 QLExpress 中的合法变量,而是一个数字常量。 +此时可以用默认占位符 `$\{0}` 来提取。 + +> 注意和动态字符串中插值区分,占位符是写在字符串之外。动态字符串插值是 `$\{expression}`,其中默认写的是表达式,`"${0}"` 的运行结果是 `"0"`。而占位符是 `$\{placeholder}`,其中默认写的是 context 中的 key,`${0}` 的运行结果是 context 中 "0" key 对应的值。 + +QLExpress 默认使用 `$\{placeholder}` 格式的占位符,其中: + +* `${` 是起始标记 +* `}` 是结束标记 +* `placeholder` 是占位符内容,对应 cotext 中的 key + +除了默认的占位符外,QLExpress 还支持自定义占位符的起始和结束标记: + +[source,java] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=customSelector] +---- + +自定义占位符并不是任意的,限制条件如下: + +* **起始标记限制**:`selectorStart` 必须是以下四种格式之一: + ** `${` (默认) + ** `$[` + ** `#{` + ** `#[` +* **结束标记限制**:`selectorEnd` 必须是 1 个或更多字符的字符串 + +=== 分号 + +表达式语句可以省略结尾的分号,整个脚本的返回值就是最后一个表达式的计算结果。 + +以下脚本的返回值为 2: + +[source,java] +---- +a = 1 +b = 2 +// last express +1+1 +---- + +等价于以下写法: + +[source,java] +---- +a = 1 +b = 2 +// return statment +return 1+1; +---- + +因为分号可以省略,QLExpress4 对于换行的处理相比 3 或者 Java 语言更加严格。如果想要将多行表达式拆成多行,建议将操作符保留在当前行,而将右操作数换到下一行。 + +以下多行表达式会报语法错误(反例): + +[source,java] +---- +// syntax error +a ++ b +---- + +以下是正确的换行示例(正例): + +[source,java] +---- +a + +b +---- + +其他的语法习惯保持和 Java 一致即可。 + +=== 表达式 + +QLExpress 采用表达式优先的设计,其中 除了 import, return 和循环等结构外,几乎都是表达式。 + +if 语句也是一个表达式: + +[source,java] +---- +include::./src/test/resources/testsuite/independent/doc/if_as_expr.ql[] +---- + +try catch 结构也是一个表达式: + +[source,java] +---- +include::./src/test/resources/testsuite/independent/doc/try_catch_as_expr.ql[] +---- + +=== 短路计算 + +和 Java 类似,`&&` 和 `||` 逻辑运算都是短路运算的。 + +比如表达式 `false && (1/0)` 不会发生除 0 错误,因为 `&&` 短路在了最开始的 `false` 处。 + +短路计算默认是开启的,引擎也提供了选项,可以在某次执行时将短路关闭: + +> 关闭短路的一个场景是保证表达式的充分预热 + +[source,java] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=disableShortCircuit] +---- + + +=== 控制结构 + +==== if 分支 + +除了完全兼容 Java 中的 `if` 写法,还支持类似规则引擎的 `if ... then ... else ...` 的写法,其中 `then` 可以当成一个可以省略的关键字: + +[source,java] +---- +include::./src/test/resources/testsuite/independent/doc/if.ql[] +---- + +==== while 循环 + +[source,java] +---- +include::./src/test/resources/testsuite/independent/doc/while.ql[] +---- + +==== for 循环 + +[source,java] +---- +include::./src/test/resources/testsuite/independent/doc/for.ql[] +---- + +==== for-each 循环 + +[source,java] +---- +include::./src/test/resources/testsuite/independent/doc/for_each.ql[] +---- + +==== try-catch + +[source,java] +---- +include::./src/test/resources/testsuite/independent/doc/try_catch.ql[] +---- + +=== 函数定义 + +[source,java] +---- +include::./src/test/resources/testsuite/independent/doc/function.ql[] +---- + +=== Lambda 表达式 + +QLExpress4 中,Lambda 表达式作为一等公民,可以作为变量进行传递或者返回。 + +[source,java] +---- +include::./src/test/resources/testsuite/independent/doc/lambda.ql[] +---- + +=== 列表过滤和映射 + +支持通过 filter, map 方法直接对列表类型进行函数式过滤和映射。 + +底层通过在列表类型添加 link:#扩展函数[扩展函数] 实现,注意和 Stream API 中同名方法区分。 + +相比 Stream Api,它可以直接对列表进行操作,返回值也直接就是列表,更加方便。 + +[source,java] +---- +include::./src/test/resources/testsuite/independent/doc/list_map_filter.ql[] +---- + +=== 兼容 Java8 语法 + +QLExpress 可以兼容 Java8 的常见语法。 + +比如 link:#for-each-循环[for each循环], Stream API, 函数式接口等等。 + +==== Stream API + +可以直接使用 Java 集合中的 stream api 对集合进行操作。 + +因为此时的 stream api 都是来自 Java 中的方法,参考 link:#安全策略[安全策略] 打开安全选项,以下脚本才能正常执行。 + +[source,java] +---- +include::./src/test/resources/testsuite/java/stream/java_stream.ql[] +---- + +==== 函数式接口 + +Java8 中引入了 Function, Consumer, Predicate 等函数式接口,QLExpress 中的 link:#lambda-表达式[Lambda表达式] 可以赋值给这些接口,或者作为接收这些接口的方法参数: + +[source,java] +---- +include::./src/test/resources/testsuite/java/lambda/java_functional_interface.ql[] +---- + +== 附录一 升级指南 + +QLExpress 的上一版本因为多年的迭代停滞,在各项特性上和业界产生了较大差距。 + +QLExpress4 的目标之一就是一次性弥补这些差距,因此选择进行了大刀阔斧的升级,而有意放弃了部分兼容性。当然,基础的功能和体验还是和上一版本保持了对齐。 + +如果系统已经使用老版本的 QLExpress,升级之前务必要进行一次全面的回归测试,确保这些脚本都能在新版中正常执行,再进行升级。 + +如果没有时间或者方法对它们一一验证,那么不建议进行升级。 + +如果是新系统,建议直接采用 QLExpress4,未来 QLExpress4 的生态建设会越来越完善,而 3 会被逐渐抛弃。 + +下面将列表新版和旧版的主要不同,方便用户对已有脚本进行升级。如有遗漏,欢迎反馈: + +=== 默认安全策略 + +如果完全使用默认选项,获取 Java 对象的字段(`o.field`),或者调用成员方法(`o.method()`),则会分别抛出 `FIELD_NOT_FOUND` 和 `METHOD_NOT_FOUND` 错误。 + +这是因为 3 可以没有限制地通过反射访问 Java 应用系统中的任意字段和方法,这在嵌入式脚本中被认为是不安全的。 + +如果想兼容 3 的行为,则在新建 `Express4Runner` 时, 要将安全策略设置为 “开放”,参考代码如下: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=securityStrategyOpen] +---- + +详细参考 link:#安全策略[安全策略] 章节。 + +=== 定义映射 + +QLExpress 老版本支持通过 `NewMap(key:value)` 的方式快速创建映射,虽然在文档中没有详细讨论,但是很多用户通过单元测试和询问的方式,知晓并使用了这个语法。 + +不过这种语法过于定制,也和业界的规范相差很大,因此在新版中将其移除。 + +新版原生支持 JSON 语法,直接采用 JSON 字典的格式(`{key:value}`)即可快速创建映射,更加直观。 + +详细参考 link:#方便语法元素[方便语法元素] + +=== 全局变量污染上下文 + +QLExpress 支持在执行脚本时传入一个全局的上下文,即 context 参数。 + +在老版本中,如果脚本中定义了全局变量,则这些变量也会写入到 context。在脚本执行结束后,可以通过 context 获取到脚本中定义的全局变量的值。 + +一个老版本的列子如下: + +[source,java] +---- +// only for QLExpress 3.x + +String express = "a=3;a+1"; +ExpressRunner runner = new ExpressRunner(false, true); +DefaultContext context = new DefaultContext<>(); + +Object res = runner.execute(express, context, null, true, true); +// The result of the script execution should be 4 (a+1) +Assert.assertEquals(4, res); +// The variable 'a' defined in the script is also stored in the context +Assert.assertEquals(3, context.get("a")); +---- + +根据调研和反馈,我们认为这会导致全局上下文被脚本 “污染”,存在安全性问题。 + +因此在 QLExpress4 中,全局变量默认不会写入到 context 中。 + +如果想要兼容 3 的特性,需要将 `polluteUserContext` 选项设置为 `true`,参考代码如下: + +[source,java,indent=0] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=polluteUserContext] +---- + +=== 分号可省略 + +"分号可省略" 已经是现代脚本语言的一个标配,QLExpress4 也跟进了这个特性,分号是可以省略的。 + +具体参考 link:#分号[分号] 章节。 + +=== 严格换行模式 + +由于 QLExpress4 支持分号可省略,解释器需要通过换行符来判断语句是否结束。因此,QLExpress4 对换行的要求比 QLExpress3 更加严格。 + +下面的脚本在 QLExpress3 中是合法的,但在 QLExpress4 中不是: + +[source,java] +---- +// 在 QLExpress3 中合法的脚本,但在 QLExpress4 中不是 +商家应收= + 价格 + - 饭卡商家承担 + + 平台补贴 +---- + +在 QLExpress4 中,上述脚本会被解析为两个独立的语句: +1. `商家应收 = 价格` +2. `- 饭卡商家承担 + 平台补贴`(第二个语句会报语法错误) + +如果要在 QLExpress4 中实现同样的效果,需要将操作符放在行尾,而不是行首: + +[source,java] +---- +// QLExpress4 中正确的写法 +商家应收= + 价格 - + 饭卡商家承担 + + 平台补贴 +---- + +这样解释器就知道当前行的表达式还未结束,会继续读取下一行。 + +如果您需要兼容 QLExpress3 的换行特性,可以设置 `strictNewLines` 选项为 `false`: + +[source,java] +---- +include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=notStrictNewLinesTest] +---- + +注意:非严格换行模式会让解释器忽略所有换行符,可能会影响代码的可读性和错误提示的准确性。建议仅在需要兼容旧代码时使用。 + +=== 获得 char 类型 + +在 QLExpress 3 中,单引号包裹的单个字符会被解析为 char 类型,而不是 String。 + +这个给用户带来了不少困惑,比如 `"a"=='a'` 的判断结果是 `false`。 + +所以后来 QLExpress 3 中新增了 `ExpressRunner.setIgnoreConstChar` 选项,设置为 `true` 后,所有的单引号和双引号包裹的字符都会被解析为 String 类型。但是这个选项默认是关闭的,需要用户手动开启。 + +考虑到脚本用户很少会使用到 `char` 这种底层类型,我们在 QLExpress 4 中直接取消了这个选项,所有的单引号和双引号包裹的字符都会被解析为 String 类型。 + +如果您在脚本还是需要使用 `char` 类型,可以通过两种方法获得: + +* 类型强转:`(char) 'a'` +* 类型声明:`char a = 'a'` + +== 附录二 如何贡献? + +QLExpress 对社区的更改完全开放,任何建议和修改,都会受到欢迎,讨论后合理最后会被接纳到主干中。 + +首先需要将代码 clone 到本地,在正式修改代码前,需要先进行如下准备: + +1. 项目根目录执行 `mvn compile`:项目刚刚下载到本地时,会有大量的类找不到,需要先生成 Antlr4 的运行时代码 +2. 配置代码格式化:QLExpress 项目有统一的代码格式规范,开发前需要配置在 git 提交前的自动格式化 + +在项目目录下新建文件 `.git/hooks/pre-commit`,内容如下: + +[source,bash] +---- +#!/bin/sh +mvn spotless:apply +git add -u +exit 0 +---- + +这样在每次 git commit 之前,就会自动执行 maven 的 spotless 插件执行代码格式化,具体代码格式配置见 link:spotless_eclipse_formatter.xml[] + +3. 执行单元测试:在开发完成代码之后,先在本地全量执行单元测试,确保代码质量 + - JDK 8 环境:执行 `mvn test` + - JDK 9 及以上环境:执行 `mvn test -DargLine="--add-opens java.base/java.util.stream=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED"` + +== 附录三 QLExpress4性能提升 + +link:https://www.yuque.com/xuanheng-ffjti/iunlps/pgfzw46zel2xfnie?singleDoc#%20%E3%80%8AQLExpress3%E4%B8%8E4%E6%80%A7%E8%83%BD%E5%AF%B9%E6%AF%94%E3%80%8B[QLExpress4与3性能对比] + +总结:常见场景下,无编译缓存时,QLExpress4能比3有接近10倍性能提升;有编译缓存,也有一倍性能提升。 + +== 附录四 开发者联系方式 + + * Email: + ** qinyuan.dqy@alibaba-inc.com + ** yumin.pym@taobao.com + ** 704643716@qq.com + * WeChat: + ** xuanheng: dqy932087612 + ** binggou: pymbupt + ** linxiang: tkk33362 + * DingTalk Support Group + +image::images/qlexpress_support_group_qr_2026.jpg[] diff --git a/README.adoc b/README.adoc new file mode 100644 index 0000000..0a2ebf9 --- /dev/null +++ b/README.adoc @@ -0,0 +1,1894 @@ +:toc: + += QLExpress + +image::images/logo.png[] + +【中文版】| link:README-EN.adoc[[English\]] + +image::https://api.star-history.com/svg?repos=alibaba/QLExpress&type=Date[Star History Chart] + +== 背景介绍 + +由阿里的电商业务规则演化而来的嵌入式Java动态脚本工具,在阿里集团有很强的影响力,同时为了自身不断优化、发扬开源贡献精神,于2012年开源。 + +在基本的表达式计算的基础上,还有以下特色: + +* 灵活的自定义能力,通过 Java API 自定义函数和操作符,可以快速实现业务规则的 DSL +* 兼容最新的 Java 语法,方便 Java 程序员快速熟悉。熟悉类 C 语言的业务人员使用起来也会非常顺手 +* 原生支持 JSON,快捷定义复杂数据结构 +* 友好的报错提示,无论是编译还是运行时错误,都能精确友好地提示错误位置 +* 独一无二的表达式追踪功能,可以追踪表达式在中间节点计算的值,方便业务人员或者 AI 对线上规则的计算结果进行归因分析 +* 默认安全,脚本默认不允许和应用代码进行交互,如果需要交互,也可以自行定义安全的交互方式 +* 解释执行,不占用 JVM 元空间,可以开启缓存提升解释性能 +* 代码精简,依赖最小,适合所有java的运行环境 + +QLExpress4 作为 QLExpress 的最新演进版本,基于 Antlr4 重写了解析引擎,将原先的优点进一步发扬光大,新增了大量特色功能,彻底拥抱函数式编程,在性能和表达能力上都进行了进一步增强。 + +> 如果项目还是使用旧版本的 QLExpress,可以跳转 link:https://github.com/alibaba/QLExpress/tree/branch_version_3.x.x[branch_version_3.x.x] 维护分支查看旧版文档。如需升级可以参考 link:#附录一-升级指南[附录一-升级指南] + +场景举例: + +* 电商优惠券规则配置:通过 QLExpress 自定义函数和操作符快速实现优惠规则 DSL,供运营人员根据需求自行动态配置 +* 表单搭建控件关联规则配置:表单搭建平台允许用户拖拽控件搭建自定义的表单,利用 QLExpress 脚本配置不同控件间的关联关系 +* 流程引擎条件规则配置 +* 广告系统计费规则配置 + +\...\... + +== 新版特色 + +新版本并不是对旧版本的简单功能重构,而是我们基于对用户需求的洞察,对下一代规则表达式引擎的探索。拥有许多非常实用,但是在其他引擎中缺失的重要功能。 + +=== 表达式计算追踪 + +在业务人员完成规则脚本的配置后,很难对其线上执行情况进行感知。比如电商的促销规则,要求用户满足规则 `isVip && 未登录10天以上`。到底有多少线上用户是被 vip 条件拦截,又有多少用户是因为登录条件被拦截?这还是只是仅仅两个条件的简单规则,实际线上情况则更加复杂。 + +线上规则执行情况的追踪,不仅仅可以帮助业务人员了解线上的实际情况,排查和修复问题。其沉淀的数据也非常有价值,可以用于后续的规则优化和业务决策。以下是某个规则平台,基于 QLExpress4 的表达式追踪能力,对规则进行归因分析与附注的决策的产品简化图: + +image::images/order_rules_cn.png[] + +归因分析的原理在于利用 QLExpress4 的表达式追踪能力,获得表达式在计算过程中每个中间结果的值, 据此判断表达式最终运行结果产生的原因。 + +具体使用方法参考:link:#表达式计算追踪-1[表达式计算追踪] + +=== 原生支持 JSON 语法 + +QLExpress4 原生支持 JSON 语法,可以快捷定义复杂的数据结构。 + +JSON 数组代表列表(List),而 JSON 对象代表映射(Map),也可以直接定义复杂对象。 + +产品上可以基于该特性实现 JSON 映射规则。让用户可以便捷地定义从一个模型向另一个模型的映射关系。以下是某个规则平台,基于该能力实现的模型映射产品简化图: + +image::images/json_map.png[] + +具体使用方法参考:link:#方便语法元素[方便语法元素] + +=== 便捷字符串处理 + +QLExpress4 对字符串处理能力进行针对性的增强,在字符串中可以直接通过 `$\{expression}` 嵌入表达式计算结果。 + +具体使用方法参考:link:#动态字符串[动态字符串] + +=== 附件透传 + +正常情况下,脚本执行需要的全部信息都在 `context` 中。context 中的 key 可以在脚本中作为变量引用,最终传递给自定义函数或者操作符。 + +但是出于安全,或者方便使用等因素考虑。有些信息并不希望用户通过变量引用到,比如租户名,密码等等。 + +此时可以通过附件(attachments)将这部分信息传递给自定义函数或者操作符使用。 + +具体使用方法参考:link:#添加自定义函数与操作符[添加自定义函数与操作符] 其中 `hello` 自定义函数根据附件中租户不同,返回不同的欢迎信息的示例。 + +=== 函数式编程 + +函数被提升为 QLExpress4 中的第一等公民,可以作为变量使用,也可以作为函数的返回值。并且可以很容易地和 Java 中常见的函数式 API(比如 Stream) 结合使用。 + +以下是一个简单的 QLExpress 示例脚本: + +[source,java] +---- +add = (a, b) -> { + return a + b; +}; +i = add(1,2); +assert(i == 3); +---- + +更多使用方法参考: + +* link:#lambda-表达式[Lambda表达式] +* link:#列表过滤和映射[列表过滤和映射] +* link:#stream-api[Stream API] +* link:#函数式接口[函数式接口] + +=== 分号简化 + +QLExpress4 支持省略分号,让表达式更加简洁。具体参考 link:#分号[分号] + +== API 快速入门 + +=== 引入依赖 + +[source,xml] +---- + + com.alibaba + qlexpress4 + 4.0.7 + +---- + +环境要求: + +* JDK 8 或更高版本 + +=== 第一个 QLExpress 程序 + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + Map context = new HashMap<>(); + context.put("a", 1); + context.put("b", 2); + context.put("c", 3); + Object result = express4Runner.execute("a + b * c", context, QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals(7, result); +---- + +更多的表达式执行方式见文档 link:docs/execute.adoc[表达式执行] + +=== 添加自定义函数与操作符 + +最简单的方式是通过 Java Lambda 表达式快速定义函数/操作符的逻辑: + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + // custom function + express4Runner.addVarArgsFunction("join", + params -> Arrays.stream(params).map(Object::toString).collect(Collectors.joining(","))); + Object resultFunction = + express4Runner.execute("join(1,2,3)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals("1,2,3", resultFunction); + + // custom operator + express4Runner.addOperatorBiFunction("join", (left, right) -> left + "," + right); + Object resultOperator = + express4Runner.execute("1 join 2 join 3", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals("1,2,3", resultOperator); +---- + +如果自定义函数的逻辑比较复杂,或者需要获得脚本的上下文信息,也可以通过继承 `CustomFunction` 的方式实现。 + +比如下面的 `hello` 自定义函数,根据租户不同,返回不同的欢迎信息: + +[source,java,indent=0] +---- +package com.alibaba.qlexpress4.test.function; + +import com.alibaba.qlexpress4.runtime.Parameters; +import com.alibaba.qlexpress4.runtime.QContext; +import com.alibaba.qlexpress4.runtime.function.CustomFunction; + +public class HelloFunction implements CustomFunction { + @Override + public Object call(QContext qContext, Parameters parameters) + throws Throwable { + String tenant = (String)qContext.attachment().get("tenant"); + return "hello," + tenant; + } +} +---- + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + express4Runner.addFunction("hello", new HelloFunction()); + String resultJack = (String)express4Runner.execute("hello()", + Collections.emptyMap(), + // Additional information(tenant for example) can be brought into the custom function from outside via attachments + QLOptions.builder().attachments(Collections.singletonMap("tenant", "jack")).build()).getResult(); + assertEquals("hello,jack", resultJack); + String resultLucy = + (String)express4Runner + .execute("hello()", + Collections.emptyMap(), + QLOptions.builder().attachments(Collections.singletonMap("tenant", "lucy")).build()) + .getResult(); + assertEquals("hello,lucy", resultLucy); +---- + +QLExpress4还支持通过QLExpress脚本添加自定义函数。需要注意的是,在函数外定义的变量(如示例中的defineTime)在函数定义时就已初始化完成,后续调用函数时不会重新计算该变量的值。 + +[source,java,indent=0] +---- + Express4Runner express4Runner = + new Express4Runner(InitOptions.builder().securityStrategy(QLSecurityStrategy.open()).build()); + BatchAddFunctionResult addResult = express4Runner.addFunctionsDefinedInScript( + "function myAdd(a,b) {\n" + " return a+b;" + "}\n" + "\n" + "function getCurrentTime() {\n" + + " return System.currentTimeMillis();\n" + "}" + "\n" + "defineTime=System.currentTimeMillis();\n" + + "function defineTime() {\n" + " return defineTime;" + "}\n", + ExpressContext.EMPTY_CONTEXT, + QLOptions.DEFAULT_OPTIONS); + assertEquals(3, addResult.getSucc().size()); + QLResult result = express4Runner.execute("myAdd(1,2)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS); + assertEquals(3, result.getResult()); + + QLResult resultCurTime1 = + express4Runner.execute("getCurrentTime()", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS); + Thread.sleep(1000); + QLResult resultCurTime2 = + express4Runner.execute("getCurrentTime()", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS); + assertNotSame(resultCurTime1.getResult(), resultCurTime2.getResult()); + + /* + * The defineTime variable is defined outside the function and is initialized when the function is defined; + * it is not recalculated afterward, so the value returned is always the time at which the function was defined. + */ + QLResult resultDefineTime1 = + express4Runner.execute("defineTime()", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS); + Thread.sleep(1000); + QLResult resultDefineTime2 = + express4Runner.execute("defineTime()", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS); + assertSame(resultDefineTime1.getResult(), resultDefineTime2.getResult()); +---- + +建议尽可能使用Java方式定义自定义函数,这样可以获得更好的性能和稳定性。 + +更多自定义语法元素的方式见文档 link:docs/custom-item.adoc[自定义语法元素] + +=== 校验语法正确性 + +在不执行脚本的情况下,单纯校验语法的正确性,其中包含了操作符的限制校验,调用 `check` 并且捕获异常,如果捕获到 `QLSyntaxException`,则说明存在语法错误 + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + try { + express4Runner.check("a+b;\n(a+b"); + fail(); + } + catch (QLSyntaxException e) { + assertEquals(2, e.getLineNo()); + assertEquals(4, e.getColNo()); + assertEquals("SYNTAX_ERROR", e.getErrorCode()); + // represents the end of script + assertEquals( + "[Error SYNTAX_ERROR: mismatched input '' expecting ')']\n" + "[Near: a+b; (a+b]\n" + + " ^^^^^\n" + "[Line: 2, Column: 4]", + e.getMessage()); + } +---- + +你可以使用 `CheckOptions` 配置更精细的语法校验规则,主要支持以下两个选项: + +1. `operatorCheckStrategy`: 操作符校验策略,用于限制脚本中可以使用的操作符 +2. `disableFunctionCalls`: 是否禁用函数调用,默认为 false + +示例1:使用操作符校验策略(白名单) + +[source,java,indent=0] +---- + // Create a whitelist of allowed operators + Set allowedOps = new HashSet<>(Arrays.asList("+", "*")); + + // Configure check options with operator whitelist + CheckOptions checkOptions = + CheckOptions.builder().operatorCheckStrategy(OperatorCheckStrategy.whitelist(allowedOps)).build(); + + // Create runner and check script with custom options + Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + runner.check("a + b * c", checkOptions); // This will pass as + and * are allowed +---- + +示例2:禁用函数调用 + +[source,java,indent=0] +---- + // Create a runner + Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + + // Create options with function calls disabled + CheckOptions options = CheckOptions.builder().disableFunctionCalls(true).build(); + + // Script with function call + String scriptWithFunctionCall = "Math.max(1, 2)"; + + // Use custom options to check script + try { + runner.check(scriptWithFunctionCall, options); + } + catch (QLSyntaxException e) { + // Will throw exception as function calls are disabled + } +---- + +=== 解析脚本所需外部变量 + +脚本中使用的变量有的是脚本内生,有的是需要从外部通过 `context` 传入的。 + +QLExpress4 提供了一个方法,可以解析出脚本中所有需要从外部传入的变量: + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + Set outVarNames = + express4Runner.getOutVarNames("int a = 1, b = 10;\n" + "c = 11\n" + "e = a + b + c + d\n" + "f+e"); + Set expectSet = new HashSet<>(); + expectSet.add("d"); + expectSet.add("f"); + assertEquals(expectSet, outVarNames); +---- + +更多脚本依赖解析工具: + +* `getOutFunctions`: 解析所有需要从外部定义的函数 +* `getOutVarAttrs`:解析所有需要从外部传入变量及其涉及的属性,`getOutVarNames` 的增强版本 + +=== 高精度计算 + +QLExpress 内部会用 BigDecimal 表示所有无法用 double 精确表示数字,来尽可能地表示计算精度: + +> 举例:0.1 在 double 中无法精确表示 + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + Object result = express4Runner.execute("0.1", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertTrue(result instanceof BigDecimal); +---- + +通过这种方式能够解决一些计算精度问题: + +比如 0.1+0.2 因为精度问题,在 Java 中是不等于 0.3 的。 +而 QLExpress 能够自动识别出 0.1 和 0.2 无法用双精度精确表示,改成用 BigDecimal 表示,确保其结果等于0.3 + +[source,java,indent=0] +---- + assertNotEquals(0.3, 0.1 + 0.2, 0.0); + assertTrue((Boolean)express4Runner.execute("0.3==0.1+0.2", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS) + .getResult()); +---- + +除了默认的精度保证外,还提供了 `precise` 开关,打开后所有的计算都使用BigDecimal,防止外部传入的低精度数字导致的问题: + +[source,java,indent=0] +---- + Map context = new HashMap<>(); + context.put("a", 0.1); + context.put("b", 0.2); + assertFalse((Boolean)express4Runner.execute("0.3==a+b", context, QLOptions.DEFAULT_OPTIONS).getResult()); + // open precise switch + assertTrue((Boolean)express4Runner.execute("0.3==a+b", context, QLOptions.builder().precise(true).build()) + .getResult()); +---- + +=== 安全策略 + +QLExpress4 默认采用隔离安全策略,不允许脚本访问 Java 对象的字段和方法,这确保了脚本执行的安全性。如果需要访问 Java 对象,可以通过不同的安全策略进行配置。 + +假设应用中有如下的 Java 类: + +[source,java,indent=0] +---- +package com.alibaba.qlexpress4.inport; + +/** + * Author: DQinYuan + */ +public class MyDesk { + + private String book1; + + private String book2; + + public String getBook1() { + return book1; + } + + public void setBook1(String book1) { + this.book1 = book1; + } + + public String getBook2() { + return book2; + } + + public void setBook2(String book2) { + this.book2 = book2; + } +} +---- + +脚本执行的上下文设置如下: + +[source,java,indent=0] +---- + MyDesk desk = new MyDesk(); + desk.setBook1("Thinking in Java"); + desk.setBook2("Effective Java"); + Map context = Collections.singletonMap("desk", desk); +---- + +QLExpress4 提供了四种安全策略: + +==== 1. 隔离策略(默认) + +默认情况下,QLExpress4 采用隔离策略,不允许访问任何字段和方法: + +[source,java,indent=0] +---- + // default isolation strategy, no field or method can be found + Express4Runner express4RunnerIsolation = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + assertErrorCode(express4RunnerIsolation, context, "desk.book1", "FIELD_NOT_FOUND"); + assertErrorCode(express4RunnerIsolation, context, "desk.getBook2()", "METHOD_NOT_FOUND"); +---- + +==== 2. 黑名单策略 + +通过黑名单策略,可以禁止访问特定的字段或方法,其他字段和方法可以正常访问: + +[source,java,indent=0] +---- + // black list security strategy + Set memberList = new HashSet<>(); + memberList.add(MyDesk.class.getMethod("getBook2")); + Express4Runner express4RunnerBlackList = new Express4Runner( + InitOptions.builder().securityStrategy(QLSecurityStrategy.blackList(memberList)).build()); + assertErrorCode(express4RunnerBlackList, context, "desk.book2", "FIELD_NOT_FOUND"); + Object resultBlack = + express4RunnerBlackList.execute("desk.book1", context, QLOptions.DEFAULT_OPTIONS).getResult(); + Assert.assertEquals("Thinking in Java", resultBlack); +---- + +==== 3. 白名单策略 + +通过白名单策略,只允许访问指定的字段或方法,其他字段和方法都会被禁止: + +[source,java,indent=0] +---- + // white list security strategy + Express4Runner express4RunnerWhiteList = new Express4Runner( + InitOptions.builder().securityStrategy(QLSecurityStrategy.whiteList(memberList)).build()); + Object resultWhite = + express4RunnerWhiteList.execute("desk.getBook2()", context, QLOptions.DEFAULT_OPTIONS).getResult(); + Assert.assertEquals("Effective Java", resultWhite); + assertErrorCode(express4RunnerWhiteList, context, "desk.getBook1()", "METHOD_NOT_FOUND"); +---- + +==== 4. 开放策略 + +开放策略允许访问所有字段和方法,类似于 QLExpress3 的行为,但需要注意安全风险: + +[source,java,indent=0] +---- + // open security strategy + Express4Runner express4RunnerOpen = + new Express4Runner(InitOptions.builder().securityStrategy(QLSecurityStrategy.open()).build()); + Assert.assertEquals("Thinking in Java", + express4RunnerOpen.execute("desk.book1", context, QLOptions.DEFAULT_OPTIONS).getResult()); + Assert.assertEquals("Effective Java", + express4RunnerOpen.execute("desk.getBook2()", context, QLOptions.DEFAULT_OPTIONS).getResult()); +---- + +> 注意:开放策略虽然提供了最大的灵活性,但也带来了安全风险。建议只在受信任的环境中使用,不建议用于处理终端用户输入的脚本。 + +==== 策略建议 + +建议直接采用默认策略,在脚本中不要直接调用 Java 对象的字段和方法。而是通过自定义函数和操作符的方式(参考 link:#添加自定义函数与操作符[添加自定义函数与操作符]),对嵌入式脚本提供系统功能。这样能同时保证脚本的安全性和灵活性,用户体验还更好。 + +如果确实需要调用 Java 对象的字段和方法,至少应该使用白名单策略,只提供脚本有限的访问权限。 + +至于黑名单和开放策略,不建议在外部输入脚本的场景使用,除非确保每个脚本都会经过审核。 + +=== 调用应用中的 Java 类 + +> 需要放开安全策略,不建议用于终端用户输入 + +假设应用中有如下的 Java 类(`com.alibaba.qlexpress4.QLImportTester`): + +[source,java,indent=0] +---- +package com.alibaba.qlexpress4; + +public class QLImportTester { + + public static int add(int a, int b) { + return a + b; + } + +} +---- + +在 QLExpress 中有如下两种调用方式。 + +==== 1. 在脚本中使用 `import` 语句导入类并且使用 + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.builder() + // open security strategy, which allows access to all Java classes within the application. + .securityStrategy(QLSecurityStrategy.open()) + .build()); + // Import Java classes using the import statement. + Map params = new HashMap<>(); + params.put("a", 1); + params.put("b", 2); + Object result = + express4Runner + .execute("import com.alibaba.qlexpress4.QLImportTester;" + "QLImportTester.add(a,b)", + params, + QLOptions.DEFAULT_OPTIONS) + .getResult(); + Assert.assertEquals(3, result); +---- + +==== 2. 在创建 `Express4Runner` 时默认导入该类,此时脚本中就不需要额外的 `import` 语句 + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.builder() + .addDefaultImport( + Collections.singletonList(ImportManager.importCls("com.alibaba.qlexpress4.QLImportTester"))) + .securityStrategy(QLSecurityStrategy.open()) + .build()); + Object result = + express4Runner.execute("QLImportTester.add(1,2)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS) + .getResult(); + Assert.assertEquals(3, result); +---- + +除了用 `ImportManager.importCls` 导入单个类外,还有其他更方便的导入方式: + + * `ImportManager.importPack` 直接导入包路径下的所有类,比如 `ImportManager.importPack("java.util")` 会导入 `java.util` 包下的所有类,QLExpress 默认就会导入下面的包 + ** `ImportManager.importPack("java.lang")` + ** `ImportManager.importPack("java.util")` + ** `ImportManager.importPack("java.math")` + ** `ImportManager.importPack("java.util.stream")` + ** `ImportManager.importPack("java.util.function")` + * `ImportManager.importInnerCls` 导入给定类路径里的所有内部类 + +=== 自定义 ClassLoader + +QLExpress4 支持通过自定义 `ClassSupplier` 来指定类加载器,这在插件化架构、模块化应用等场景中非常有用。通过自定义类加载器,可以让 QLExpress 脚本访问特定 ClassLoader 中的类。 + +下面的示例展示了如何与 link:https://pf4j.org/[PF4J] 插件框架集成,让 QLExpress 脚本能够访问插件中的类: + +[source,java,indent=0] +---- + // Specify plugin directory (test-plugins directory under test resources) + Path pluginsDir = new File("src/test/resources/test-plugins").toPath(); + PluginManager pluginManager = new DefaultPluginManager(pluginsDir); + pluginManager.loadPlugins(); + pluginManager.startPlugins(); + + // Get the PluginClassLoader of the first plugin + PluginWrapper plugin = pluginManager.getPlugins().get(0); + ClassLoader pluginClassLoader = plugin.getPluginClassLoader(); + + // Custom class supplier using plugin ClassLoader + ClassSupplier pluginClassSupplier = clsName -> { + try { + return Class.forName(clsName, true, pluginClassLoader); + } + catch (ClassNotFoundException | NoClassDefFoundError e) { + return null; + } + }; + + InitOptions options = InitOptions.builder() + .securityStrategy(QLSecurityStrategy.open()) + .classSupplier(pluginClassSupplier) + .build(); + Express4Runner runner = new Express4Runner(options); + + String script = "import com.alibaba.qlexpress4.pf4j.TestPluginInterface; TestPluginInterface.TEST_CONSTANT"; + Object result = runner.execute(script, Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); + + Assert.assertEquals("Hello from PF4J Plugin!", result.toString()); +---- + +自定义 ClassSupplier 的典型应用场景: + +* **插件化架构**:让脚本能够访问插件中定义的类和接口 +* **模块化应用**:在 OSGi 等模块化框架中,让脚本访问特定模块的类 +* **动态类加载**:从远程仓库或动态生成的字节码中加载类 +* **类隔离**:使用不同的 ClassLoader 来实现类的隔离 + +=== 表达式缓存 + +通过 `cache` 选项可以开启表达式缓存,这样相同的表达式就不会重新编译,能够大大提升性能。 + +注意该缓存没有限制大小,只适合在表达式为有限数量的情况下使用: + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + // open cache switch + express4Runner.execute("1+2", new HashMap<>(), QLOptions.builder().cache(true).build()); +---- + +但是当脚本首次执行时,因为没有缓存,依旧会比较慢。 + +可以通过下面的方法在首次执行前就将脚本缓存起来,保证首次执行的速度: + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + express4Runner.parseToDefinitionWithCache("a+b"); +---- + +注意该缓存的大小是无限的,业务上注意控制大小,可以调用 `clearCompileCache` 方法定期清空编译缓存。 + +=== 清除 DFA 缓存 + +QLExpress 使用 ANTLR4 作为解析引擎,ANTLR4 在运行时会构建 DFA (确定有限状态自动机) 缓存来加速后续的语法解析。这个缓存会占用一定的内存空间。 + +在某些内存敏感的场景下,可以通过调用 `clearDFACache` 方法来清除 DFA 缓存,释放内存。 + +> **重要警告**: 清除 DFA 缓存会导致编译性能大幅下降,正常情况下不建议使用此方法。 + +==== 适用场景 + +* **内存敏感型应用**: 当内存使用是关键考虑因素,且可以容忍较慢的编译时间时 +* **脚本不经常变更**: 当脚本相对稳定且不会频繁重新编译时 + +==== 最佳实践 + +在解析并缓存表达式后立即调用此方法,并确保后续所有执行都打开缓存选项以避免重新编译。示例代码如下: + +[source,java,indent=0] +---- + /* + * 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(exampleExpress); + 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(exampleExpress, ExpressContext.EMPTY_CONTEXT, QLOptions.builder().cache(true).build()); + } +---- + +通过这种方式,可以在保证性能的同时,最大限度地降低内存占用。 + +=== 设置超时时间 + +可以给脚本设置一个超时时间,防止其中存在死循环或者其他原因导致应用资源被过量消耗。 + +下面的示例代码给脚本给脚本设置了一个 10ms 的超时时间: + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + try { + express4Runner.execute("while (true) {\n" + " 1+1\n" + "}", + Collections.emptyMap(), + QLOptions.builder().timeoutMillis(10L).build()); + fail("should timeout"); + } + catch (QLTimeoutException e) { + assertEquals(QLErrorCodes.SCRIPT_TIME_OUT.name(), e.getErrorCode()); + } +---- + +> 注意,出于系统性能的考虑,QLExpress 对于超时时间的检测是不准确的。特别是在回调Java代码中(比如自定义函数或者操作符)出现的超时,不会立刻被检测到。只有在执行完,回到 QLExpress 运行时后才会被检测到并中断执行。 + +=== 扩展函数 + +利用 QLExpress 提供的扩展函数能力,可以给Java类中添加额外的成员方法。 + +扩展函数是基于 QLExpress 运行时实现的,因此仅仅在 QLExpress 脚本中有效。 + +下面的示例代码给 String 类添加了一个 `hello()` 扩展函数: + +[source,java,indent=0] +---- + ExtensionFunction helloFunction = new ExtensionFunction() { + @Override + public Class[] getParameterTypes() { + return new Class[0]; + } + + @Override + public String getName() { + return "hello"; + } + + @Override + public Class getDeclaringClass() { + return String.class; + } + + @Override + public Object invoke(Object obj, Object[] args) { + String originStr = (String)obj; + return "Hello," + originStr; + } + }; + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + express4Runner.addExtendFunction(helloFunction); + Object result = + express4Runner.execute("'jack'.hello()", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals("Hello,jack", result); + + // simpler way to define extension function + express4Runner.addExtendFunction("add", + Number.class, + params -> ((Number)params[0]).intValue() + ((Number)params[1]).intValue()); + QLResult resultAdd = express4Runner.execute("1.add(2)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS); + assertEquals(3, resultAdd.getResult()); + +---- + +=== Java类的对象,字段和方法别名 + +QLExpress 支持通过 `QLAlias` 注解给对象,字段或者方法定义一个或多个别名,方便非技术人员使用表达式定义规则。 + +下面的例子中,根据用户是否 vip 计算订单最终金额。 + +用户类定义: + +[source,java,indent=0] +---- +package com.alibaba.qlexpress4.test.qlalias; + +import com.alibaba.qlexpress4.annotation.QLAlias; + +@QLAlias("用户") +public class User { + + @QLAlias("是vip") + private boolean vip; + + @QLAlias("用户名") + private String name; + + public boolean isVip() { + return vip; + } + + public void setVip(boolean vip) { + this.vip = vip; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} +---- + +订单类定义: + +[source,java,indent=0] +---- +package com.alibaba.qlexpress4.test.qlalias; + +import com.alibaba.qlexpress4.annotation.QLAlias; + +@QLAlias("订单") +public class Order { + + @QLAlias("订单号") + private String orderNum; + + @QLAlias("金额") + private int amount; + + public String getOrderNum() { + return orderNum; + } + + public void setOrderNum(String orderNum) { + this.orderNum = orderNum; + } + + public int getAmount() { + return amount; + } + + public void setAmount(int amount) { + this.amount = amount; + } +} +---- + +通过 QLExpress 脚本规则计算最终订单金额: + +[source,java,indent=0] +---- + Order order = new Order(); + order.setOrderNum("OR123455"); + order.setAmount(100); + + User user = new User(); + user.setName("jack"); + user.setVip(true); + + // Calculate the Final Order Amount + Express4Runner express4Runner = + new Express4Runner(InitOptions.builder().securityStrategy(QLSecurityStrategy.open()).build()); + Number result = (Number)express4Runner + .executeWithAliasObjects("用户.是vip? 订单.金额 * 0.8 : 订单.金额", QLOptions.DEFAULT_OPTIONS, order, user) + .getResult(); + assertEquals(80, result.intValue()); +---- + +=== 关键字,操作符和函数别名 + +为了进一步方面非技术人员编写规则,QLExpress 提供 `addAlias` 给原始关键字,操作符和函数增加别名。让整个脚本的表述更加贴近自然语言。 + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + // add custom function zero + express4Runner.addFunction("zero", (String ignore) -> 0); + + // keyword alias + assertTrue(express4Runner.addAlias("如果", "if")); + assertTrue(express4Runner.addAlias("则", "then")); + assertTrue(express4Runner.addAlias("否则", "else")); + assertTrue(express4Runner.addAlias("返回", "return")); + // operator alias + assertTrue(express4Runner.addAlias("大于", ">")); + // function alias + assertTrue(express4Runner.addAlias("零", "zero")); + + Map context = new HashMap<>(); + context.put("语文", 90); + context.put("数学", 90); + context.put("英语", 90); + + Object result = express4Runner + .execute("如果 (语文 + 数学 + 英语 大于 270) 则 {返回 1;} 否则 {返回 零();}", context, QLOptions.DEFAULT_OPTIONS) + .getResult(); + assertEquals(0, result); +---- + +支持设置别名的关键字有: + + * if + * then + * else + * for + * while + * break + * continue + * return + * function + * macro + * new + * null + * true + * false + +> 注意:部分大家熟悉的用法其实是操作符,而不是关键字,比如 `in` 操作符。而所有的操作符和函数默认就是支持别名的 + +=== 宏 + +宏是QLExpress中一个强大的代码复用机制,它允许用户定义一段可重用的脚本片段,并在需要时进行调用。与简单的文本替换不同,QLExpress的宏是基于指令回放的机制实现的,具有更好的性能和语义准确性。 + +宏特别适用于以下场景: + +* **代码复用**:将常用的脚本片段封装成宏,避免重复编写相同的逻辑 +* **业务规则模板**:定义标准的业务规则模板,如价格计算、权限检查等 +* **流程控制**:封装复杂的控制流程,如条件判断、循环逻辑等 +* **DSL构建**:作为构建领域特定语言的基础组件 + +宏可以通过两种方式定义: + +**1. 在脚本中使用 `macro` 关键字定义** + +[source,java] +---- +macro add { + c = a + b; +} + +a = 1; +b = 2; +add; +assert(c == 3); +---- + +**2. 通过Java API添加** + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + express4Runner.addMacro("rename", "name='haha-'+name"); + Map context = Collections.singletonMap("name", "wuli"); + Object result = express4Runner.execute("rename", context, QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals("haha-wuli", result); + + // replace macro + express4Runner.addOrReplaceMacro("rename", "name='huhu-'+name"); + Object result1 = express4Runner.execute("rename", context, QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals("huhu-wuli", result1); +---- + +宏与函数的区别: + +[cols="1,1,1"] +|=== +| 特性 | 宏 | 函数 +| 参数传递 | 无参数,依赖上下文变量 | 支持参数传递 +| 性能 | 指令直接插入,无调用开销 | 有函数调用开销 +| 作用域 | 共享调用者作用域 | 独立的作用域 +| 适用场景 | 代码片段复用 | 逻辑封装和参数化 +|=== + +宏特别适合那些不需要参数传递、主要依赖上下文变量的代码片段复用场景,而函数更适合需要参数化和独立作用域的场景。 + +**QLExpress4 相比 3 版本,宏特性的变化**: + + * 4 的宏实现更加接近通常编程语言中宏的定义,相当于将预定义的代码片段插入到宏所在的位置,与调用点位于同一作用域,宏中的 `return`, `contine` 和 `break` 等可以影响调用方的控制流。但是 3 中的实现其实更加接近无参函数调用。 + * 4 的宏无法作为变量使用,只有单独作为一行语句时才能被宏替换。因为宏可以是任意脚本,不一定是有返回值的表达式,作为变量时会存在语义问题。3 的宏本质是一个无参函数调用,所以常常被作为变量使用 + +如果想兼容 3 中的宏特性,建议使用 link:#动态变量[动态变量] + +=== 动态变量 + +常规的 “静态变量”,是 context 中和 key 关联的固定的值。而动态变量可以是一个表达式,由另外一些变量计算而得。动态变量支持嵌套,即动态变量可以依赖另一个动态变量计算得到。 + +示例如下: + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + + Map staticContext = new HashMap<>(); + staticContext.put("语文", 88); + staticContext.put("数学", 99); + staticContext.put("英语", 95); + + QLOptions defaultOptions = QLOptions.DEFAULT_OPTIONS; + DynamicVariableContext dynamicContext = + new DynamicVariableContext(express4Runner, staticContext, defaultOptions); + dynamicContext.put("平均成绩", "(语文+数学+英语)/3.0"); + dynamicContext.put("是否优秀", "平均成绩>90"); + + // dynamic var + assertTrue((Boolean)express4Runner.execute("是否优秀", dynamicContext, defaultOptions).getResult()); + assertEquals(94, + ((Number)express4Runner.execute("平均成绩", dynamicContext, defaultOptions).getResult()).intValue()); + // static var + assertEquals(187, + ((Number)express4Runner.execute("语文+数学", dynamicContext, defaultOptions).getResult()).intValue()); +---- + +=== 表达式计算追踪 + +如果打开相关选项,QLExpress4 就会在返回规则脚本计算结果的同时,返回一颗表达式追踪树。表达式追踪树的结构类似语法树,不同之处在于,它会在每个节点上记录本次执行的中间结果。 + +比如对于表达式 `!true || myTest(a, 1)`,表达式追踪树的结构大概如下: + +[source] +---- + || true + / \ + ! false myTest + / / \ + true a 10 1 +---- + +可应用于多种场景: + + * 方便业务人员对规则的计算结果进行分析排查 + * 对线上判断为 false 的规则进行采样归类 + * AI 自动诊断和修复规则 + +节点计算结果会被放置到 `ExpressionTrace` 对象的 `value` 字段中。如果中间发生短路导致部分表达式未被计算,则 `ExpressionTrace` 对象的 `evaluated` 字段会被设置为 false。代码示例如下: + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.builder().traceExpression(true).build()); + express4Runner.addFunction("myTest", (Predicate)i -> i > 10); + + Map context = new HashMap<>(); + context.put("a", true); + QLResult result = express4Runner + .execute("a && (!myTest(11) || false)", context, QLOptions.builder().traceExpression(true).build()); + Assert.assertFalse((Boolean)result.getResult()); + + List expressionTraces = result.getExpressionTraces(); + Assert.assertEquals(1, expressionTraces.size()); + ExpressionTrace expressionTrace = expressionTraces.get(0); + Assert.assertEquals("OPERATOR && false\n" + " | VARIABLE a true\n" + " | OPERATOR || false\n" + + " | OPERATOR ! false\n" + " | FUNCTION myTest true\n" + " | VALUE 11 11\n" + + " | VALUE false false\n", expressionTrace.toPrettyString(0)); + + // short circuit + context.put("a", false); + QLResult resultShortCircuit = express4Runner.execute("(a && true) && (!myTest(11) || false)", + context, + QLOptions.builder().traceExpression(true).build()); + Assert.assertFalse((Boolean)resultShortCircuit.getResult()); + ExpressionTrace expressionTraceShortCircuit = resultShortCircuit.getExpressionTraces().get(0); + Assert.assertEquals( + "OPERATOR && false\n" + " | OPERATOR && false\n" + " | VARIABLE a false\n" + " | VALUE true \n" + + " | OPERATOR || \n" + " | OPERATOR ! \n" + " | FUNCTION myTest \n" + + " | VALUE 11 \n" + " | VALUE false \n", + expressionTraceShortCircuit.toPrettyString(0)); + Assert.assertTrue(expressionTraceShortCircuit.getChildren().get(0).isEvaluated()); + Assert.assertFalse(expressionTraceShortCircuit.getChildren().get(1).isEvaluated()); + + // in + QLResult resultIn = express4Runner + .execute("'ab' in ['cc', 'dd', 'ff']", context, QLOptions.builder().traceExpression(true).build()); + Assert.assertFalse((Boolean)resultIn.getResult()); + ExpressionTrace expressionTraceIn = resultIn.getExpressionTraces().get(0); + Assert + .assertEquals( + "OPERATOR in false\n" + " | VALUE 'ab' ab\n" + " | LIST [ [cc, dd, ff]\n" + " | VALUE 'cc' cc\n" + + " | VALUE 'dd' dd\n" + " | VALUE 'ff' ff\n", + expressionTraceIn.toPrettyString(0)); +---- + +> 注意,必须在新建 `Express4Runner` 时将 `InitOptions.traceExpression` 选项设置为 true,同时在执行脚本时将 `QLOptions.traceExpression` 设置为 true,该功能才能生效。 + +也可以在不执行脚本的情况下获得所有表达式追踪点: + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + TracePointTree tracePointTree = express4Runner.getExpressionTracePoints("1+3+5*ab+9").get(0); + Assert.assertEquals("OPERATOR +\n" + " | OPERATOR +\n" + " | OPERATOR +\n" + " | VALUE 1\n" + + " | VALUE 3\n" + " | OPERATOR *\n" + " | VALUE 5\n" + " | VARIABLE ab\n" + + " | VALUE 9\n", tracePointTree.toPrettyString(0)); +---- + +支持的表达式追踪点类型以及对应子节点的含义如下: + +[cols="1,1,1"] +|=== +| 节点类型 | 节点含义 | 子节点含义 +| OPERATOR | 操作符 | 两侧操作数 +| FUNCTION | 函数 | 函数参数 +| METHOD | 方法 | 方法参数 +| FIELD | 字段 | 取字段的目标对象 +| LIST | 列表 | 列表元素 +| MAP | 字段 | 无 +| IF | 条件分支 | condition表达式,then逻辑块和else逻辑块 +| RETURN | 返回语句 | 返回表达式 +| VARIABLE | 变量 | 无 +| VALUE | 字面值 | 无 +| DEFINE_FUNCTION | 定义函数 | 无 +| DEFINE_MACRO | 定义宏 | 无 +| PRIMARY | 暂时未继续下钻的其他复合值(比如字典,if等等)| 无 +| STATEMENT | 暂未继续下钻的其他复合语句(比如 while, for 等等)| 无 +|=== + +=== 与 Spring 集成 + +QLExpress 并不需要专门与 Spring 集成,只需要一个 `Express4Runner` 单例,即可使用。 + +这里提供的 “集成” 示例,可以在 QLExpress 脚本中直接引用任意 Spring Bean。 + +这种方式虽然很方便,但是脚本权限过大,自由度太高。不再推荐使用,还是建议在 context 只放入允许用户访问的对象。 + +核心集成组件: + +* link:src/test/java/com/alibaba/qlexpress4/spring/QLSpringContext.java[QLSpringContext]: 实现了 `ExpressContext` 接口,提供了对 Spring 容器的访问能力。它会优先从传入的 context 中查找变量,如果找不到则尝试从 Spring 容器中获取同名的 Bean。 +* link:src/test/java/com/alibaba/qlexpress4/spring/QLExecuteService.java[QLExecuteService]: 封装了 QLExpress 的执行逻辑,集成了 Spring 容器,方便在 Spring 应用中使用。 + +假设存在一个 Spring Bean, 名为 `helloService`: + +[source,java,indent=0] +---- +package com.alibaba.qlexpress4.spring; + +import org.springframework.stereotype.Service; + +/** + * Spring Bean example service class + */ +@Service +public class HelloService { + + /** + * Hello method that returns a greeting string + * @return greeting string + */ + public String hello(String name) { + return "Hello, " + name + "!"; + } +} +---- + +在脚本中调用该 Bean: + +[source,java,indent=0] +---- +package com.alibaba.qlexpress4.spring; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import java.util.HashMap; +import java.util.Map; + +/** + * HelloService unit test class + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = SpringTestConfig.class) +public class SpringDemoTest { + + @Autowired + private QLExecuteService qlExecuteService; + + @Test + public void qlExecuteWithSpringContextTest() { + Map context = new HashMap<>(); + context.put("name", "Wang"); + String result = (String)qlExecuteService.execute("helloService.hello(name)", context); + Assert.assertEquals("Hello, Wang!", result); + } +} +---- + + +== 语法入门 + +QLExpress4 兼容 Java8 语法的同时,也提供了很多更加灵活宽松的语法模式,帮助用户更快捷地编写表达式。 + +基于表达式优先的语法设计,复杂的条件判断语句也可以直接当作表达式使用。 + +在本章节中出现的代码片段都是 qlexpress 脚本, +`assert` 是测试框架往引擎中注入的断言方法,会确保其参数为 `true`。 +`assertErrCode` 会确保其 lambda 参数表达式的执行一定会抛出含第二个参数 error code 的 QLException。 + +=== 变量声明 + +同时支持静态类型和动态类型: + + * 变量声明时不写类型,则变量是动态类型,也同时是一个赋值表达式 + * 变量声明如果写类型,则是静态类型,此时是一个变量声明语句 + +[source,java] +---- +// Dynamic Typeing +a = 1; +a = "1"; +// Static Typing +int b = 2; +// throw QLException with error code INCOMPATIBLE_ASSIGNMENT_TYPE when assign with incompatible type String +assertErrorCode(() -> b = "1", "INCOMPATIBLE_ASSIGNMENT_TYPE") + +---- + +=== 方便语法元素 + +列表(List),映射(Map)等常用语法元素在 QLExpress 中都有非常方便的构造语法糖: + +[source,java] +---- +// list +l = [1,2,3] +assert(l[0]==1) +assert(l[-1]==3) +// Underlying data type of list is ArrayList in Java +assert(l instanceof ArrayList) +// map +m = { + "aa": 10, + "bb": { + "cc": "cc1", + "dd": "dd1" + } +} +assert(m['aa']==10) +// Underlying data type of map is ArrayList in Java +assert(m instanceof LinkedHashMap) +// empty map +emMap = {:} +emMap['haha']='huhu' +assert(emMap['haha']=='huhu') +---- + +通过 `*.` 操作符,可以快捷地对列表和映射进行处理,比如对列表元素进行取属性,或者获得映射的 key 列表和 value 列表: + +[source,java] +---- +list = [ + { + "name": "Li", + "age": 10 + }, + { + "name": "Wang", + "age": 15 + } +] + +// get field from list +assert(list*.age==[10,15]) + +mm = { + "aaa": 1, + "bbb": 2 +} + +// get map key value list +assert(mm*.key==["aaa", "bbb"]) +assert(mm*.value==[1, 2]) +---- + +在映射中通过 `@class` key 指定类型的全限定名,就可以直接使用 JSON 创建复杂Java对象。比如下面的 MyHome , 是一个含有复杂嵌套类型 Java 类: + +[source,java] +---- +package com.alibaba.qlexpress4.inport; + +/** + * Author: DQinYuan + */ +public class MyHome { + + private String sofa; + + private String chair; + + private MyDesk myDesk; + + private String bed; + + public String getSofa() { + return sofa; + } + + public void setSofa(String sofa) { + this.sofa = sofa; + } + + public String getChair() { + return chair; + } + + public MyDesk getMyDesk() { + return myDesk; + } + + public void setMyDesk(MyDesk myDesk) { + this.myDesk = myDesk; + } + + public void setChair(String chair) { + this.chair = chair; + } + + public String getBed() { + return bed; + } +} +---- + +可以通过下面的 QLExpress 脚本,便捷创建: + +> 注意,该特性需要参考 link:#安全策略[安全策略] 打开安全选项,才能正常执行。 + +[source,java] +---- +myHome = { + '@class': 'com.alibaba.qlexpress4.inport.MyHome', + 'sofa': 'a-sofa', + 'chair': 'b-chair', + 'myDesk': { + 'book1': 'Then Moon and Sixpence', + '@class': 'com.alibaba.qlexpress4.inport.MyDesk' + }, + // ignore field that don't exist + 'notexist': 1234 +} +assert(myHome.getSofa()=='a-sofa') +assert(myHome instanceof com.alibaba.qlexpress4.inport.MyHome) +assert(myHome.getMyDesk().getBook1()=='Then Moon and Sixpence') +assert(myHome.getMyDesk() instanceof com.alibaba.qlexpress4.inport.MyDesk) +---- + +=== 数字 + +对于未声明类型的数字, +QLExpress会根据其所属范围自动从 int, long, BigInteger, double, BigDecimal 等数据类型中选择一个最合适的: + +[source,java] +---- +assert(2147483647 instanceof Integer); +assert(9223372036854775807 instanceof Long); +assert(18446744073709552000 instanceof BigInteger); +// 0.25 can be precisely presented with double +assert(0.25 instanceof Double); +assert(2.7976931348623157E308 instanceof BigDecimal); +---- + +因此在自定义函数或者操作符时,建议使用 Number 类型进行接收,因为数字类型是无法事先确定的。 + +=== 动态字符串 + +动态字符串是 QLExpress 为了增强字符串处理能力,在 4 版本新引入的能力。 + +支持 `$\{expression}` 的格式在字符串中插入表达式计算: + +> 如果想在字符串中原样保持 `$\{expression}`,可以使用 `\$` 对 `$` 进行转义 + +[source,java] +---- +a = 123 +assert("hello,${a-1}" == "hello,122") + +// escape $ with \$ +assert("hello,\${a-1}" == "hello,\${a-1}") + +b = "test" +assert("m xx ${ + if (b like 't%') { + 'YYY' + } +}" == "m xx YYY") +---- + +如果还想让 QLExpress4 的字符串和 3 保持兼容性,不对插值表达式进行处理,可以在新建 `Express4Runner` 时直接关闭该特性: + +[source,java] +---- + Express4Runner express4RunnerDisable = new Express4Runner( + // disable string interpolation + InitOptions.builder().interpolationMode(InterpolationMode.DISABLE).build()); + Assert.assertEquals("Hello,${ a + 1 }", + express4RunnerDisable.execute("\"Hello,${ a + 1 }\"", context, QLOptions.DEFAULT_OPTIONS).getResult()); + Assert.assertEquals("Hello,${lll", + express4RunnerDisable.execute("\"Hello,${lll\"", context, QLOptions.DEFAULT_OPTIONS).getResult()); + Assert.assertEquals("Hello,aaa $ lll\"\n\b", + express4RunnerDisable.execute("\"Hello,aaa $ lll\\\"\n\b\"", context, QLOptions.DEFAULT_OPTIONS) + .getResult()); +---- + +=== 模板语言 + +利用动态字符串能力,QLExpress4 也可以作为一个轻量级模板引擎使用。 + +无需在脚本中手动添加字符串引号,直接调用 `executeTemplate` 渲染模板字符串: + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + Map context = new HashMap<>(); + context.put("a", 1); + context.put("b", 2); + context.put("c", "test"); + QLResult simpleTemplate = express4Runner.executeTemplate("a ${a};b ${b+2}", context, QLOptions.DEFAULT_OPTIONS); + Assert.assertEquals("a 1;b 4", simpleTemplate.getResult()); + QLResult conditionTemplate = + express4Runner.executeTemplate("m xx ${\n" + " if (c like 't%') {\n" + " 'YYY'\n" + " }\n" + "}", + context, + QLOptions.DEFAULT_OPTIONS); + Assert.assertEquals("m xx YYY", conditionTemplate.getResult()); + QLResult multiLineTemplate = express4Runner.executeTemplate("m\n ${a}\n c", context, QLOptions.DEFAULT_OPTIONS); + Assert.assertEquals("m\n 1\n c", multiLineTemplate.getResult()); + QLResult escapeStringTemplate = + express4Runner.executeTemplate("m \n\"haha\" d\"", context, QLOptions.DEFAULT_OPTIONS); + Assert.assertEquals("m \n\"haha\" d\"", escapeStringTemplate.getResult()); +---- + +=== 占位符 + +占位符用于从 context 中提取任意 key 的值。 + +全局变量也可以从 context 中提取值,但是收到 QLExpress 关键词和语法的限制,能提取的 key 有限。 +比如 context 中 "0" key 对应的值就无法通过变量提取,因为 0 不是 QLExpress 中的合法变量,而是一个数字常量。 +此时可以用默认占位符 `$\{0}` 来提取。 + +> 注意和动态字符串中插值区分,占位符是写在字符串之外。动态字符串插值是 `$\{expression}`,其中默认写的是表达式,`"${0}"` 的运行结果是 `"0"`。而占位符是 `$\{placeholder}`,其中默认写的是 context 中的 key,`${0}` 的运行结果是 context 中 "0" key 对应的值。 + +QLExpress 默认使用 `$\{placeholder}` 格式的占位符,其中: + +* `${` 是起始标记 +* `}` 是结束标记 +* `placeholder` 是占位符内容,对应 cotext 中的 key + +除了默认的占位符外,QLExpress 还支持自定义占位符的起始和结束标记: + +[source,java] +---- + Express4Runner express4Runner = + new Express4Runner(InitOptions.builder().selectorStart("#[").selectorEnd("]").build()); + + Map context = new HashMap<>(); + context.put("0", "World"); + + QLResult result = express4Runner.execute("'Hello ' + #[0]", context, QLOptions.DEFAULT_OPTIONS); + assertEquals("Hello World", result.getResult()); +---- + +自定义占位符并不是任意的,限制条件如下: + +* **起始标记限制**:`selectorStart` 必须是以下四种格式之一: + ** `${` (默认) + ** `$[` + ** `#{` + ** `#[` +* **结束标记限制**:`selectorEnd` 必须是 1 个或更多字符的字符串 + +=== 分号 + +表达式语句可以省略结尾的分号,整个脚本的返回值就是最后一个表达式的计算结果。 + +以下脚本的返回值为 2: + +[source,java] +---- +a = 1 +b = 2 +// last express +1+1 +---- + +等价于以下写法: + +[source,java] +---- +a = 1 +b = 2 +// return statment +return 1+1; +---- + +因为分号可以省略,QLExpress4 对于换行的处理相比 3 或者 Java 语言更加严格。如果想要将多行表达式拆成多行,建议将操作符保留在当前行,而将右操作数换到下一行。 + +以下多行表达式会报语法错误(反例): + +[source,java] +---- +// syntax error +a ++ b +---- + +以下是正确的换行示例(正例): + +[source,java] +---- +a + +b +---- + +其他的语法习惯保持和 Java 一致即可。 + +=== 表达式 + +QLExpress 采用表达式优先的设计,其中 除了 import, return 和循环等结构外,几乎都是表达式。 + +if 语句也是一个表达式: + +[source,java] +---- +assert(if (11 == 11) { + 10 +} else { + 20 + 2 +} + 1 == 11) +---- + +try catch 结构也是一个表达式: + +[source,java] +---- +assert(1 + try { + 100 + 1/0 +} catch(e) { + // Throw a zero-division exception + 11 +} == 12) +---- + +=== 短路计算 + +和 Java 类似,`&&` 和 `||` 逻辑运算都是短路运算的。 + +比如表达式 `false && (1/0)` 不会发生除 0 错误,因为 `&&` 短路在了最开始的 `false` 处。 + +短路计算默认是开启的,引擎也提供了选项,可以在某次执行时将短路关闭: + +> 关闭短路的一个场景是保证表达式的充分预热 + +[source,java] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + // execute when enable short circuit (default) + // `1/0` is short-circuited by the preceding `false`, so it won't throw an error. + assertFalse((Boolean)express4Runner.execute("false && (1/0)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS) + .getResult()); + try { + // execute when disable short circuit + express4Runner.execute("false && (1/0)", + Collections.emptyMap(), + QLOptions.builder().shortCircuitDisable(true).build()); + fail(); + } + catch (QLException e) { + Assert.assertEquals("INVALID_ARITHMETIC", e.getErrorCode()); + Assert.assertEquals("Division by zero", e.getReason()); + } +---- + + +=== 控制结构 + +==== if 分支 + +除了完全兼容 Java 中的 `if` 写法,还支持类似规则引擎的 `if ... then ... else ...` 的写法,其中 `then` 可以当成一个可以省略的关键字: + +[source,java] +---- +a = 11; +// if ... else ... +assert(if (a >= 0 && a < 5) { + true +} else if (a >= 5 && a < 10) { + false +} else if (a >= 10 && a < 15) { + true +} == true) + +// if ... then ... else ... +r = if (a == 11) then true else false +assert(r == true) +---- + +==== while 循环 + +[source,java] +---- +i = 0; +while (i < 5) { + if (++i == 2) { + break; + } +} +assert(i==2) +---- + +==== for 循环 + +[source,java] +---- +l = []; +for (int i = 3; i < 6; i++) { + l.add(i); +} +assert(l==[3,4,5]) +---- + +==== for-each 循环 + +[source,java] +---- +sum = 0; +for (i: [0,1,2,3,4]) { + if (i == 2) { + continue; + } + sum += i; +} +assert(sum==8) +---- + +==== try-catch + +[source,java] +---- +assert(try { + 100 + 1/0 +} catch(e) { + // Throw a zero-division exception + 11 +} == 11) +---- + +=== 函数定义 + +[source,java] +---- +function sub(a, b) { + return a-b; +} +assert(sub(3,1)==2) +---- + +=== Lambda 表达式 + +QLExpress4 中,Lambda 表达式作为一等公民,可以作为变量进行传递或者返回。 + +[source,java] +---- +add = (a, b) -> { + return a + b; +} +assert(add(1,2)==3) +---- + +=== 列表过滤和映射 + +支持通过 filter, map 方法直接对列表类型进行函数式过滤和映射。 + +底层通过在列表类型添加 link:#扩展函数[扩展函数] 实现,注意和 Stream API 中同名方法区分。 + +相比 Stream Api,它可以直接对列表进行操作,返回值也直接就是列表,更加方便。 + +[source,java] +---- +l = ["a-111", "a-222", "b-333", "c-888"] +assert(l.filter(i -> i.startsWith("a-")) + .map(i -> i.split("-")[1]) == ["111", "222"]) +---- + +=== 兼容 Java8 语法 + +QLExpress 可以兼容 Java8 的常见语法。 + +比如 link:#for-each-循环[for each循环], Stream API, 函数式接口等等。 + +==== Stream API + +可以直接使用 Java 集合中的 stream api 对集合进行操作。 + +因为此时的 stream api 都是来自 Java 中的方法,参考 link:#安全策略[安全策略] 打开安全选项,以下脚本才能正常执行。 + +[source,java] +---- +l = ["a-111", "a-222", "b-333", "c-888"] + +l2 = l.stream() + .filter(i -> i.startsWith("a-")) + .map(i -> i.split("-")[1]) + .collect(Collectors.toList()); +assert(l2 == ["111", "222"]); +---- + +==== 函数式接口 + +Java8 中引入了 Function, Consumer, Predicate 等函数式接口,QLExpress 中的 link:#lambda-表达式[Lambda表达式] 可以赋值给这些接口,或者作为接收这些接口的方法参数: + +[source,java] +---- +Runnable r = () -> a = 8; +r.run(); +assert(a == 8); + +Supplier s = () -> "test"; +assert(s.get() == 'test'); + +Consumer c = (a) -> b = a + "-te"; +c.accept("ccc"); +assert(b == 'ccc-te'); + +Function f = a -> a + 3; +assert(f.apply(1) == 4); + +Function f1 = (a, b) -> a + b; +assert(f1.apply("test-") == "test-null"); +---- + +== 附录一 升级指南 + +QLExpress 的上一版本因为多年的迭代停滞,在各项特性上和业界产生了较大差距。 + +QLExpress4 的目标之一就是一次性弥补这些差距,因此选择进行了大刀阔斧的升级,而有意放弃了部分兼容性。当然,基础的功能和体验还是和上一版本保持了对齐。 + +如果系统已经使用老版本的 QLExpress,升级之前务必要进行一次全面的回归测试,确保这些脚本都能在新版中正常执行,再进行升级。 + +如果没有时间或者方法对它们一一验证,那么不建议进行升级。 + +如果是新系统,建议直接采用 QLExpress4,未来 QLExpress4 的生态建设会越来越完善,而 3 会被逐渐抛弃。 + +下面将列表新版和旧版的主要不同,方便用户对已有脚本进行升级。如有遗漏,欢迎反馈: + +=== 默认安全策略 + +如果完全使用默认选项,获取 Java 对象的字段(`o.field`),或者调用成员方法(`o.method()`),则会分别抛出 `FIELD_NOT_FOUND` 和 `METHOD_NOT_FOUND` 错误。 + +这是因为 3 可以没有限制地通过反射访问 Java 应用系统中的任意字段和方法,这在嵌入式脚本中被认为是不安全的。 + +如果想兼容 3 的行为,则在新建 `Express4Runner` 时, 要将安全策略设置为 “开放”,参考代码如下: + +[source,java,indent=0] +---- + // open security strategy + Express4Runner express4RunnerOpen = + new Express4Runner(InitOptions.builder().securityStrategy(QLSecurityStrategy.open()).build()); + Assert.assertEquals("Thinking in Java", + express4RunnerOpen.execute("desk.book1", context, QLOptions.DEFAULT_OPTIONS).getResult()); + Assert.assertEquals("Effective Java", + express4RunnerOpen.execute("desk.getBook2()", context, QLOptions.DEFAULT_OPTIONS).getResult()); +---- + +详细参考 link:#安全策略[安全策略] 章节。 + +=== 定义映射 + +QLExpress 老版本支持通过 `NewMap(key:value)` 的方式快速创建映射,虽然在文档中没有详细讨论,但是很多用户通过单元测试和询问的方式,知晓并使用了这个语法。 + +不过这种语法过于定制,也和业界的规范相差很大,因此在新版中将其移除。 + +新版原生支持 JSON 语法,直接采用 JSON 字典的格式(`{key:value}`)即可快速创建映射,更加直观。 + +详细参考 link:#方便语法元素[方便语法元素] + +=== 全局变量污染上下文 + +QLExpress 支持在执行脚本时传入一个全局的上下文,即 context 参数。 + +在老版本中,如果脚本中定义了全局变量,则这些变量也会写入到 context。在脚本执行结束后,可以通过 context 获取到脚本中定义的全局变量的值。 + +一个老版本的列子如下: + +[source,java] +---- +// only for QLExpress 3.x + +String express = "a=3;a+1"; +ExpressRunner runner = new ExpressRunner(false, true); +DefaultContext context = new DefaultContext<>(); + +Object res = runner.execute(express, context, null, true, true); +// The result of the script execution should be 4 (a+1) +Assert.assertEquals(4, res); +// The variable 'a' defined in the script is also stored in the context +Assert.assertEquals(3, context.get("a")); +---- + +根据调研和反馈,我们认为这会导致全局上下文被脚本 “污染”,存在安全性问题。 + +因此在 QLExpress4 中,全局变量默认不会写入到 context 中。 + +如果想要兼容 3 的特性,需要将 `polluteUserContext` 选项设置为 `true`,参考代码如下: + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + QLOptions populateOption = QLOptions.builder().polluteUserContext(true).build(); + Map populatedMap = new HashMap<>(); + populatedMap.put("b", 10); + express4Runner.execute("a = 11;b = a", populatedMap, populateOption); + assertEquals(11, populatedMap.get("a")); + assertEquals(11, populatedMap.get("b")); + + // no population + Map noPopulatedMap1 = new HashMap<>(); + express4Runner.execute("a = 11", noPopulatedMap1, QLOptions.DEFAULT_OPTIONS); + assertFalse(noPopulatedMap1.containsKey("a")); + + Map noPopulatedMap2 = new HashMap<>(); + noPopulatedMap2.put("a", 10); + assertEquals(19, express4Runner.execute("a = 19;a", noPopulatedMap2, QLOptions.DEFAULT_OPTIONS).getResult()); + assertEquals(10, noPopulatedMap2.get("a")); +---- + +=== 分号可省略 + +"分号可省略" 已经是现代脚本语言的一个标配,QLExpress4 也跟进了这个特性,分号是可以省略的。 + +具体参考 link:#分号[分号] 章节。 + +=== 严格换行模式 + +由于 QLExpress4 支持分号可省略,解释器需要通过换行符来判断语句是否结束。因此,QLExpress4 对换行的要求比 QLExpress3 更加严格。 + +下面的脚本在 QLExpress3 中是合法的,但在 QLExpress4 中不是: + +[source,java] +---- +// 在 QLExpress3 中合法的脚本,但在 QLExpress4 中不是 +商家应收= + 价格 + - 饭卡商家承担 + + 平台补贴 +---- + +在 QLExpress4 中,上述脚本会被解析为两个独立的语句: +1. `商家应收 = 价格` +2. `- 饭卡商家承担 + 平台补贴`(第二个语句会报语法错误) + +如果要在 QLExpress4 中实现同样的效果,需要将操作符放在行尾,而不是行首: + +[source,java] +---- +// QLExpress4 中正确的写法 +商家应收= + 价格 - + 饭卡商家承担 + + 平台补贴 +---- + +这样解释器就知道当前行的表达式还未结束,会继续读取下一行。 + +如果您需要兼容 QLExpress3 的换行特性,可以设置 `strictNewLines` 选项为 `false`: + +[source,java] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.builder().strictNewLines(false).build()); + String script = "商家应收=\n 价格\n - 饭卡商家承担\n + 平台补贴"; + Map context = new HashMap<>(); + context.put("价格", 10); + context.put("饭卡商家承担", 3); + context.put("平台补贴", 5); + QLResult result = express4Runner.execute(script, context, QLOptions.DEFAULT_OPTIONS); + Assert.assertEquals(12, ((Number)result.getResult()).intValue()); +---- + +注意:非严格换行模式会让解释器忽略所有换行符,可能会影响代码的可读性和错误提示的准确性。建议仅在需要兼容旧代码时使用。 + +=== 获得 char 类型 + +在 QLExpress 3 中,单引号包裹的单个字符会被解析为 char 类型,而不是 String。 + +这个给用户带来了不少困惑,比如 `"a"=='a'` 的判断结果是 `false`。 + +所以后来 QLExpress 3 中新增了 `ExpressRunner.setIgnoreConstChar` 选项,设置为 `true` 后,所有的单引号和双引号包裹的字符都会被解析为 String 类型。但是这个选项默认是关闭的,需要用户手动开启。 + +考虑到脚本用户很少会使用到 `char` 这种底层类型,我们在 QLExpress 4 中直接取消了这个选项,所有的单引号和双引号包裹的字符都会被解析为 String 类型。 + +如果您在脚本还是需要使用 `char` 类型,可以通过两种方法获得: + +* 类型强转:`(char) 'a'` +* 类型声明:`char a = 'a'` + +== 附录二 如何贡献? + +QLExpress 对社区的更改完全开放,任何建议和修改,都会受到欢迎,讨论后合理最后会被接纳到主干中。 + +首先需要将代码 clone 到本地,在正式修改代码前,需要先进行如下准备: + +1. 项目根目录执行 `mvn compile`:项目刚刚下载到本地时,会有大量的类找不到,需要先生成 Antlr4 的运行时代码 +2. 配置代码格式化:QLExpress 项目有统一的代码格式规范,开发前需要配置在 git 提交前的自动格式化 + +在项目目录下新建文件 `.git/hooks/pre-commit`,内容如下: + +[source,bash] +---- +#!/bin/sh +mvn spotless:apply +git add -u +exit 0 +---- + +这样在每次 git commit 之前,就会自动执行 maven 的 spotless 插件执行代码格式化,具体代码格式配置见 link:spotless_eclipse_formatter.xml[] + +3. 执行单元测试:在开发完成代码之后,先在本地全量执行单元测试,确保代码质量 + - JDK 8 环境:执行 `mvn test` + - JDK 9 及以上环境:执行 `mvn test -DargLine="--add-opens java.base/java.util.stream=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED"` + +== 附录三 QLExpress4性能提升 + +link:https://www.yuque.com/xuanheng-ffjti/iunlps/pgfzw46zel2xfnie?singleDoc#%20%E3%80%8AQLExpress3%E4%B8%8E4%E6%80%A7%E8%83%BD%E5%AF%B9%E6%AF%94%E3%80%8B[QLExpress4与3性能对比] + +总结:常见场景下,无编译缓存时,QLExpress4能比3有接近10倍性能提升;有编译缓存,也有一倍性能提升。 + +== 附录四 开发者联系方式 + + * Email: + ** qinyuan.dqy@alibaba-inc.com + ** yumin.pym@taobao.com + ** 704643716@qq.com + * WeChat: + ** xuanheng: dqy932087612 + ** binggou: pymbupt + ** linxiang: tkk33362 + * DingTalk Support Group + +image::images/qlexpress_support_group_qr_2026.jpg[] diff --git a/docs/custom-item-en-source.adoc b/docs/custom-item-en-source.adoc new file mode 100644 index 0000000..5437670 --- /dev/null +++ b/docs/custom-item-en-source.adoc @@ -0,0 +1,152 @@ +:toc: + += Custom Syntax Elements + +This section systematically outlines the interfaces and capabilities in QLExpress4 for customizing syntax elements. + +== Overview + +* Custom functions +** Implement the CustomFunction interface +** Use Java functional interfaces +** Register via annotation scanning +** Add via QLExpress script +** Implement QLFunctionalVarargs +* Custom operators +** Implement CustomBinaryOperator +** Replace built-in operators +** Use Java functional interfaces +** Add aliases +** Implement QLFunctionalVarargs +* Extension functions +** Extend ExtensionFunction +** Implement QLFunctionalVarargs +* Aliases for operators and functions + +== Custom Functions + +=== Implement the CustomFunction interface + +[source,java,indent=0] +---- +include::../src/test/java/com/alibaba/qlexpress4/test/function/HelloFunction.java[] +---- + +[source,java,indent=0] +---- +include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=customComplexFunction] +---- + +=== Use Java functional interfaces + +[source,java,indent=0] +---- +include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=addFunctionWithJavaFunctional] +---- + +=== Register via annotations + +[source,java,indent=0] +---- +include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=annoObj] +---- + +[source,java,indent=0] +---- +include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=addFunctionByAnnotationObject] +---- + +=== Add via QLExpress script + +[source,java,indent=0] +---- +include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=joinFunctionVarargsObj] +---- + +=== Implement QLFunctionalVarargs + +[source,java,indent=0] +---- +include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=addFunctionByVarargs] +---- + +[source,java,indent=0] +---- +include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=addFunctionByVarargs] +---- + +== Custom Operators + +=== Implement CustomBinaryOperator and set precedence + +[source,java,indent=0] +---- +include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=addOperatorWithPrecedence] +---- + +=== Replace built-in operators + +[source,java,indent=0] +---- +include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=replaceDefaultOperator] +---- + +=== Use Java functional interfaces + +[source,java,indent=0] +---- +include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=addOperatorBiFunction] +---- + +=== Implement QLFunctionalVarargs + +[source,java,indent=0] +---- +include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=addOperatorByVarargs] +---- + +== Extension Functions + +=== Extend ExtensionFunction + +[source,java,indent=0] +---- +include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=defineExtensionFunctionByExtensionFunction] +---- + +=== Implement QLFunctionalVarargs + +[source,java,indent=0] +---- +include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=defineExtensionFunctionByQLFunctionalVarargs] +---- + +== QLFunctionalVarargs: One object defines three kinds of operations + +The same QLFunctionalVarargs object can serve as the implementation of a function, an operator, and an extension function at the same time, making it easy to reuse unified semantics and implementation across multiple places. This capability comes from the interface’s varargs design; see the example and interface definition below. For background discussion, refer to link:https://github.com/alibaba/QLExpress/issues/407[issue407]: + +[source,java,indent=0] +---- +include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=qlfunctionalvarargsAllInOne] +---- + +=== Interface definition + +[source,java,indent=0] +---- +include::../src/main/java/com/alibaba/qlexpress4/api/QLFunctionalVarargs.java[] +---- + +== Aliases for operators and functions (keyword aliases also supported) + +[source,java,indent=0] +---- +include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=addAlias] +---- + +== Notes and recommendations + +- Under the QLFunctionalVarargs calling convention, when invoking an extension function the first actual argument is the receiver object, followed by the call arguments; functions and operators do not include a receiver. +- Set the precedence of custom operators according to the intended expression semantics to avoid confusion with existing rules. +- Annotation-based registration only processes public methods, and duplicate names will fail to register; the batch registration result contains both success and failure lists. + diff --git a/docs/custom-item-en.adoc b/docs/custom-item-en.adoc new file mode 100644 index 0000000..66e23bf --- /dev/null +++ b/docs/custom-item-en.adoc @@ -0,0 +1,334 @@ +:toc: + += Custom Syntax Elements + +This section systematically outlines the interfaces and capabilities in QLExpress4 for customizing syntax elements. + +== Overview + +* Custom functions +** Implement the CustomFunction interface +** Use Java functional interfaces +** Register via annotation scanning +** Add via QLExpress script +** Implement QLFunctionalVarargs +* Custom operators +** Implement CustomBinaryOperator +** Replace built-in operators +** Use Java functional interfaces +** Add aliases +** Implement QLFunctionalVarargs +* Extension functions +** Extend ExtensionFunction +** Implement QLFunctionalVarargs +* Aliases for operators and functions + +== Custom Functions + +=== Implement the CustomFunction interface + +[source,java,indent=0] +---- +package com.alibaba.qlexpress4.test.function; + +import com.alibaba.qlexpress4.runtime.Parameters; +import com.alibaba.qlexpress4.runtime.QContext; +import com.alibaba.qlexpress4.runtime.function.CustomFunction; + +public class HelloFunction implements CustomFunction { + @Override + public Object call(QContext qContext, Parameters parameters) + throws Throwable { + String tenant = (String)qContext.attachment().get("tenant"); + return "hello," + tenant; + } +} +---- + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + express4Runner.addFunction("hello", new HelloFunction()); + String resultJack = (String)express4Runner.execute("hello()", + Collections.emptyMap(), + // Additional information(tenant for example) can be brought into the custom function from outside via attachments + QLOptions.builder().attachments(Collections.singletonMap("tenant", "jack")).build()).getResult(); + assertEquals("hello,jack", resultJack); + String resultLucy = + (String)express4Runner + .execute("hello()", + Collections.emptyMap(), + QLOptions.builder().attachments(Collections.singletonMap("tenant", "lucy")).build()) + .getResult(); + assertEquals("hello,lucy", resultLucy); +---- + +=== Use Java functional interfaces + +[source,java,indent=0] +---- + Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + // Function + runner.addFunction("inc", (Function)x -> x + 1); + // Predicate + runner.addFunction("isPos", (Predicate)x -> x > 0); + // Runnable + runner.addFunction("notify", () -> { + }); + // Consumer + runner.addFunction("print", (Consumer)System.out::println); + + Object r1 = runner.execute("inc(1)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); + Object r2 = runner.execute("isPos(1)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals(2, r1); + assertEquals(true, r2); +---- + +=== Register via annotations + +[source,java,indent=0] +---- + public static class MyFunctionUtil { + @QLFunction({"myAdd", "iAdd"}) + public int add(int a, int b) { + return a + b; + } + + @QLFunction("arr3") + public static int[] array3(int a, int b, int c) { + return new int[] {a, b, c}; + } + + @QLFunction("concat") + public String concat(String a, String b) { + return a + b; + } + } +---- + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + BatchAddFunctionResult addResult = express4Runner.addObjFunction(new MyFunctionUtil()); + assertEquals(4, addResult.getSucc().size()); + Object result = + express4Runner.execute("myAdd(1,2) + iAdd(5,6)", new HashMap<>(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals(14, result); + express4Runner.addStaticFunction(MyFunctionUtil.class); + Object result1 = + express4Runner.execute("arr3(5,9,10)[2]", new HashMap<>(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals(10, result1); + + Object result2 = + express4Runner.execute("concat('aa', null)", new HashMap<>(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals("aanull", result2); +---- + +=== Add via QLExpress script + +[source,java,indent=0] +---- + public static class JoinFunction implements QLFunctionalVarargs { + @Override + public Object call(Object... params) { + return Arrays.stream(params).map(Object::toString).collect(Collectors.joining(",")); + } + } +---- + +=== Implement QLFunctionalVarargs + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + express4Runner.addVarArgsFunction("join", new JoinFunction()); + Object resultFunction = + express4Runner.execute("join(1,2,3)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals("1,2,3", resultFunction); +---- + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + express4Runner.addVarArgsFunction("join", new JoinFunction()); + Object resultFunction = + express4Runner.execute("join(1,2,3)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals("1,2,3", resultFunction); +---- + +== Custom Operators + +=== Implement CustomBinaryOperator and set precedence + +[source,java,indent=0] +---- + Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + runner.addOperator("?><", (left, right) -> left.get().toString() + right.get().toString(), QLPrecedences.ADD); + Object r = runner.execute("1 ?>< 2 * 3", new HashMap<>(), QLOptions.DEFAULT_OPTIONS).getResult(); + // precedence set to ADD, so multiply first, then custom operator: "1" + "6" => "16" + assertEquals("16", r); +---- + +=== Replace built-in operators + +[source,java,indent=0] +---- + Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + boolean ok = runner.replaceDefaultOperator("+", + (left, right) -> Double.parseDouble(left.get().toString()) + Double.parseDouble(right.get().toString())); + assertTrue(ok); + Object r = runner.execute("'1.2' + '2.3'", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals(3.5d, r); +---- + +=== Use Java functional interfaces + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + express4Runner.addOperatorBiFunction("join", (left, right) -> left + "," + right); + Object resultOperator = + express4Runner.execute("1 join 2 join 3", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals("1,2,3", resultOperator); +---- + +=== Implement QLFunctionalVarargs + +[source,java,indent=0] +---- + Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + runner.addOperator("join", params -> params[0] + "," + params[1]); + Object r = runner.execute("1 join 2", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals("1,2", r); +---- + +== Extension Functions + +=== Extend ExtensionFunction + +[source,java,indent=0] +---- + ExtensionFunction helloFunction = new ExtensionFunction() { + @Override + public Class[] getParameterTypes() { + return new Class[0]; + } + + @Override + public String getName() { + return "hello"; + } + + @Override + public Class getDeclaringClass() { + return String.class; + } + + @Override + public Object invoke(Object obj, Object[] args) { + String originStr = (String)obj; + return "Hello," + originStr; + } + }; + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + express4Runner.addExtendFunction(helloFunction); + Object result = + express4Runner.execute("'jack'.hello()", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals("Hello,jack", result); +---- + +=== Implement QLFunctionalVarargs + +[source,java,indent=0] +---- + // simpler way to define extension function + express4Runner.addExtendFunction("add", + Number.class, + params -> ((Number)params[0]).intValue() + ((Number)params[1]).intValue()); + QLResult resultAdd = express4Runner.execute("1.add(2)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS); + assertEquals(3, resultAdd.getResult()); +---- + +== QLFunctionalVarargs: One object defines three kinds of operations + +The same QLFunctionalVarargs object can serve as the implementation of a function, an operator, and an extension function at the same time, making it easy to reuse unified semantics and implementation across multiple places. This capability comes from the interface’s varargs design; see the example and interface definition below. For background discussion, refer to link:https://github.com/alibaba/QLExpress/issues/407[issue407]: + +[source,java,indent=0] +---- + Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + QLFunctionalVarargs allInOne = params -> { + // sum numbers no matter how many args + double sum = 0d; + for (Object p : params) { + if (p instanceof Number) { + sum += ((Number)p).doubleValue(); + } + } + return sum; + }; + + // as function + runner.addVarArgsFunction("sumAll", allInOne); + // as operator + runner.addOperator("+&", allInOne); + // as extension function: first arg is the receiver, followed by call arguments + runner.addExtendFunction("plusAll", Number.class, allInOne); + + Map ctx = new HashMap<>(); + Object rf = runner.execute("sumAll(1,2,3)", ctx, QLOptions.DEFAULT_OPTIONS).getResult(); + Object ro = runner.execute("1 +& 4", ctx, QLOptions.DEFAULT_OPTIONS).getResult(); + Object re = runner.execute("1.plusAll(5)", ctx, QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals(6d, rf); + assertEquals(5d, ro); + assertEquals(6d, re); +---- + +=== Interface definition + +[source,java,indent=0] +---- +package com.alibaba.qlexpress4.api; + +/** + * Author: TaoKan + */ +@FunctionalInterface +public interface QLFunctionalVarargs { + Object call(Object... params); +} +---- + +== Aliases for operators and functions (keyword aliases also supported) + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + // add custom function zero + express4Runner.addFunction("zero", (String ignore) -> 0); + + // keyword alias + assertTrue(express4Runner.addAlias("如果", "if")); + assertTrue(express4Runner.addAlias("则", "then")); + assertTrue(express4Runner.addAlias("否则", "else")); + assertTrue(express4Runner.addAlias("返回", "return")); + // operator alias + assertTrue(express4Runner.addAlias("大于", ">")); + // function alias + assertTrue(express4Runner.addAlias("零", "zero")); + + Map context = new HashMap<>(); + context.put("语文", 90); + context.put("数学", 90); + context.put("英语", 90); + + Object result = express4Runner + .execute("如果 (语文 + 数学 + 英语 大于 270) 则 {返回 1;} 否则 {返回 零();}", context, QLOptions.DEFAULT_OPTIONS) + .getResult(); + assertEquals(0, result); +---- + +== Notes and recommendations + +- Under the QLFunctionalVarargs calling convention, when invoking an extension function the first actual argument is the receiver object, followed by the call arguments; functions and operators do not include a receiver. +- Set the precedence of custom operators according to the intended expression semantics to avoid confusion with existing rules. +- Annotation-based registration only processes public methods, and duplicate names will fail to register; the batch registration result contains both success and failure lists. diff --git a/docs/custom-item-source.adoc b/docs/custom-item-source.adoc new file mode 100644 index 0000000..da4b932 --- /dev/null +++ b/docs/custom-item-source.adoc @@ -0,0 +1,152 @@ +:toc: + += 自定义语法元素 + +本节系统梳理在 QLExpress4 在自定义语法元素方面的接口和能力。 + +== 总览 + +* 自定义函数 +** 实现CustomFunction接口 +** 使用 Java 函数式接口 +** 通过注解扫描注册 +** 通过QLExpress脚本添加 +** 实现QLFunctionalVarargs +* 自定义操作符 +** 实现CustomBinaryOperator +** 替换内置操作符 +** 使用 Java 函数式接口 +** 添加别名 +** 实现QLFunctionalVarargs +* 扩展函数 +** 继承ExtensionFunction +** 实现QLFunctionalVarargs +* 操作符与函数别名 + +== 自定义函数 + +=== 实现CustomFunction接口 + +[source,java,indent=0] +---- +include::../src/test/java/com/alibaba/qlexpress4/test/function/HelloFunction.java[] +---- + +[source,java,indent=0] +---- +include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=customComplexFunction] +---- + +=== 使用 Java 函数式接口 + +[source,java,indent=0] +---- +include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=addFunctionWithJavaFunctional] +---- + +=== 注解方式注册 + +[source,java,indent=0] +---- +include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=annoObj] +---- + +[source,java,indent=0] +---- +include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=addFunctionByAnnotationObject] +---- + +=== 通过QLExpress脚本添加 + +[source,java,indent=0] +---- +include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=joinFunctionVarargsObj] +---- + +=== 实现QLFunctionalVarargs + +[source,java,indent=0] +---- +include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=addFunctionByVarargs] +---- + +[source,java,indent=0] +---- +include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=addFunctionByVarargs] +---- + +== 自定义操作符 + +=== 实现CustomBinaryOperator并设置优先级 + +[source,java,indent=0] +---- +include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=addOperatorWithPrecedence] +---- + +=== 替换内置操作符 + +[source,java,indent=0] +---- +include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=replaceDefaultOperator] +---- + +=== 使用 Java 函数式接口 + +[source,java,indent=0] +---- +include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=addOperatorBiFunction] +---- + +=== 实现QLFunctionalVarargs + +[source,java,indent=0] +---- +include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=addOperatorByVarargs] +---- + +== 扩展函数 + +=== 继承ExtensionFunction + +[source,java,indent=0] +---- +include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=defineExtensionFunctionByExtensionFunction] +---- + +=== 实现QLFunctionalVarargs + +[source,java,indent=0] +---- +include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=defineExtensionFunctionByQLFunctionalVarargs] +---- + +== QLFunctionalVarargs:一个对象同时定义三类操作 + +同一个QLFunctionalVarargs对象可同时用作函数、操作符与扩展函数的实现,便于在多处复用统一的语义与实现。该能力来源于接口的可变参数设计,详见下方示例与接口定义。背景讨论参考 link:https://github.com/alibaba/QLExpress/issues/407[issue407]: + +[source,java,indent=0] +---- +include::../src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java[tag=qlfunctionalvarargsAllInOne] +---- + +=== 接口定义 + +[source,java,indent=0] +---- +include::../src/main/java/com/alibaba/qlexpress4/api/QLFunctionalVarargs.java[] +---- + +== 操作符与函数别名(亦支持关键字别名) + +[source,java,indent=0] +---- +include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=addAlias] +---- + +== 说明与建议 + +- QLFunctionalVarargs 的定义模式下,扩展函数调用时实参列表首位是接收者对象,其后为调用参数;函数与操作符不含接收者。 +- 自定义操作符的优先级需根据表达式期望进行设置,避免与已有运算规则产生混淆。 +- 注解方式注册仅会处理公开方法,且重复名称将注册失败;批量注册返回结果中包含成功与失败清单。 + diff --git a/docs/custom-item.adoc b/docs/custom-item.adoc new file mode 100644 index 0000000..ad131a1 --- /dev/null +++ b/docs/custom-item.adoc @@ -0,0 +1,334 @@ +:toc: + += 自定义语法元素 + +本节系统梳理在 QLExpress4 在自定义语法元素方面的接口和能力。 + +== 总览 + +* 自定义函数 +** 实现CustomFunction接口 +** 使用 Java 函数式接口 +** 通过注解扫描注册 +** 通过QLExpress脚本添加 +** 实现QLFunctionalVarargs +* 自定义操作符 +** 实现CustomBinaryOperator +** 替换内置操作符 +** 使用 Java 函数式接口 +** 添加别名 +** 实现QLFunctionalVarargs +* 扩展函数 +** 继承ExtensionFunction +** 实现QLFunctionalVarargs +* 操作符与函数别名 + +== 自定义函数 + +=== 实现CustomFunction接口 + +[source,java,indent=0] +---- +package com.alibaba.qlexpress4.test.function; + +import com.alibaba.qlexpress4.runtime.Parameters; +import com.alibaba.qlexpress4.runtime.QContext; +import com.alibaba.qlexpress4.runtime.function.CustomFunction; + +public class HelloFunction implements CustomFunction { + @Override + public Object call(QContext qContext, Parameters parameters) + throws Throwable { + String tenant = (String)qContext.attachment().get("tenant"); + return "hello," + tenant; + } +} +---- + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + express4Runner.addFunction("hello", new HelloFunction()); + String resultJack = (String)express4Runner.execute("hello()", + Collections.emptyMap(), + // Additional information(tenant for example) can be brought into the custom function from outside via attachments + QLOptions.builder().attachments(Collections.singletonMap("tenant", "jack")).build()).getResult(); + assertEquals("hello,jack", resultJack); + String resultLucy = + (String)express4Runner + .execute("hello()", + Collections.emptyMap(), + QLOptions.builder().attachments(Collections.singletonMap("tenant", "lucy")).build()) + .getResult(); + assertEquals("hello,lucy", resultLucy); +---- + +=== 使用 Java 函数式接口 + +[source,java,indent=0] +---- + Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + // Function + runner.addFunction("inc", (Function)x -> x + 1); + // Predicate + runner.addFunction("isPos", (Predicate)x -> x > 0); + // Runnable + runner.addFunction("notify", () -> { + }); + // Consumer + runner.addFunction("print", (Consumer)System.out::println); + + Object r1 = runner.execute("inc(1)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); + Object r2 = runner.execute("isPos(1)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals(2, r1); + assertEquals(true, r2); +---- + +=== 注解方式注册 + +[source,java,indent=0] +---- + public static class MyFunctionUtil { + @QLFunction({"myAdd", "iAdd"}) + public int add(int a, int b) { + return a + b; + } + + @QLFunction("arr3") + public static int[] array3(int a, int b, int c) { + return new int[] {a, b, c}; + } + + @QLFunction("concat") + public String concat(String a, String b) { + return a + b; + } + } +---- + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + BatchAddFunctionResult addResult = express4Runner.addObjFunction(new MyFunctionUtil()); + assertEquals(4, addResult.getSucc().size()); + Object result = + express4Runner.execute("myAdd(1,2) + iAdd(5,6)", new HashMap<>(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals(14, result); + express4Runner.addStaticFunction(MyFunctionUtil.class); + Object result1 = + express4Runner.execute("arr3(5,9,10)[2]", new HashMap<>(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals(10, result1); + + Object result2 = + express4Runner.execute("concat('aa', null)", new HashMap<>(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals("aanull", result2); +---- + +=== 通过QLExpress脚本添加 + +[source,java,indent=0] +---- + public static class JoinFunction implements QLFunctionalVarargs { + @Override + public Object call(Object... params) { + return Arrays.stream(params).map(Object::toString).collect(Collectors.joining(",")); + } + } +---- + +=== 实现QLFunctionalVarargs + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + express4Runner.addVarArgsFunction("join", new JoinFunction()); + Object resultFunction = + express4Runner.execute("join(1,2,3)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals("1,2,3", resultFunction); +---- + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + express4Runner.addVarArgsFunction("join", new JoinFunction()); + Object resultFunction = + express4Runner.execute("join(1,2,3)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals("1,2,3", resultFunction); +---- + +== 自定义操作符 + +=== 实现CustomBinaryOperator并设置优先级 + +[source,java,indent=0] +---- + Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + runner.addOperator("?><", (left, right) -> left.get().toString() + right.get().toString(), QLPrecedences.ADD); + Object r = runner.execute("1 ?>< 2 * 3", new HashMap<>(), QLOptions.DEFAULT_OPTIONS).getResult(); + // precedence set to ADD, so multiply first, then custom operator: "1" + "6" => "16" + assertEquals("16", r); +---- + +=== 替换内置操作符 + +[source,java,indent=0] +---- + Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + boolean ok = runner.replaceDefaultOperator("+", + (left, right) -> Double.parseDouble(left.get().toString()) + Double.parseDouble(right.get().toString())); + assertTrue(ok); + Object r = runner.execute("'1.2' + '2.3'", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals(3.5d, r); +---- + +=== 使用 Java 函数式接口 + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + express4Runner.addOperatorBiFunction("join", (left, right) -> left + "," + right); + Object resultOperator = + express4Runner.execute("1 join 2 join 3", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals("1,2,3", resultOperator); +---- + +=== 实现QLFunctionalVarargs + +[source,java,indent=0] +---- + Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + runner.addOperator("join", params -> params[0] + "," + params[1]); + Object r = runner.execute("1 join 2", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals("1,2", r); +---- + +== 扩展函数 + +=== 继承ExtensionFunction + +[source,java,indent=0] +---- + ExtensionFunction helloFunction = new ExtensionFunction() { + @Override + public Class[] getParameterTypes() { + return new Class[0]; + } + + @Override + public String getName() { + return "hello"; + } + + @Override + public Class getDeclaringClass() { + return String.class; + } + + @Override + public Object invoke(Object obj, Object[] args) { + String originStr = (String)obj; + return "Hello," + originStr; + } + }; + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + express4Runner.addExtendFunction(helloFunction); + Object result = + express4Runner.execute("'jack'.hello()", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals("Hello,jack", result); +---- + +=== 实现QLFunctionalVarargs + +[source,java,indent=0] +---- + // simpler way to define extension function + express4Runner.addExtendFunction("add", + Number.class, + params -> ((Number)params[0]).intValue() + ((Number)params[1]).intValue()); + QLResult resultAdd = express4Runner.execute("1.add(2)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS); + assertEquals(3, resultAdd.getResult()); +---- + +== QLFunctionalVarargs:一个对象同时定义三类操作 + +同一个QLFunctionalVarargs对象可同时用作函数、操作符与扩展函数的实现,便于在多处复用统一的语义与实现。该能力来源于接口的可变参数设计,详见下方示例与接口定义。背景讨论参考 link:https://github.com/alibaba/QLExpress/issues/407[issue407]: + +[source,java,indent=0] +---- + Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + QLFunctionalVarargs allInOne = params -> { + // sum numbers no matter how many args + double sum = 0d; + for (Object p : params) { + if (p instanceof Number) { + sum += ((Number)p).doubleValue(); + } + } + return sum; + }; + + // as function + runner.addVarArgsFunction("sumAll", allInOne); + // as operator + runner.addOperator("+&", allInOne); + // as extension function: first arg is the receiver, followed by call arguments + runner.addExtendFunction("plusAll", Number.class, allInOne); + + Map ctx = new HashMap<>(); + Object rf = runner.execute("sumAll(1,2,3)", ctx, QLOptions.DEFAULT_OPTIONS).getResult(); + Object ro = runner.execute("1 +& 4", ctx, QLOptions.DEFAULT_OPTIONS).getResult(); + Object re = runner.execute("1.plusAll(5)", ctx, QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals(6d, rf); + assertEquals(5d, ro); + assertEquals(6d, re); +---- + +=== 接口定义 + +[source,java,indent=0] +---- +package com.alibaba.qlexpress4.api; + +/** + * Author: TaoKan + */ +@FunctionalInterface +public interface QLFunctionalVarargs { + Object call(Object... params); +} +---- + +== 操作符与函数别名(亦支持关键字别名) + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + // add custom function zero + express4Runner.addFunction("zero", (String ignore) -> 0); + + // keyword alias + assertTrue(express4Runner.addAlias("如果", "if")); + assertTrue(express4Runner.addAlias("则", "then")); + assertTrue(express4Runner.addAlias("否则", "else")); + assertTrue(express4Runner.addAlias("返回", "return")); + // operator alias + assertTrue(express4Runner.addAlias("大于", ">")); + // function alias + assertTrue(express4Runner.addAlias("零", "zero")); + + Map context = new HashMap<>(); + context.put("语文", 90); + context.put("数学", 90); + context.put("英语", 90); + + Object result = express4Runner + .execute("如果 (语文 + 数学 + 英语 大于 270) 则 {返回 1;} 否则 {返回 零();}", context, QLOptions.DEFAULT_OPTIONS) + .getResult(); + assertEquals(0, result); +---- + +== 说明与建议 + +- QLFunctionalVarargs 的定义模式下,扩展函数调用时实参列表首位是接收者对象,其后为调用参数;函数与操作符不含接收者。 +- 自定义操作符的优先级需根据表达式期望进行设置,避免与已有运算规则产生混淆。 +- 注解方式注册仅会处理公开方法,且重复名称将注册失败;批量注册返回结果中包含成功与失败清单。 diff --git a/docs/execute-en-source.adoc b/docs/execute-en-source.adoc new file mode 100644 index 0000000..4b25134 --- /dev/null +++ b/docs/execute-en-source.adoc @@ -0,0 +1,50 @@ +:toc: + += Expression Execution + +This section describes the different ways to execute expressions in QLExpress4. + +== Use Map as the context + +- `execute(String, Map, QLOptions)` +- Description: According to the method comments, use a `Map` as the context; the keys in the `Map` are the variable names used in the script, and the values are the corresponding variable values. +- Typical scenario: Build a `Map` from external business data and pass it in so the script can access variables directly by name. + +[source,java,indent=0] +---- +include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=firstQl] +---- + +== Use object fields as the context + +- `execute(String, Object, QLOptions)` +- Description: Use a plain Java object as the context; variable names in the script correspond to the object's field names (or accessible getters). +- Typical scenario: When you already have a DTO/POJO that carries the context data, pass the object directly so the script can read its fields. + +// Note: This references the test code for execution with an object context. +[source,java,indent=0] +---- +include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=executeWithObject] +---- + +== Use a set of aliased objects + +- `executeWithAliasObjects(String, QLOptions, Object...)` +- Description: Pass objects annotated with `@QLAlias`; the annotation value becomes the variable name in the context. Objects without the annotation are ignored. +- Typical scenario: Expose object properties/methods with Chinese (or domain-specific) aliases so rule authors can write scripts more intuitively. + +[source,java,indent=0] +---- +include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=qlAlias] +---- + +== Use a custom `ExpressContext` + +- `execute(String, ExpressContext, QLOptions)` +- Description: This overload allows passing a custom context that implements `ExpressContext` (for example, a dynamic variable context). The other `execute` overloads ultimately delegate to this method. +- Typical scenario: When you need on-demand, by-name evaluation of variable values, read/write isolation, or integration with external storage, a custom context is the most flexible choice. + +[source,java,indent=0] +---- +include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=dynamicVar] +---- diff --git a/docs/execute-en.adoc b/docs/execute-en.adoc new file mode 100644 index 0000000..fe5e879 --- /dev/null +++ b/docs/execute-en.adoc @@ -0,0 +1,95 @@ +:toc: + += Expression Execution + +This section describes the different ways to execute expressions in QLExpress4. + +== Use Map as the context + +- `execute(String, Map, QLOptions)` +- Description: According to the method comments, use a `Map` as the context; the keys in the `Map` are the variable names used in the script, and the values are the corresponding variable values. +- Typical scenario: Build a `Map` from external business data and pass it in so the script can access variables directly by name. + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + Map context = new HashMap<>(); + context.put("a", 1); + context.put("b", 2); + context.put("c", 3); + Object result = express4Runner.execute("a + b * c", context, QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals(7, result); +---- + +== Use object fields as the context + +- `execute(String, Object, QLOptions)` +- Description: Use a plain Java object as the context; variable names in the script correspond to the object's field names (or accessible getters). +- Typical scenario: When you already have a DTO/POJO that carries the context data, pass the object directly so the script can read its fields. + +// Note: This references the test code for execution with an object context. +[source,java,indent=0] +---- + MyObj myObj = new MyObj(); + myObj.a = 1; + myObj.b = "test"; + + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + Object result = express4Runner.execute("a+b", myObj, QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals("1test", result); +---- + +== Use a set of aliased objects + +- `executeWithAliasObjects(String, QLOptions, Object...)` +- Description: Pass objects annotated with `@QLAlias`; the annotation value becomes the variable name in the context. Objects without the annotation are ignored. +- Typical scenario: Expose object properties/methods with Chinese (or domain-specific) aliases so rule authors can write scripts more intuitively. + +[source,java,indent=0] +---- + Order order = new Order(); + order.setOrderNum("OR123455"); + order.setAmount(100); + + User user = new User(); + user.setName("jack"); + user.setVip(true); + + // Calculate the Final Order Amount + Express4Runner express4Runner = + new Express4Runner(InitOptions.builder().securityStrategy(QLSecurityStrategy.open()).build()); + Number result = (Number)express4Runner + .executeWithAliasObjects("用户.是vip? 订单.金额 * 0.8 : 订单.金额", QLOptions.DEFAULT_OPTIONS, order, user) + .getResult(); + assertEquals(80, result.intValue()); +---- + +== Use a custom `ExpressContext` + +- `execute(String, ExpressContext, QLOptions)` +- Description: This overload allows passing a custom context that implements `ExpressContext` (for example, a dynamic variable context). The other `execute` overloads ultimately delegate to this method. +- Typical scenario: When you need on-demand, by-name evaluation of variable values, read/write isolation, or integration with external storage, a custom context is the most flexible choice. + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + + Map staticContext = new HashMap<>(); + staticContext.put("语文", 88); + staticContext.put("数学", 99); + staticContext.put("英语", 95); + + QLOptions defaultOptions = QLOptions.DEFAULT_OPTIONS; + DynamicVariableContext dynamicContext = + new DynamicVariableContext(express4Runner, staticContext, defaultOptions); + dynamicContext.put("平均成绩", "(语文+数学+英语)/3.0"); + dynamicContext.put("是否优秀", "平均成绩>90"); + + // dynamic var + assertTrue((Boolean)express4Runner.execute("是否优秀", dynamicContext, defaultOptions).getResult()); + assertEquals(94, + ((Number)express4Runner.execute("平均成绩", dynamicContext, defaultOptions).getResult()).intValue()); + // static var + assertEquals(187, + ((Number)express4Runner.execute("语文+数学", dynamicContext, defaultOptions).getResult()).intValue()); +---- diff --git a/docs/execute-source.adoc b/docs/execute-source.adoc new file mode 100644 index 0000000..9c1b724 --- /dev/null +++ b/docs/execute-source.adoc @@ -0,0 +1,50 @@ +:toc: + += 表达式执行 + +本节说明在 QLExpress4 中执行表达式的多种方式。 + +== 使用 Map 作为上下文 + +- `execute(String, Map, QLOptions)` +- 说明:根据方法注释,使用 `Map` 作为上下文,`Map` 的 key 即为脚本中的变量名,value 为对应变量值。 +- 典型场景:从外部业务数据构造一个 `Map` 传入,直接在脚本里以变量名访问。 + +[source,java,indent=0] +---- +include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=firstQl] +---- + +== 使用对象字段作为上下文 + +- `execute(String, Object, QLOptions)` +- 说明:根据方法注释,使用一个普通 Java 对象作为上下文,脚本中的变量名对应该对象的字段名(或可访问的 getter)。 +- 典型场景:已有一个承载上下文数据的 DTO/POJO,直接将对象传入以便脚本读取其字段。 + +// 说明:此处引用对象上下文执行的测试代码。 +[source,java,indent=0] +---- +include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=executeWithObject] +---- + +== 使用带别名的对象集合 + +- `executeWithAliasObjects(String, QLOptions, Object...)` +- 说明:根据方法注释,传入标记了 `@QLAlias` 的对象,注解值将作为上下文中的变量名;未标记的对象会被忽略。 +- 典型场景:以中文(或领域语言)别名暴露对象属性/方法,供规则编写者直观地编写脚本。 + +[source,java,indent=0] +---- +include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=qlAlias] +---- + +== 使用自定义 `ExpressContext` + +- `execute(String, ExpressContext, QLOptions)` +- 说明:该重载允许传入实现了 `ExpressContext` 的自定义上下文(例如动态变量上下文)。其它 `execute` 重载最终也会委托到此方法。 +- 典型场景:需要按需按名计算变量值、隔离读写、或接入外部存储等高级能力时,自定义上下文最灵活。 + +[source,java,indent=0] +---- +include::../src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=dynamicVar] +---- \ No newline at end of file diff --git a/docs/execute.adoc b/docs/execute.adoc new file mode 100644 index 0000000..66740cb --- /dev/null +++ b/docs/execute.adoc @@ -0,0 +1,95 @@ +:toc: + += 表达式执行 + +本节说明在 QLExpress4 中执行表达式的多种方式。 + +== 使用 Map 作为上下文 + +- `execute(String, Map, QLOptions)` +- 说明:根据方法注释,使用 `Map` 作为上下文,`Map` 的 key 即为脚本中的变量名,value 为对应变量值。 +- 典型场景:从外部业务数据构造一个 `Map` 传入,直接在脚本里以变量名访问。 + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + Map context = new HashMap<>(); + context.put("a", 1); + context.put("b", 2); + context.put("c", 3); + Object result = express4Runner.execute("a + b * c", context, QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals(7, result); +---- + +== 使用对象字段作为上下文 + +- `execute(String, Object, QLOptions)` +- 说明:根据方法注释,使用一个普通 Java 对象作为上下文,脚本中的变量名对应该对象的字段名(或可访问的 getter)。 +- 典型场景:已有一个承载上下文数据的 DTO/POJO,直接将对象传入以便脚本读取其字段。 + +// 说明:此处引用对象上下文执行的测试代码。 +[source,java,indent=0] +---- + MyObj myObj = new MyObj(); + myObj.a = 1; + myObj.b = "test"; + + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + Object result = express4Runner.execute("a+b", myObj, QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals("1test", result); +---- + +== 使用带别名的对象集合 + +- `executeWithAliasObjects(String, QLOptions, Object...)` +- 说明:根据方法注释,传入标记了 `@QLAlias` 的对象,注解值将作为上下文中的变量名;未标记的对象会被忽略。 +- 典型场景:以中文(或领域语言)别名暴露对象属性/方法,供规则编写者直观地编写脚本。 + +[source,java,indent=0] +---- + Order order = new Order(); + order.setOrderNum("OR123455"); + order.setAmount(100); + + User user = new User(); + user.setName("jack"); + user.setVip(true); + + // Calculate the Final Order Amount + Express4Runner express4Runner = + new Express4Runner(InitOptions.builder().securityStrategy(QLSecurityStrategy.open()).build()); + Number result = (Number)express4Runner + .executeWithAliasObjects("用户.是vip? 订单.金额 * 0.8 : 订单.金额", QLOptions.DEFAULT_OPTIONS, order, user) + .getResult(); + assertEquals(80, result.intValue()); +---- + +== 使用自定义 `ExpressContext` + +- `execute(String, ExpressContext, QLOptions)` +- 说明:该重载允许传入实现了 `ExpressContext` 的自定义上下文(例如动态变量上下文)。其它 `execute` 重载最终也会委托到此方法。 +- 典型场景:需要按需按名计算变量值、隔离读写、或接入外部存储等高级能力时,自定义上下文最灵活。 + +[source,java,indent=0] +---- + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + + Map staticContext = new HashMap<>(); + staticContext.put("语文", 88); + staticContext.put("数学", 99); + staticContext.put("英语", 95); + + QLOptions defaultOptions = QLOptions.DEFAULT_OPTIONS; + DynamicVariableContext dynamicContext = + new DynamicVariableContext(express4Runner, staticContext, defaultOptions); + dynamicContext.put("平均成绩", "(语文+数学+英语)/3.0"); + dynamicContext.put("是否优秀", "平均成绩>90"); + + // dynamic var + assertTrue((Boolean)express4Runner.execute("是否优秀", dynamicContext, defaultOptions).getResult()); + assertEquals(94, + ((Number)express4Runner.execute("平均成绩", dynamicContext, defaultOptions).getResult()).intValue()); + // static var + assertEquals(187, + ((Number)express4Runner.execute("语文+数学", dynamicContext, defaultOptions).getResult()).intValue()); +---- diff --git a/images/json_map.png b/images/json_map.png new file mode 100644 index 0000000000000000000000000000000000000000..1c56329399bf47ffd8ae924d11956d224ca733f5 GIT binary patch literal 183949 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 literal 0 HcmV?d00001 diff --git a/images/logo.png b/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..03de603512fed52cdb6afbd107bd737769d2bed2 GIT binary patch literal 701025 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

{Z#Yfr9;b%FkTHQGpqsPal$SuwF|m%pQdo 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

!PnqLW9Ci4h85Qg~Asc$x->)0)*OFy-n%cq3Kl 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 z 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


DxYaYkHqy5s!n8=tyr=jwObxD7*?n zp|3l$D?3~vn{}ko5Q&P@QkdR%a8cc0^<#~N56mPgd&dWDFuQ*hWa>6Vm@n90tt{_| z)bbod`^uVD?lkCNNGQoQ0*nsxQO}^|-qzFhE-ky#0#3AovrZJ8CI?`R9HYt$f-eBD zWjOBrYvhpy?G$myFvQy|ku#7KQD6$x8LcCWMC*+aL}--*+R0)-+0;70(1!=f^7SdQ z>L~B&%Kqfys!oRkar)5^k4-7}z^R4%F@V!%+8EOYdVwjrt-xc{2t_DF9cv=Q8#R?UQxRtADi{PE2P%A73=b!!Kqv<}hdg9Y%2Z*+;w%eG?qfOtVFW|~PL4Se zp&aZ z0-l<&LSO|Se+*OGQZvr+N zxH3Kkym0NoaSI%ws0hxf%X7Os7w)?G_V>O1E#Ldrci|ELcii*FTP|OLnGHfei5e7n z$iSA#;WC(0=dE-9>zDrN&wc7|UBxxPxdpx?hgKgF!4Qk8RVxX6W(E;7LsvT0+R~#6 zezC;*unMf5Gj|bX?xxBP2s6sWX?!dwgBlrO^1Z~vKg*;4{E>hCr{Df#>~g%5fj{&Q zuK{=l&6uENQI`>H0_YiD07?XMqc*`71!@vWflUKPkf;%%;f({peBN#xA`8NBBBK{G zQFg?L%TSDoH5P*K=RhIY#2mF42$4xiV}StWZf)VAR<1}0a1MXAzXVdyC{xb`wHE*? zUh1TP4;Gn8AZoM=bRC|1h8kS$mi{AzcqYUg?Jee>ra6vMMDm6iu;*2$3}LF^G9v6| z3fH6!LgK@-@^V!Z2n4a}L;AL8nS_qnrA{2TlYE#=90GcjQ|wx=Rjq@0 z#~H%3Ld&!1I^T^V8_11IhzjTri`-0>1`UHe)a~+`squuN`B4g|!iX*m)hRPLA3Npk zn$Hlyt*59MgV5ToS38X?YBo;JuD$npzdse?tnf&2BDMD*c*Oy}~ zHQ~U`z(a`ch8mFg=zH_PWPPON_B4bRN}A|^JC24bn{xA7ry-1XWMF>Xj*p&jXwg|T z53C03I7N$&%{+#1g3Eme$1*%zLfWH){U=`h*6%<2_22&1mwxX%k3IUrQ_t)@f8z-E znb`>ZMo}L3G~H!X{^@0bY;+LNDem*^r z4_*NCG#@k$0SHfFWd_ym&Z{n8dE;%bd(Ru*^6HpS>*Fyj3OTYE=pZZ%b9bUV*g&)kqha`B%TbIc&a(<__ zuPriWvd}VJmau38NzKhU8SFJP69Nh<>4~*em{CJSgs3(x3S9!hotq0pj+W5Cmy*q7V9XfLD6up_;370+oUR1TabI69Oy>!~LZ==c>l}9(OBV8LQ6( zI?-j0MmZ>fc`LO}NOsq$7nLT}M9IwIk~k`b-n=3dm1hnoL&e46!7<|2S@QkWkR6I_ zGmAzhJD*L>wL|r2`_q?FD|KvyYu6v-pfuwjT@;J{u$9a`{R&XWEVgOm;$}{G)s|qHJT?H0)^Gbdk(&b`i zo`x{gBchplj2NvDCL^>$RIw=%a>|x5)o#M6fZ>8=hN+sU<3!xfy_~xGb>LpquDuqe z2mtLldVR4lQ4S*il{y7kv#@C!?KW$k4cd_Acc*0=*u|Nr`&erJA@$XSN_?hfDgFbi zO*v$M-bh*P;oZKlQcr=C@+pZjso-;dEFo58>}*cqsEAAtzMNKYFICFuO#YGFWQCT% zvtZLJkS14f<})*N!yk8dur2V$0FQgPv3~T-wHN-`^WXgZGvD}w=N|jQb6xbu- zc;;SjLgFEQ+^+Iv08a*RqTjil#qRlwx8HR08*hE>19#r@p4Y$iJ$JwL{@1_)a3JQLE>5~x;T6755gX)~9BeK%y;4=`k*696-;H^0`+xl6C;#0~{#VxzuU)|R z0wF6@0GY8h_%-b@@8v#XUx;QSut60Eam~*pxoG3V zNS54!vTEhr2^0wvLDRAwfJ6y3CtU;gVt=gV=S zP`NF%+1)@;W-x5=f{VO}P7CFr$_7s+!++ z)(6>`tSBn!4J1+GzNO(hB*A=I-b&O#F5ISj;|uK0wi!oi5tB;za|pG z7nUaZ2*~}2J1a&Y0g$J$h3gx9Z<3dvL~zx^ILHK4U?dkE$r0&W8M>-5PBt^FnjyyVWr|}t!=}wA zBOKr~FX1Fv3;G0)M`Ml)2el>!2WIyENC1zWtL4?b>rcP*?ANY-`zu$UdhF^`5T3g6 z{4@J6zI1fs`r-cJ@u9xx2mDM4JEa&f)_fSn;Zp}{1;nWgAH2M<*xf$bdG6_}*Ppw3 z?tovxgBk8(vADc@5fAX(apl&#Zn^E=+g|sU+wOeJYwmi}ZLfR7t8Tyb%9V@f@f18~ zxO_q#^-~4eY7|sZ9Gvfb#`HA_kLO5=sX^fVWgXB}#DpD}4!EdV?jVDI@P$wOVK;&rN@aTXWM7K&fMol7)HV()&i|jCYFgXk2W$^E6AuZdDjTYfm*@&xpRg&|O z>;_x;2hN0Qnb3@_F$E4Nu+D4oE(W3RP;W@L-pxsp+(vu6bg zxw)33vkgFGJe#vY`@B+iJ<<`KNYVc$;TrvBtF(>CJvoqVbvecHl< z2A50*BV4tymX+F+ejuXWx^{%h=vg-@VUI2LDTInE2k7j)L8v5b2R0LyW30q1Tho@O z^z=%K3N>FvnX?x;>2_1O0*(Klhty|v5}ytQY&1C3@KRIol*WY7rWCn&R7z~CxeI`1 z4JjH38&)xA7Hd-P-elVy*IKd#eZn#WohVZ|zqKdR zo-ENs)J}3ra0N}pgkf5o2^ERvCOwK-3YBtXO-S6uf@N<5!GxUD$mE*Hf~f$u&1^3_ zWg0O)vNanR0CyW=PF(0~)c`L_32RP$Dl>$1GF=*dIja&@c)5ML3blwH9Yf5dp{r}& z;E{f(=tO4`_cfco@l6v=OqYHq${MFDWfD`n^vZp>%4{K9sz#|+J2x*b-+unpR~9$jytwJsOIPp=&#Nz9x$UN#Uv=^F zfck$Q}ZJSI0VgprmKzZQL0Tb3iHzK#gB1Bv=dw4?W!&ambG`5P?K+iMV(s@^bIdWG=NV&O z)_O1BWfT83-kH4nrGNNq_*o!)AK*Lqz3314k(+m*>WU$q7e4s96H|+_O3R|41Qq9s zyQ-@*vh&I-8tad;_y&wWWlt~;i6NJ=F&`X&;+OY1di^z@3HK755S+Z;@G{EAZY@us?lpS(qo>e)WdK(r-dhm#+IgPvCuG6xmrLS?z4m_ zi=7Xzhh=AAF+>cHDRXioT(Yk=AVm#jUCBA-s6>PFY2tDX=~HQ_oIf?@#i4gCk(&dq zt$t&~8Wa>o`P`(Ogo~bB;dXpLNOrb@4Wp9|XPXtap#b-nNgfSKakfOhOhaeoCkhWK=(UU2PBj}^NV@}`I5zD z_w}V=a%`Fn--?0ZXJJyT8Y8Yh4#S)N+mFcg=l=Nj@J0nc3-nLF_@_Vi{ylz(_U$|T zg(R+-XQ=3!M>uuJtB>1IK3CqkoDxNns|GDPaNxnJ*zlGP0}T(B&PMMgIJ}TT7;}Um zO2WH^uReVK*}ETn_Tj($wf`2-KabxB_`%yZ-~Nn$Oas5xz`q3qp5BzG2x(F`<(9dq zD2+^SG`kUwhV`g!0@fpH4+0029^zoj69WpW^tp%}oobE1xsiy9U*k@cqf^a5Ip2<~ ze&`%fQA!Tk4DfGZ8CAi1 zR}ZkK;||_Supn+!tgMX?NfuTnZgwwt#1erW~BZtb`rB9NR?!mZl7diGt11S?enSP_c+V zf~};N_E16$Nu^w9n|Cb$J@5s=CC(8&Jl=i(zxciYC7Mr92O1`lut0F8VCpkCxJzIX z?etw;_7OE1X%|ohLJE`itZpdO+g9H%o_p4IGeH-x=5 zMEgDqKOag{)5mZSX!?-DaAg^l0l=u{J}MQ9Y79To>A7ULnS zECRV5YkaP`Xq3@5H%HMjkvW-qdO(2xK^iLh`JeikfAuH-mA~_w|J!eW{JqcL<8yVq z8RAqy4DS+Ho}U7h7JbxTRq$n$Q)E%-bLt3Fy%DDEe=LI(LiDO%qvWueQV|)v?X$-^ zgvNWL0>T3W002M$Nkl>^y5GEr+)fpKYj%W{$eQqE>LVS)=Hk_ z+)@Ka`sS-Glwj#Vu*reQRVP)VUd3IU`6;g4kt@)w#IN=>kVW6BrC8a9A?48zRWuZK z15M5(^!ejrb_>VRcp3v7-ea>dJ&zC|m9f;74Ue}4+^*jc;eeE{Tmx(w?VwT&QZ>q{ zk(6s)sdiWH%GI%HAslN228V+yT4(!j*xIO?fT2YJJ>cWQ=In&;Qqcg6sj>vXL(X>t zrm{#SC0HfuRPH5tdu2%$<*6guFcX1xD#8w$0C5V$0G4TeOzZ`dm!mTX7a(PjG)Sd1 z6HYe)iHiYfKDDDd<`tz#DFWT87`YsVT6GCV((nm}lOF|?TRo+TF4cG;&P8t>-QRQr zLcEmPa4oS>+f6G&tM@_gtWWPL0%y7d!cpC#GjDu0AKX;PC|lPp&ndhBCJ%Pv(Wbc} z4qYQ-`(+$O#BKi~1pg>839oANDti^F!7NE7%&R1Z(Wox6t&YCZniA8#kpX1iuN&(! ztyW6+hQ3MjLbpY4B~JnR@)ZKJpkFW>)!mAqf3C|G4w=Uq$wA>cA(_AX33^$ZS8Lo; zUq+g!g~%#u0$kFyu&Zb4wQby#_pmDZ;laUw!@g*Zfa^|Epj7@BW8>__yETw=2HD>%u`(BxB&PRK+#||^NoC6e zNYAiy+G&dIKvq=)H%uyJH`~flq7W$^6qZ00kOdj+o&gDqda^M@XPj2h+Dpch)`BZ2 z<-L+?>_N}@Ex5{}_nP81GP}CmSO0A#DBH6P{0k|!S{bm;H={PDq6Evxgj>t_;BC=! z*Vj9k-2W8Nb+BL|xvN&ccIv`-Stk-ESW*za79d1jM0Eux@%K-wA^7&C`v~Xr!SU|< z-`95lJc*oBJ&_GY%`<(`$$4Ub#~Y=mSebyZ2HoBJt^$wLEC>K$+6a_%Oc>q(^AQ~R z0Wy+j)`RB`@^o}WTJTk9vm@eJ5=bW&WD3qU;!f-Y9#X0r<3U86jd|%{PbEg3ims4^ zE^R|1K{(pKXVQevFf;2*3VJygDU8~%D<8ppLZV6RLwqRjO)pMIN(Xh}sBR!;pEj#^ z@pz>GwEt--#Mtr4K~TRI$piyKjnR#y0TI-AV{ncgU~j+~WL+MCohFo?6w~JxXBtdT zGEIDiDZ^|{JVV(fF8XS60PO~HjH_NiP@wdU4joJDJXCbaxxxU*Qw0s^5NPun&vab1 zdAOH8uP@TT;Ri;8j{7+CQ@;SCr%sFu4R-|&i1-eOF1+;U)s??n>3+s8xS5iyW)vsh{XwKlqGSN zI^GF<`0ywG(9itLPyCU;{eSP-4z;R^SkY zIc|KxQRQ(VfK+CR@L_^GEDz39bC9c{H!Hy4Pag660AGIm@XPB^!`nHdatYa+fjJeMT3umxDY?D55$LT|rbWk5-)?tt0DM zhsdIM08$)#>dcu{tWK)DdIE+loUyXRj9kMEd@h*8I$2o< z-UC*C2LLmHqs}9*eHP<{`4B*-_90U7G<*|lfATMF64rn-3|)lx;Sh6Xd!rl_C;m*; zt*Ds*sg(At7J^yESuaau)H5dBGu<^y^tMX2Zh#JD=(e-^*Pf{--?5JSObfiO=EeKk zPF97{rWs4Z@vn1GJ*3vPz$q{1ni*oH=g8KLFunY`ks%e~olEx~I4KOOB&N=^Lrto( zBZ$V2iiGiC@=Mpop)5X^lprnlbdC$V!qYfN&2Vc@FKu6Sr?@4D@CssYxh3;%nDtUN ze%gHwhzWW4K5qq_Yp2MLY2|YT5zOLNwMISMW!e}zs}8Enxo0GZl9?x{SO_=cP*ix> z`EqxN#Iq!SUWo$l@c-z3{Ih@jd;iV%{f0aQ;rl9o`G5MG|KtDp_da|7 z_Ol=0O$`1Z5dMNGl7O#2@ahyG=dtcOghvs>GoH=S|P)pq?m!ZCrYq3xTk}K3mTeGNXuQ*W2 z))5H}o`D3gEp$5VAY2wtb|t_w)xFnRsTh-P{q?$yjC)>X@=N~=akg}u*rx=5#E7=X zGdw}IugDzX#L6cJOF-|Q#u8IvZ`S}c9?9(Ft-oq*!R`W<$k?OpXcVlxZOP2jg4jE$ zyZgIuMB=msI(xbsPAk3HuF;qFUNEnjAlsKnOICz@x|mObXN9-^C16f3z{kSibQO#W z!4q752ANk&ZW-c>Ca_Q9IRGfp7%Z_!==R%b z8)scF*qXUp1~N>7BK_7(DTge(4acDk3?zk>SpcU2Dm9qaVQmWCA|w(oiOJR{@}*3& z+1@_Tbu0~Ef{~Si9JUh!VnaSvMi%+LfiY@vsUea70TO;GoyCi?zkB!PS9nQ{pDc&8(tzX_DN&Jf z=qJymrlJKw@%a~i;yWWX43?(%1tudqrz4cq_y>UaYvcGwk6#yMpaCQ;%_ zk5D3dWFBQ{s6xXtbH!jlXOlVzGi|$JmO^@Sun{x5P&BG!L30^2pQxC>7BAV6M@dT~ zGpUU<4Dur47%SeE1h-4FU@%M{tEQb!cT`apk^HJ7Iv{MDst_x_-BEfw+8QBOatzho zqN8b5y-u#T?lqVMZEbT#U{n&`T)ur4ID8zro}8v|!JDx%35|kciigK$oQt|7oDd2i zh=$2!85@jfCWQ>;<@7R+FZDOj)9y1;UJg%%2!L4Vp&?U~iCJWojCdr0xk&0qSbdW& zA*)`X;mEawxTFutPRVeu<|Yajt}Psw!6MT80daKL*KnANUywcJX6SmjnxztgqDboD znQEb;@MpkZ!tZC1E1DeX8#x+2P<7m_r6Ocr%g z9KZo;GkIHUK#-rJK?bH_;E9^a*(-a?^x|$^+gfL?HlH9NjBDMOU`V|r8EQQgWLJ-4 zug!6ugSWa$q%1-%a7mejVQjc>4u^iV7%wB7@h)c<;jE)`R&~a;043 zmmUeMLPSl0#$xvcE$G<`2B#{6yTFVkW^D%oGr`aPy&}u2|CKU(D4V~+PFEF?CAl_4 z_Nvwk!}AbqH4_hqr%731=EhfGlb%H3x05+?=5|tb+=sWv2(s{oLTwZQGphkcvzUEL zo7nW3kmMLBwi+@uAMX;4^goEHIRlml=XMlP6$H~kI+ zpl?>5F6mjF5?KS(creinm5qRl2v8}e0fA0f z=!~wa?S&;-YttVYTPwi!j574a_P_E0Z1f2d1=i%#@HB4yQZEbMZ7Vm+f}(ZoOQi0&fGlI#tp+hocY^k(1VrF z2Usf9YXDgHWyA(qm$rDFQr32o>cmca!&Vm<70ZlKmRtqc3VVw*wqhdX z5QMM4EY(!J9%G&GV^8Zs%C?f84M<2dqEM!&^-G??+>!wl2w+~{M*A!%JPOsTUh#P^ zfMn>0)u}R@+j&;X7!UkMiS3b=#XVal>UTL`0BsD2#eA^(vqL5%7mM-}(CU-~I66 zFaFA3|69ND>+in(7XRt;XK(Od84#&0$6{2d&Qer2PY!19*yf~DNSZ#Ud*f_^=!&Mo zH|Vvykpr-KEr}w$7V+ffEXsD(EssC3Q!$tNM;cCc#I@%lG!R}HgPc0`+!{IvaQmTJ z5?qBHjYLUCo%EdGb#*8c5V`?0(wj#!#N845qT^TVoQ;#y%NZDzIED+cGkaul!8l8C zyc`h*Vo*Tztb+@S;gHLAsHa(>GBPsTwe%idWDxgGp#7n;iOoE)8@>fZJ46pIFe^zJ zl3ROssLtHezA*_oXGQ?nh^g^PVnx=U1(}m)LXy~;$uyS(p+H_jyW^#}otJ&xW!Whq z&qK^cUL^%fo?89yT=v8i*u8*4xo#*=-WfL+C8!ElMiW(_qi3v{uWJ`sc!7-elSqwE zPAoeF#P*Mn7F`t$`k`5 zNazOoeAi#4tW6)jwhP+MnYpuscNRK?<5`2VIT0lJvsI+?{D8!pexj5>Ct?-Uz{(|w zI??EZBBO|53S$j|;;=a!8DNz)pV|?KEOOD=x!I2}n{;K%9PkH%Js!&iJ{)4R?(??-yo1r`!({ec zmaB8Yn0n4W{#62+>HyAb*S$coX7D+9g!KMFdEiEo9yOz>0We&p_v6_2-{rD8<}d05%D&#G)Xj4Gf9y9y?}l9Wi(6ocPK z`O~5H&P#5@XacMIFeef-qVKuD(wz#kR!OFI@bnx6ATcR9VQp*S%({`pHIlh9r6fht z^K`0Zn9sJXK8|!}KiYwyXF(}8JXG3^HY+W!^d1gDcDH7DD_>)GaF}cQ6<%Q>Qb@71$^|b(OFUi7FO;%6n zRlevcD2K@VB86fx+6Y-USSBqqduzUkug+B2)F)_$=pTKXa8A4>cTcg(h}5VSuHCC( zIM-Ni;-E*fr>p4s&XNVM(y0Y+WeMRUu#(WYAksmFw9U9J*5Q3RtCkEuYmvA~vb~)Hv3XCNTcq^CV4>E@ZSo-RHK9CW zh6>cQ;WTHM2W>Y_mg1PCOmm|}x2DpCRsCfo&#PCpGjGgXR-$e?L$_kCXO5pU!k>+6 zNa$t8YK^Fh$_VZdFy-P1CHM!oZ0 ziu#Z~OaSim?*l1vY^nT6^+a+BU}jlUqkKl)mJ5!c02F0U){|}!#jr19@kHZk%VD{* zC|61fOm&7Zaz3#4Ck1I|feS~ttZV5kuvc_u=9P9IS#cbBmc$4@Kj_lpCcaauU(Eb7 zpi~Pdb$MqhOk28nL4+aRQq1vfEVSt~LU%KLVjDiWr*2Sg8t3Q7L}BeXxLGC|*2S-- zk{yozRmh$!nG|}uV0z|(Q8SlZ#TDH1fk2Usf+v7M0-rulw>mbPC}5!5W}JuFX{Fbp zEt99jIW$+yt2_WkRuLbbEE>`j4m^6%9596rEU5*E2SX$J=4h~O(?aO?D6cdt3L=LJ+A=19M zTY?zclGWY^wsB3DWiY}Qgm0l5U6xPr_#{qn^qZ!UcGr1g0F|`jVN~Bj-5tUrS44`{ zn?~n~*<*QCp?ndbxY005qKX{G8YCS&lb4V?iz}61;v5;jDHC@`m1?~#0xvK50w0_5 zks_h-~2zmc=HAR)(-vzr*1qz;xghHR}wuu@zDQ&hE{@nbxuS78M4Ci zkNJ&#s2Zq7QDgu|=`}mz_abG(H%#gCcyB4e+lM_ph zZlvU}V-b48HWF?<+-d~LKm-FV9^E<&?G7w~Mm5Y4MFtwp8V{D3p_Pp;ICV0LJx$Y> zP^3$AwOsXF89TuVg%m3AX7JJgmVEw1Iu43SjhTlmhi1h2+`+KgF$hOz$206H>uWUq zM54MrZ1Y^;oU9nxijP6W2&psWrgG;H=MuU&f-wDa&NL)nL8?ssnFRK@#75GoxDjR- zFd?02MjqN6zbH8o+7g_WsF0!=YbB+Mu5R!hy0s=pAZLg?NsF$Vg|c)2S0AU{pGwhU zBzmJpMSO++27<^1@e2fgSyp?^T8d)+{lq3edk=xsUrneO@_Yb@b>N3 zUwwT0<=fx=`omxTJOBOP`oDht&3oj+`vW{faR=sIV2#c(X|nE%#TuSNWx<>ma5He5St5 z?Lde9)Z|GniADX~X$4g*>hzJd>UC!N*BBh$>bryjI4#G8ThX8&R0^^g%S^H3;z}WN zm1EG*P;G6h;r0K_A?Vqv3`st}D7q8Wmf32_ex)%cH< z11^%#YlU=4Rk5tiosLP8gTt|mv1J`%A|@~YH5+1MXa$W~p^Vv1#_kNEGIC ztV(}opEiX$Qk{S%V?f}_ppq1YZbtCM$^xWtpX|k%6Tp9XjB#ie!#!LfRdR@h%@=~? z<6#}N3aOVl<;e~J0x$aF8#b8)8oduj63m$g&C!jHye?86C*^ptQ~?iTC*#6{i^J1l zo1k@OBD^<4txZ|$xKkUMYEpNt*Ys83fuO8fEPsOj=1=|f&wl&ew}17Y{DU8S`~p8w zulE4_g z!l}y_9u*G{-hLtvf4&x<_hWfJzWJSxzx#K8*uB(l)b48vlVmjxF9eme}gk*?BYjG~80s&P(d zyO@qeLKi~UmXRR_XY2$gHc`5Y=z4JJ}({lG3&%!jIBVO(|W2mI4w$>T(V zyN_&|HA%_O{7OTGc|-}*)%JucokN##O`ITbw9g%8SF#w3%4rZQUWVLNWKw`*2z%^# zpOYEMGEL^rNX$fbpKE4aFGU6}@g%fd-YqMeGQ zOG9O+W#0bQZ~azq9N<;Sd-TFl$xKy#Ua&my84V2my*BmjY}0*E^PqyMIO`TLf(aTe zqxcx8K+Kk5Z5;M;>RUV^H|><;Y{V{-)`QwfvUuubh;e(NQ#3NE5yfIELr57 z3`6=l2+Fy^+^gV`K2x*E$f##p2&n69=)r&^nc1H6$h#^>*^HR$yLe!wL^6*Ye4bp2&rscT{rbj@}>xSp68%2|-cCcxp@bLHRtVnd#kbhZW&YNA9;wX;C&JXk?qBa<4L?W)ob-B8K}UlnhAo6s%p8wq&X=S?Gu z#`A*fMk`Zm`wxEWAOAnU{r}=4ef(GgMy^fB1>h(Bx~{R=`C?lqt^)_tFA(vXo`-Lh zeD}bEZwAolB4H-T(nD5|tA>pMZ5IRKQQJVfDb0L9+D{o<<+U;ob6|IQ!#6W{&z zcPPNWx(BfI(SLXVL_wvces_my1v_xYVNUsN*l$32UT<_Bu8T&kx$>0U=~Z1#kI`g7 z+1@GGzoRm>{c(RgNem*)nxJ}yIdQZ`29wTQxf)zRZA%3b$Y^Y$P9bH&g54_G$z6dl z0>){urn_v#W7DM$YEXup8G=j4#^hrlu9`504dMQ$uzGfLsR?z0(nzC_>G*^;#^8Bz zA)(?9(?2^z_9}qnWzzW>QSnUbClTKnIPlXkCxVc;rgfK6YQ8J>^paliGBDXW#fWRd z2|Ns+ZXQgNOC&QoHF0)w6qVRRGSlQxA<1{CDU~l0GEETC;E~f4ghvfG(7=bxWbB3U z539vyMlMWaZ~`|51W!2KK$Y0}20#HT6w%2Mk#JDe)u%&z1HkW|>!x-sRGv8`=qPJL z(GAu1O)FE=ryA5G3o~Xtmqg6n)|;YXhc=pg0}vS!JCnn=cGq4~)LI4r6{~v$O!rYB zZxMjF)LCyLC315DF<0D(t!5=kgi_|bDJjF0sD*GXH_49fm~*QPL@1r=Ij#x?u3!n? z6eTo1b>y4vZHtaNJn0^w)woTNDpon5OrKM-u(N|Gj;@ddWEE+|79IPo5KQ6i+0Jw6 zGt}wQ^Q29Y*Au6(Z}&Z?x-q|6G8D-xE@z~5h0u)LPm5JbfL#pIc&ZI?_0FeNA8O?& zr6t8wA=)+L=Cu_+asQlc&Wa~wErh-d;PZa`;h#77;7@6pYnt$S!r z%Sk?`k|{V-_-P+%$0ypEnDgBSGT^BlFx_}~JBVAtOqTH0{rI)Nr;w3PXjO(6u}Vvs zDv`rT=WOr%6Z6F8V7!Ys@t$cH@?!B(8r34<9L*|KARB`YYWA|-$~R>?S71JyuNf!a zws50Vo7SR@{eXRw0F~99t6bF0>HrII&SwwkL^}IC25iAa$Jnm$UU`&|rSPhv8a7(Z zFbpE`xIgA1-lHBxJQyH6ngQixjqaW45l7Thiq7J_qn2N(H2xsoCHB-64+Sz2Cr{(> zZJsWz4slSDI&B87$Lr_NIjuA`XVqI$hoVk$Tvt{zRqUHuiF(@V8>p|~qaMB*((O$p z@N>Lab}nV!;P)v4DC+b71niBgIrq(KtMoD$Wue!(Lro>XnXOcoj)PNf4~r8|U)(M6 zqD>Tj41`e)iuaRtUXIlQzysz)yeM(RB)DVL01;e_jILOTC?r2lt|U-d7Q5cpDIb7P zn8ya(hCVg zq-6FGMJ#6)F+5^Lb0imeI>g7RYw3>ugVGp+$g$_=zMx-oNC8fVrliy)A9ox{JUi)` zW{9vFi7*eVWb$-SOkL-j?g)%=O`4D%hN)H7RmrOGXKBX5vF0lY7X>`xzHHD|8uCb# z19y>VEoPK-^(oKHKw_sPGWY&KX^MnPxT^LPdUq&?N?^t4`8%pnVbmk_p-bp`NFOfB z)qOFRN-5}%ut1)^RU~^ZTQurcVhdF_;n5OG>AUy_e__{Z&YZRMU1Eq7#G}Ly(0sn)O6NR!X$0$4>u<4fAV4>xzIdMIx@s`N3X(zW|>Y z39A1F%hQStCp<736sTyKl_L0(2$Q_}5YyWzfV@DXKkS2RLlW?f3#W88qHLqFb%#&(SgrCl}<7KMjAJTU7F^O;E<=yyFvB-BPU0=|u zgm*wEB=QQiCrXWtJ#?bfeKO&u+yH)>86s|Q_5nj4yOG6_G7?wq4_nA9_}H|3^`C=h zO>kjieOEZl@S#e6e9z-qUQfg(Ad+Up(^BClMxk%BM)48XErUhsN#al|My)N_c`HS@ zqJK;{oJE6CcB}5zV8R@|=&WndaSW(B{hjiuUg1j>6`Amt+rD)P@4JQ*WSVRr2{z_j z%h4ORsCxAu382+-Pm-1{NgJzTiw3+5jQz8RGkeNWIx(xJdkT6R7B2C@G!Q@i?9>&7 z0gt0}uA3uZzI87}T=NW(pjFfIrahyP-p zO1*&SKxQV_7f#~gF0ar2+2tDozBa&$@M6R`5ydNVo`X3I-dWwH1vt6H<16iSrVc+7 zFf6YDA#vi?pv|ga;79)bngg$&e8{h&^j(E;b1XmPA&Jbw^3Dfq!?A>y&Kf$4+<3LI zT!JLGEeRHae%vTbw}97T3zAi5>%yJhAlO<9T^oR1>nW}YK$8)jZI;4~#yN;njUAws z^wsO$jM;8a6yDmB@M1Mo2R7m%)rP=DS>)#busFRsk0NBRihpf3nODW~Rj z#R{6TB$`Zju0ncZZd4KD9wP|KlCtW^Wlyt-bd>BkrX!&aF;AkxqT&q!Pr48{2ZgKF zVo#;vyfrylIGo-#y0HPPpBN_1xss0#892>y>0^y=bl0_2!Nz1KvTT&_+5Js5_3>iS`)MBzWV~)4`C*7q?MEhC<(L#}*~yfKu#S4azk$MU!*~UvV0F z3uy1`i0X&rQ09u_MGWqqrGaSJ6vMpsxUo5Kr^pS4_5**7KhTCLN?xM42)^9l@kNR| zGXJAVb~vTz0)rYpevmjmzQRE?_4%Vo8bAr6%LE=3vn%56OV1X-IR_h~mggXnOP}y2 zCisq-@k^Tn&`jYIyBJZoWEd31T_og~!7L(0HU`5Dq534J8bL-ed*T(1Q|a(hEQqyE z_{pU$Ry(=gNxRv=8w2R@bbyxcUffQW44lQoUDOsyxIzSTXF?=+3Lj4sV>wbZ`!Hq*7dx@ZzHL*| z%vRX zMv>8Vxzn?OKc#!f%qtreArdc)d5^^E&joQUcL}dAs1HaCWRAs(4|E6qh)O56>C`=B z&Z%^W;0u9!Jq34#JO7T0zMf$&2^h4H&qsh15K#~(La_?PJCPh*imwb2lkD+yASdpg z>)ngE%E^|zQ+=s-*DB9_vZNXD9GCcOyK%Sf6<<@z+hT@k)F|zZZb1@y(;C-n+As)} zba}6y3Js&F#=o){D22ky(nX=lqW$T6r^@F`z*Gs*8rSfT;%`h`*5et-QYCodtkO|W z4y*4Fge7z}FN%*G7`dmowA}T1VHUJwu)yvVqa;Iy$X=1zs<dARxFU*p)*IV1R&au}cO66iaN%JEOYud){9m!r^pZ_D7i4WVO9hxt= zHWJP20yf@}`ssYi!%rvTDBX{wxkb>A>4qoXsvPQC?_~u@6||=#+`5)sj`J2DB-`Gf z(;pRP1_8NCsL3ylD?rYzbz(?|?j5i~v;3xXH{hPd=zxV=P+pv4hsWf)7Zr z8lda1kk1Uz7ct>m%=ER7-g0Qfv2(A1^yrEcR33@sBv^q1Zb0(bg3E_oOvEvB!sszW zPW7rfxEy2S_>xp=Hz!WH4pU)ctWslOqO0Ml0L1;5Kt zo7Wth$iy~2)MwRk<;p^bNFKDpu1r=4uR0WnBNitWic|)1g(th9c!FInb)Z-w;v$<= z)J*uW<-SdQk7MOi8UAq27@V^^?Muc>5RQv4Sut3w}^JyN1mCrtvB$G3y{9U*@Mffpg2xH9lrxRiO3@Ke0f zhiws`BSIFCg&Jcyn1Np4F^)oq97mrivt4+0W-U}TYMHF;ojdMQ;qDinFIqW_== zeo86ozQIjtCc&|l<6=os%kcRa|3pw3u$Dis7w{#i%S$2c8dXcBejMuusih*XM@n~u z8c{jg@Ys;5Q8e`gIDDBMa55U)O0>vy(8UlZEKcCbTp8-Qh-oqp3FPUc4&7eJw^9Zd zMLuWyU=g|~vU{642kFhI}n_lJi-eOS%;|8S(WiSzP$0>JW z41!*2H)I6b`N%pGhGLL~!)ZQY!dCVx`j|1O*Vt2j0W|(uvS&Aon=>)%hCNBw3Z|!$ zO=m1qs_d9t++3f9x+K5dT9izgUEXr-MHyKMSqjk62s8Hc9v{UZl+lv5@GcVlLS5q4 zxhz#DxG=7cWg^kZY}sBJ@c8y5gK$zx5Wnhfo|?-_QJ#rBI=3PV-*w1>at@}F6t;|n zgZ=#^yxIn<}VjPD(<|goLZyZ$G{K^ zs2T_M)h5b}!H628zRCckzxgS&NJ-1qH!)toG4zYz6fe6B!TAD-?;<>ZG5L|fP2th4 z)PbkO^i7+wn`0i$QO&QpzetlAy|Y5nwr65 zJee%H#^=N>uLQY{b{mfcp#aN#euO-kPb zA`C6WNOm_Gi;wa=0kM4f!XUm0;I%C<3vJz)xr#Dm^-;zrnAC02UEmNoxGn-N?1bDC zs4ut*5#S=j67>2!wHs~#4~_v?t(>H23;*B5^-CP154QQ zmjYT;87}do16sT{=mkL;lijU9 zOl>|cK0dDyfHSmV@>~c}si~_qS4m!;2v-LaM-IO1VrM;uXPAkJvPT4-(Gw1R;Q|a_ zm&($DA{d7Qfb7Iiy?7=djDL5))CtJS*49!OEc1EBNGhUrR?gldxbmT6%^|WAh!-fB z;&YAIe|RuWg7^mn!{*ynD0w{adR?zlcp;-!loJ6LCU-%v8F~el7-_yuE$-BYb7*Q%5Q?dY z#9Ci&Umj7GdxPrer=9V#Y-}NB;0})>wBKsFnQ>$#c65jN!b)N|M(bY7%-Bnig_@1h zdfB40a#o^hD6*7HG3vlOj#wf)Bc$X>hG_gLPRWY8N;qF?_DzBK1=#`wSAmV`VmJx( zJJtvimqQ`0+VINGfjZ5I!o1TnC#7`byn~jY(SYUbx4w4>2Gzg6-m|loOu3FxGq+(@ zL&WP3-Mc}%xkwL!F`;{8n+H_YL*8OL)qRaegLyb2zV-+`HBMBLzTfAKB_=rV)^(?H zlv+u=+$fPr@hU|~w=8C)fde-M*92EorF|}Y?r1Akg2awQ6&t?AJa7;P?HtJaRbDAK zs3nCLncqC%mFXSnx}H|IQ~@+M;DtoTsKfCK%AmM6Z1u4_6xapd5?m=H^&@fdY)#;D zoU+^n^5E`fjE2M}3xw|CvE1Sc zG5VG*K1;fYY-ME{3lDh))b(vAYjVybnjr=v$VOcEx4Z=39@w7=x1f4doN7H#OgJ?{uAD<`<{|%d z{0Qxy&$g7VYV!Pbv;wRjQ_9Du75JP6M2H)!iBn#t#=ND3@7v=l__T_^vL+Mr%J9=+ z>uL)*DP}~53-tVurt5;_Pb|UeMP?eh`qjT*ND#moa1jL$V-v}+*GoN&Nk7hW<_mHb zkIRaG0hR~bM?*bVVrh=;W&k;V>kt#2?StY~98bW`LtPb2tBeV`RHby*@fm2dqg&op zrhC5dG3Q9=oTgxSe)TVB<0%gpSN>jG!JRlXUSv|g^+2m2X2LPvD6$%Rxszq}T`NN( z#VI8A5Or>kxd6pKpdBlOYg5;gmK3-=E6ak+ECUHMegue0@eJp9WlH#cdmP_za`xOE z&U?9QBxuFfo;R#~=z82TUQs>p^DdZ7v}rn1pYQw}WYq%$ILvN&R9L9WX&s$$s-Br3 znrVM#FSUqP-+-+oB$Z)Fvve}`Yy*IXmY->7{NBc97|VB$>xt_zW$~HM z#lw54ms>6dFEjE)z3$~K2xv*`Y-+~&-1XLGW12CfLAJRN(eWdMW^v0X%o^ND%cmmv z_^N;+H1p9#Uae5nrJkiqKzBCil#|lQx3qh3wjZgpV?RsND=+F;i!)^_o0OexU{l;zcUwZ6c`NYF{z9Nxp%-S;!0420x-0Q8+FwtIyWD6FzO9& zB|8tX3bUMCLC#&&2MQzeXxB+kTvnb6llo#?)iBY#Llzr#o;Fv-SEH(of#i7Tuh>dC z^pbT6wFIvMz;fUSG)Wfc1Qp2IkSe2$39Q9}upf#VyCjppJ+bPl zYF9=-U87BNBp|HZxwpAX>IgftmH%Q?{aGR%b^|)aCE+E< zPjsrI0d`Gez6l{&g&LNQU_;da-S@)jw5m@h^|{3f&FhhUy9{5rcICN6}46Z|E z3?nK||FR}>lGnwrwX6%Y#yU~>@$v*hb$3#nA%}HV!f-GiOhNZvI0F?(ZA>67RFJI; z-MknX;a6qi%y&=b>L}44i4k?U;JxfMV@9)Ly(rCs4XshFO!hRWW#mP0HHVn6(v$^mt?2D;E`hQc97OAa)4{gaag(HqEShLUX=ir8*`6MNX zYaV((G{Y|f=N zbo`EyTbJ4xfTuK`e!QhUbAY&F%2I|u?o1L6KksR)DM3Ui76{|EvtpjKjyMmbEy8ay zaFwJ>)DVQ}zr8AUA=E2^W!S|KpUfcCu|WY1l9lexOG2-dr>e>k!k6G8GR83c^7^j; z$!ioORn2uKIi#YuRjy)i)MEw*%_)iz2-d8+Z9%e#1>cXHvpG-?;F9R;Vcp>a@3~czLIRu6R$$FIUfVUD5265fh;mL5_sVo&wQraIT+An*# zsp&q<^oz1_3o}Y`c1L=uCY}`rm=-%AjVm;10!c67kQwHsmszEnoun*nuY?mBy8d|#Z}a> z(#j(7UR2l~UgXZ5nZRO(apzxg`k+bgp{d78l?|uj+o%aIKZJ-S$5;4UJd0#hy6Ra< z{s=0AV$-|T#d%9$=!dwy1-GcPl+y-61DtA#?RctKsi-o_M|SS1a8}F|@-0g&zhZK7 ze&orBvtfu5R>%~aWn0uzLsV0e^Af`621R2{v-C4&@s44YDUo;)E-;=^6fDFdf$A0D z=#U#9sGu7|dO%w=ax&qJJKmZrb-YVzzEVR8+iMGFT~EB=xY7A+Lj5Q=xm9aB%4#gD zLXXvPcA?2>j&fuM2|+ygna!&Y>MzB0diZs%(gS&RRROyyhYlVNj{8UX6edbHB+s&a zKgzSKQdXsDE9$4$pe`fUOKzU_cq1PKbD9R7uGZpi<)Jf9Idub~_z!XFWJ4rw!1U;9 zMLwkFk|mt2mrx#@0C3Rfq4w1Du%ynS)s~r_3?n`NoD3t z5Vv4lOHgn@685u?bYFCmh>C)UW^wNBxa>oT1@Llpxx#Zut}6#sqg&J05R_xif(8;I zVs)b4wu0Jc;Ixie%_kI(D3L{KhmRS z-MXa87!Q@<)~cR8hg>`D2D-PNl_^t4$`!HoGHbcZVJVKrsZkyn6V%adU=BPAW~$CtAUVJ^%nf07*naRE!oOt9xY?rOJR2PKhMV zKKF`UByQ)Hf`w!{3(-~)?u>%w50L7ffhFN1NLdoPk=KEOK%^d?uu)9CEQfMs8{H1w<3T{uO)bB{QFRkrg}kUB3EP>KJv?i_|V#$m$2W z0%aM+C`3j|0+2#+yxq6u`7lij4);Z*)NUe~A=|)T(2VaGfRq=Z3%>H2#CXufa(Lc+ z8lc#g%lKfikqSXvA9N?alTB~#U!t*p^j-{f&7vH%NUftgA-~8O20lnUugAr)hL@hP zQ8mk3qA%%`f^Mp2oGy-|R;J66vps_0jAThGPvfV;v>QQdiFkjnfakqUq6wZwgz<2p z3zR@R#wePdL3xTu;|9KtMX)>M%n~+)m83GUf5Xq`$CC{C^@CL@2Az^hdustLNYbny zJ>v1DULt+>Ih65g*P3#rD;oj2!eV!$zi_gXtt8lC-P1YkHP3C&6g~OrlSx^sisF23 zNGku5PuvX@Uu)D1!q30;8PCX=K|CZV5i8-W0MATO`k9BVt`@Rc128;ATzH&N?U6d5 z!GK3defpf8V!K*H$vug}hma1=fF8b}-HT8_8k`lHTxXDUxMjxlAPs9!)MuCsViu7Z z(eftE@7$!p2@9oCNo_a~!gpr$Zb6F5qEnFCF|Biu3D#UyS&^d%!f3`EF1 zi#;5>1O3g#iz1Fl6wTN}h*uFdo5a`%3JS;SiUdEpM(KFceS+1wfLTV_Fg$~<$JNGJ zE&w1Q82dKt3<|F!UP^cf3%DQk#A2Y&tVc%~>GsBAmH$sQ{v!-&jQS%f8_U~adE)R7gK`EMnJ0_MHWW->&!S9RR525-jL~yGKxw$C z8V2lYW}(Z}e8k!X)NS_YoObHLm+q(`yGESHK&V!&?y~UcXgsa%MflKUzHALa?+KZB zKsXI9_1G3@Cry9_XEO=)MD)b?D$z!4KG8?nik@A(N7KPa8Yro+uFSNqEfBl~Vu z`vbwX(Bx+JyEd*V-Yj&8=hyxvfe@qBziRn5$9v2*mL7!ab=ET92|z1){G3EdrFfO7 zdK@m$5E-V!TwE_|3^E!5)8qv;h&C@d;K0>#K-TOCu>znBuM=fDw+44w^z%p{xwLy` z4JxYC=_^J`s4X0Ou47&Vi7oPiCq=v(9@7wVo`Y0NUiK~G8Ac{u%^ACIg%>q2q!xTs zF#Nus8H8QT|5_GcGN5pz98Ngfg*>7++GNW&z|#MY-a1FulT zh~kk_?#0>8C4?C;-4Mh$G!B+mP=>r-7tvaSsvrqV+JB}USFn$ijX2+E{Jvs&Aqm>>VWI&;9d<1y~xA z($FSiVwTvIM#0fAemQWl@!&U9*d`RM<;w#H#*uMGgCYx5)}!A@pbR&4Jcc#In9N5= zGF=_BLn)?qFeBO0Plc!qW|UQ07efg{QTS5E5U3--g_e#>v=fpqm|pRlFgC@`oc$P& zTsZNAIq=nu5V~!Jwwrn8D){KK((`O&jYHxpu8a}BF$o=Tn0(;9Iwt{z4b@N>=%Xfx zHJ~E^PA!@*2SP}ptlv9zg0@r@p`nckgPmwV1>5^_#wp0|t-#z38S6f3oKR2DR85L2 zyGGKJD%qxgZ#!J^iskpurMixoATd`uka+le?x~}TN)=5; zzdW#lnaUKzRXNq3wE$*7nZH!Xb#l_NFQ$1Xq(Ye6wcJ$!zqaDUrWHDb%M(iSTqb@EBHo4-X)S+Hb`zXQJhc7zj z_*DaA?Bf zpb)8b^v-&55w0P$n3Y>o-wuhp8hzYHy5bna&h019QOBOr%Jy1c&lA#t*B8YE4S^gR zvgSc6WYWbp3&>FR5dx#>lf&@>Ak(LsI@ded{jn1N*+%Q?EfGg+o z-AJG=iW7k+#(JuFuL!4%%n0Nq#mQ&V`J5YYiH_3XU$Zm8C2O%)X1R}s|0b|Q;&B@C zfH`{8yCfH80;f$D#^)g}fu)n;&svjw?rF|) zb5^%aW+p)zgJ(Vk`D{_-Q#udx(O}IIV(u6?lyqJLU5^UMnr7Y!8U9?~oA5wRQ`5~< ztnM{b;N#G^ve0Z1ON86(#r%_mFT%0dtR0``cz;qB9{tWWVeFp|unkz3$PAwW(XCGc zRoqdNPEDd$CBbk@C|wWo^ln=uo@87oa4f0SF_dP+JP&a5`=NzrKLz0|KYaA7 zZ$C}Fn+SZ-hJutvK>c~LRHaO)o`6*p*Gow`U3i3IEux%Bo62`Kuw6eux(TQqemX0! zlt5!0+`N6zIUTTFYeQaVxw6-BIC(Cj3<%gRza`6A2Fvx{ills2ocr7g5gbkVJz$)z zX-Mf8Y*1foN*gC<>Lt(>M?b%GNo6*-xiwr&uvf_AQm7kKi?c$k>81j ze>G>Asnlm8C+lz|$Nq~;0ho-c?GB;r>BS5>eq^Kkp(dLrseJ&&Vat zD+u2nxlB!@sYW8awCS-*T>hkl57ezMfrJ8ORBgY~<{4_%hjUNuHjxL5fDR{iUM52giBmlFSlI0KDRIk{*an7=coFOs7 zM6RIpj)}i7;nO3Plt$$YJNwzfUHGy_@KT0jyeMelE5X^Wj7?m4`x4&>GYMIIOGi&B zvu2W3#8LpCh_s!6NmyoHfa>g#tuKMqxArKqct!_i_NbG^C~w_V2WXcva6iX$Z%H!_ zX*?`tyYvywA}}p$wvawLBR1%&U#FI&bbckHUScrdBWhwtPV~|G5s=q4E&V#4(JUth z(sW3=%+hw(XW|s7DEehLruBBq;G*o|Z=f%26xDS>G+r`lvMYdk$5GWoVAi*y2H|Kn zlQMWoIDhURQi@N>^_r)qUQHauyRapVu&vp#8xgw+!wzEfZ9`qpY{m1rAHPS7?oWd@2bMiNr<(rh6J@FSo_@NeX)TiYw< zR?P94$)edEmNjlnt=y1jmNW0ny?;>gl$?OuS{}H!u3XqNz{x6QElPPzgC7e7=k-pt z=n8jPlm&9%VR9F+Wlsv+j;AA9-Bo-6AD9Q%$~WK%Y$Tq@w6iT`;cF}BeXqh*`FTVR zTXe3y>`_Wy2D@&{)5_vZ%w9y)<>a02cQY06=+YxF>GNo(NQTO+{1Z-lT%x@EM>lh5 z%PlnA$Ng!r>Q=}}7SvrQpvmBcu7j;?j>5`3M-D_3~&ehK(+yWO1C0fR!D#x&R?U-8heGyP#rG;UNIck zi4Na#)h5$mL~EO3F3!kp(sEX;bjq1IUfphDtZvhY4Fb0!c$pdhJ)Sp$(Fl1@*A~is z6;rh3*>b20NL?3pa2NRJ{%OQ?tBRZvgn%V_% zho57g7qtr6Mk#6EXq*y?Q#A8tt2{ZoEN0*zzXt%YUfEEMUl^o>XGUchgAXX(IFwKv zoGifs^H(W!Z4)zJ`>?OWbw;AT7!X4zs*u~dJ^59nWFo5ESv6XHETMW_p;wQ34Dtb6 z@~-A`7L4-)V>>v?M+f4SM>zI+`&{Wp-%GO6$-3gQz z*Mfn&LQ`;oI#(rAUI3YoTjm;0_Ed}NGURpw)=2hD15Qx4#VSK+_k1Y^y{?y(%QkAx z?%Fzdya)h{G^VT-giJXfX33n_Maamfw^czIp{3QDWzFa5HQz*axr$4Bx%kI>elFUD;sa!8CCje<{&YL@Tkg zv!l%*6{<#fPptUvBpf#-(`c%=R6e1vz6Tx%NnZ)7PV9mzhN-Hvm=9zhha|^4Egn+z zpy2MSjWdt9>gx1%%+Yu3an5f(`hv`asALW^@w0lw(XEHORciTdl>eJEvL-4biMy&e zY~2nJ;o(;r>}cKOQ->B@ShA#g9fOy^*R=fXpOVP0_Z%!w9#0mOt0*Opav!Fs(UI*+ zfv1j&f5;8s0h5=Cbq;_BF3lr}tmpx5hCGV!#GJo~4We=^0)553xP;W<6h`GGc@(Lr zi}pa{){0e?j@KH14IDel%=@$Vo5yGU#IC*DiPAntbcd(Ri`qff%9`YPdXWG@L*&BQ zCMV*f=#7j^CvQ8Z9t0-k5Wdv#&@sgx0vQw-DgAf~e>LD~F|p_r=E%7h_aqlb0Pg$@ zdlu&#fD1Y^uYsHh31kO?fvUz&mr)58*a_7KMzCchA(gQpx+$kcyj`{}OR>aB3mqN# zg9Op7N{Oak;R*nEDmk8zMojAvg)Yk*I?Ti`EZ>6nHRyCWU)}@FtvG>yHR@p5b^_1TRVj;!snU*sZ zc~?F4?js6&9$+f@flpWEWp)g}TTq?ii4Pa^72f15q1H1TcoQ45K^$1*X=?PAyihVOVfDdIA>bqfS1X3R8us2YElxn>J<+4<`3!5ZEU=*8`H1TR}V7y>bfl z=|)Z5RF|^~(j|_e;4TUu4>qhzI66qKPW0)YM{uWWvpGvZ;Z{N{U-R>3qWuSt`B^5t zPqw28kwaK{xXI|GC)GF@k0RjJFpW?k#RD;0e(K;A!fnfP@g0)OQd^2TB?^Nb5crF) zc|9^sVagzhrMgweC~%$C!Ki=X3GMQ;_?Sn*EGp%3g6tsxf~m~oL25rZt%~U2uXyGr+IyX@Q1S)mU z?FA$U^RT8E9dq+}w2n(D6&XfucDM>}4a%!nPcDbM9GZ@Bf??7;7n;scLy@t=R=E-| zi442A)$teCduX-V#H>e3W}DF~^6CjJs0-Ug9O3CCK56O8%$u6s8i8K96b$?e_2MY> zG<;)_XDNJQb{oomFwmAuh_t=cclHm_Un;zexua1-PD%x2R8W(P-WvYI@`h^aE=rE; ztf>h+bH|1jT(w-^0|>R~&CZg024L6VCdkgbs|`j3a^gnhnJM|1<4l-{*dmql%xLfA z_PDZEL=AvmKQE-XTWSesB+gL-EhoB8im_dWtrnEz>}DYZrhj|WH$&0`j(L3Uzu}m?5-<)f0r^ncgVOn;c6G#frq_X@ad-VHmT5&ejEs z>TW=p2__!av3s7A?I)n@J#Mq6Y=O4qMvM4myBJ*TGUk}txrv@q9kcJet-n=;C5(Np zxk7qZV57Paq3zgE+#JT&c>qPP{-+dP8)+K7*C&BpMh!UAk3 z+$$Kps5?LzUcFNx?DyqcHBz9$!?36kb<@V#3yl!scCnMGzalkmwV?P#S$ zn>T#~53H3j1qjYBO$z^y>(Bh@hP77iwKvki#G#KwZ|iXRF14t!hsoD7=&Vqhk50J5JIU8`E(|)oUW{R&+Bvk~H5_LHh32VBF3%GK@4)+m5#FCE7)2{dRYJe> zWm<#;*;9;~hPj~*%!6wq-E}$XV*BjHjm5s>yug27{UCpO)k9=AJ++j!)s@)NS^F@o z5a3QGi(^Lw4;e8PjO>T_i*rS35Av^vkCfM%_}f5nXe-{!Q(A)MnL)Kn)yzdy>O zlBH|}WEn)!T8Wwb6#@{c;)Oqx7KI$OoYbkPMJ)*7xW!f2#vy~t?oGQfIzcFC>bUim zNDEWXnlq(6I&&Gx4h`Dy53sfDi(5d;*Cb{W7stfF_p!_x- zgf`^psu0$IX()k19?p_Vu6l>aX2ug8rWOoNvV^%A1gtdKTOC8J7bIs3!7cz+canDX zWaMEhTI3rNb^CLTh!9hNr8D(4U39Et(=ABe0!-FJdnnbsun_n;)BBM%#t2pbFCM?_ zqCcS}K}VKa&SyrdXvz7)A5S)281}?k!X{_-GN1m2R0MS6%M@G5a(8Sf*F?z&h*7BI zVKKT;r_8X)jE$Ufh;lxmt!s!&>u%9{9_+eguhNPF17*10a2hnB7o9?{exS61{fMV=+t5|W(fC+5usZc?{hRKOxMu`CIji_#@h#;w8( zWX0)&9<-rB8Fst{a^2uvk|ph>Eyn?z6fi(D^iM4J>?$7CA zao>1#JzwAK2({!X>8i8#+{cU}y^c9fH~>{)+~k@BZ_WU}y5T1cC8;^gyj4I|p-t&h z3v@~dh*1doW^Z^oBaHqt9VigROAgsZzDa2B3QTRbh|%-K(k>~oq$#JL2@7^seVggQS1Cg1Zhmm!&G)8xG1E4#^+ z$9H!L< zajk$h)uI%^;mdKQtLgzXQ)MjLf^7cmRuWBOh=^3lC|)q~7R@ChByT0~`i7o)+#2lQ zqT=wug#h5@$)*_#zExt4^O=kx0%qN(>90^&(I1>m76YvL$DCQZ8aancG0wYipLJA^c+@2tw^Q|b zc7q>ce5D zL^|&DJfD0W4GVMNQmt!SvBNSHdq@vcDcOHSEsbrSou6zg7?<^AD{mAWI1@{j-uuH* z2uQ<1IKv&A!LEzI3eWKHri<ByiGrXC(&rzM{ zh(TIKwlUAm-z?r7`di)JU@5KVe@N%fHkFx{)O0f!Xt+8?h)5p@_e~Nm^j%#7KDC94 z?i{Pys6oPAh|*qGWgTGFGBjxpkzpoyZ7~&^M9~Vp%t zu#y&i4a!}yLc@S_`e~2hGMM=%%eJ|y2&^gNA{XV6D;c3U^I6j8HK9qT$cjVB7<)c6 z{aqQyl0Bu(+*Y>|F*R(XgO=zm0jdv#XB^;MLgG5EDtD5fS@1m%~_q-op4vwiq76dW{tj95EQCC=htyCksrND=w7 zGv!vB+=r+UgqhjE>L(RL29PPDJG?MQkC@*d$0ygNdHlQiuQ@2HyMTJ{;U~VJ*+&_OAbiVq}MAEtwoZ_2xtx2GIQH7-^jvzK4SYh}&!MhyO!BnS%G{2w#dcFD*9tk<0)?q2U z3A>fF0?L=H$8FqNB;(q$z-(i{>hdY0{6}F7gXOS5zxpSN4n5R_DYq&eie<(KCs+AQ z=Ad0ki7nOG>uHD^ZYc}cH=Qga`LPNU4jmox%>c%PmvDJ$rGnbzG}FuEIffTHwNr91 z5!8N$FAhCrWG7amx;6?s{H|aw7*}rq$`o=fn6X*{=JZ39D^jCj3?Xem;c#NQ37-`p zcC043x9t?%LARn>@JCE4W&BpfZ&Cd2yHY2shEtA$9E{7@hl3^xrjE4sdU|ghgbyz8 zp@YW|qC?{F^PPxjc5DlC%$7*fY*RbD7OO%gwwx!!z!i?3t_R^YDN!mLf6a6&84&VM zx$K+NN4Q;t^EK}wvG$8xWPP0d${6@-uts9kJXdI2zTr<3y1Go&${9B&+I3fwI7=86 z3G8bLZ-imEL?8lG_;!i2oH{SumCuk}2VP4>poCTt<=r8LZDX+FP9>(2sFGFVwnvr@ zAmS;b`&2elFiuk9xtA*hRQf@QCz9c$a-=~MY0M6hETK`;s=VZQiM{SoMAV5MlS<$D zA}*~GnaYoCQ*c(^R6&;1<+El=Fk^KzUiI2#;ON5~z0~>tW&5Ie5Qx(}4{fd0shvkK z3=8XQb8`{XG`HRqb^{l0ySi->jJZ_P2sc%EmBS*-O|-~NtLYYkgk`yoYFotYWe zOL3w`)$6S#-gvrNbW$9Ne-vJmY;< zEKR)uKz1p(3Z|n)SJoAgcGMFeZ)LnI1_20OI_Q?VbY_K4mSaGq9_gE{u0tsL;Rxhi zQ392Jb!F&5E5bz!gr(Xka7__V4Pi*Zge&3f8OTEU&}cA;V-JKteU!eu_}&PC_9umL#r?8l~iGz^5M3t_n0^| zPj>*VV--ojv7}0!Ovrd89nRg1d~}yYCh!Ckfw7K7&zWy6tAtk3J02DQfssS@a18T8 zrDF&KN;KK{;t3oGC{%9*mgZDcfnsjs^M@6efj7{Q(i$1TFj2UU0LYB25sFLV()NS^` z($LL)@8cIPlnFEwFmNab{%{jaJ|W1roZu3MFZxZzI_bVBk1aCh-NmZ%Qj+SKO7~P` zs@YU5Ppp^^3%HXpx4gA5ioZ5Rhw?|nozAVeV-l?r!smts&O^m!W(;c8o5-*=%Zaj@ z1{W^Q_MQVwxRfbHMtAxT`O3AcvcLNR9Fp0f)gCCRhlcJHM(~3yJ`z7mg|qyKPdnl7 z+#(lJM3yaq1B^vn>JQQyb^r`ME|WO8M`@1W91~+RlpQYp<_8qpbeRz&4y7>TCp#i( zrfSQ*{zFrv4O@9iwjb1wEN}z;wBIo`r!fl~j08%5l zg4DpzLF(X2IqRs$p&#jqy>Fd1`0FT7q#e;OwZ=8P4MZs_4;7S-@*y{UeiDn^1#E0Y zq5(73oxE)efE@|u#)QkVINP8I^nAOQUM6uod!sdlEFPMFbf{RW4n6NI?sDUUWG0`A z&a0d?xb9UwaLpmJtGlWst(WR#x83^mcMAKdPb_=UqMw6{1KO#$`PP`E@jYBXj)|8OA-IKtB6Dg30=SC!j1#zWRqph_sdR{JsDd{nTM5OOmd1Q^J0)){QGv0rwgHt^$ z8^uWJ#qwD-OSJ-mWn^Bn>ugo24Bs{Ni4I@M7dd9F`@>2!+ngS+83N@|0Wrjh=Q$6%#C#+09*Eh-&{r8kwBl|)#H>ZKM&=1k!GLMN+bcRLp7 z7OUY`&IU{#&XH6n3qEOJ_K4dpD<3&%yWqKa9 zQOaJ?g#y5nmKsQOZP1%LC6Wn?OJ@f-@w(fB#iljDU3ID_Qf3E2T$9SC<2?Jq}w*y0VZ#)c?1*ObV zGb0G;e`FQx!ntqBcG}_$AkxE+ok4#-fb66xK*o(EL-@f8$8D32J{DF9W7L3g6gqN# z(D9Hn5b@a{ysLWl zv=kMVvFuz(^fTB+kI|JJJ?p!Zuia{nX3=(1Bre5+&_;wlO4qqB0JjeR_8pLHH|4dQu)M+(-V08R$X%IKW^b6vaF zIz2_`w}^w8Y^5p|05pC8q8oVhBD@msdVX4CGzzHEJd$xN5NLGO7Bg}w9GHSNokY$di-l&qMAqtQxc|`X9 z2>nxF?xR=V)7hc1dUhIsdCymyjnyi|dc?ZKVnoA99p*B}n_nA;zX*;r@kd{@e#$me z#^Q!FA__vy$27`DsB|K)uO}lC7a=!tYO`mKOvRT4nkJO0XTZ1wJOv?=_1w1u=nQb6 zs&u=>8Hdl=YAEu(t= zz^-`|J33vOLk)n{ooO1bw0?y&U>97)(pW1mEhI}sz5!5%05r>=+a*?E1UqiR-Z!pN z7;n%4O4s#y(aLoOD5z*q4+u+DauVI&_M_v=RU$yQ20flV9GwO4kz0h6H9G(ohe)Bm3QVW& zzTysV><4e2Dia1Ukq38KbgH{1c&dk|3O-xt$n6i^%7j3r>6JDkc_Qn{oUfB(`s+SK z`T_5Zc?KHH$TY@Ab4eq@IDz7aU`(nf9n+fGmgU^%$UCCib2hS+5JyH-=i!a)?`pPM zl?@43fqj%yr`7>x=n|*N$ny^AGj*l6CpV62fot4Yb5apfcAd`IAo@@3Q8ViK@%+{- zduD1^ql=fkF_Sf+2*ZaiAM%>^iRP4-5k7n6PGv4=a8vQ%c@vKPrm%LmEmxBE+CpIy zVVwIY-P|sI3?>Jt%QAgH(IL5V)8+v}u6)mBWAQiG}2JW=+5;QS-xEiVdF@ z3lgUoAy!JgY=f)@|IcZ-7yi_n#FqOLd8k+yBH>FDQ`pJTSD^07ydm+b^M%LTi8N?vIAp*Q z9nQD$ag)6z7j;FsWR4I`4n42!t&|OU?z*Zh#2D!0$83{i`98OWDp3SJG}DcuL;!ej zjKO$N3w$4E3oU6inPXO@p{qz+rf+}ZQxriwur8ijbs6mJm=bpc$su)IQuD-+!q(4_ zrxrbpC3O-DFFA9Z+#25Jh~UBMn`V573t*H*A~l}_SwBLOP#c`P#^ouhkxz2#q!QXN z<8$^QivX(h4)399NHtE8Rix*~dLcQqE@ml689h44qfYWQbX^a0Mg(of8%gqzT+%Im zcO(EaLZ!zv`Je_;dR!7*@-mdr;*%mMPScsb-}Kg_d%BFCn-lgrw9>86n75R`{Atp( zq0NYX&brv1X2$X3LUK~GmE!0O)5(+ymz;hP97%Zh_LDX0qAp8;UzRAa@^As{U3Gh1 z$>*Ue7b&?JNy9C~{BSp;sv~OR#f83RCE&h=tV*XINUmGDtj7*@p_S>vn)e@sItpz( zU5q|W@O5MxT{W3k97^HG`814%GS#;n$q@Ms=K%+r-YIQw5G*Fc&opZ>eQjL*OA0)JFg>YLcxzK_Riy4HA|c* z?v1FU7~62{k1Ej^j`<6h;+$PgaH+K6WCIOz^cr}!diHp|?!}UGuQ#@@cVHC1WLhNe zwH8ihefBuKJ*A<1OVV9>3F=G2CnhY{;>B9eqO&I1F32@Fdsd_ptyO>G7W+Zs1xH0l z0`5nSqd}>ZiL-6Q)w*J?9cE%-=?z>BU=-Tuk_M92p;7h8ADPsoM+3x@uBmTCm2DLTU zKSb*dS0s<7I63?omhTeat<{IgB_XG7X>=thuxwW32!t8512fn|K%mhdX6MnTYfGL9 zLjWoi;{Go)<~sU!RqK)|HHzd>PC8~F0aJ3`6-10HDz|R)Z3#2>NhZryInXIy8Hf;+ zK(t|22_w{{o~=~P_Kf;X_2y`vD&oNqo|b5`EuVR|O$Seaf;}3V zr$YqKZYj&asqah8bb>gGmnq$bND|}}EIlz0-RlF4s%iBNc@t^sz_b%9h7QJW!)J$Y zg%IE;>pH}ihPo(Ok=z4#67f{iiysBeJf3SlG{ht138ynKDeolIgC}K^dn}_sCJ6%* zkL!&)g&wstiF7a(l+Gic3bUWBd2!XQ6hWvpbuPo3iOJaendm`F{dc3qC*oPJ$nT^y4F~hD&ZvB{&q+l+^Pq!*N zof2h2WQP+^f9S-9G^na99r%M$E*tPV#-(&c1BwlAKq^}QEVU`$cBM!O8mv1-v%O{B z1nziFBz>BOA+7xP^WiLs-f?%yZfg7gpBn?j1kHm(wyP$EVQ*gpHKm;4jq2##S**LD z{EK3CScUIgTvhH-dbYzZX2_e|0VGZ#M{f}W0RM^xL!-ge&pH4Hvg-s_%n*65&^4Q` zQeLjlsKlEdP#-ClZTin`-k+Q&IlS9g)-ytG#+ay*m8haPkw~Z!(`XIO(N!U&G;+o< zxBTv#li*1f2UNuZ&4#Y?frwJwA?#mEXz&7htj z?K;sH-=%=lL@bP@Q)s93vsk51C+j-SA+-t(yW|)^XX(@T_y8PgBn5Hshe?r<6%hs5 z4)+w=$X$i-5!H8ri$LSf)&wsp;%4Pb3aAOR)YTIytfRB;sba^tunLge>%5gZ7rlDp zy1-4qGJ5iH&b%M^4zkkTsV6xyFe=}OB+1mygb<9LL3HXUZ=#7SCk*=w|69axk*;%A z`=C5i04T#?&qg_|Hm1l9U^NrO4dHQ)8Ra#j z1I#j4)d@e_9=)VW#u_+3N6eXVHaHAyhnR$r?)}kq0P9D)Y&rFFvhU2%$5K$R7{D?^Mk2mDunN1#kZ0 z*vz8azjZe-mX5a+ACV8uIdkIBf>X5NZgW%cUpT8UqchFi@O_Uecp6aAg~Hng1hi!% zrg5;OEpbw_?a`)Zwywz|gnOP=wo_{&lo^_OtmP}DdDk3vWys4J;xs``FqKmJ&VjQLs(Tj8 zq41!j#sgbudF$kiQZ1i=I8};>@RL6K*yb9;Q-6NQYJ@90LiET^QMIAMCW5d6?G)vX zS(`NKuLq}H%UJU*>5o5tp%(-pZDmB3yjpY14HFKQ&PTSKM;+tWr|1c9PI7yUo@b3p zCwtR$imZ#?6xBRyEwve9kaN8PDM9N^59=3cmXYEyvl#>Sfab3W|;j`hnd75E=o=YelJywK{ z$Mk0^w7kf)SIQ6@5nK|)k>{TOFi_sJ3Cl~F_I7~A%|--OaD%g@j(yf%cw%MBM7f2U z6kLxo!=&7;&LYiDm(b&qMN!8?(R~deOb3+DRi@wL?2Fuf7ZwPDN4 zN;GTZa-q*xnT0E(8F10NN8HCbD)Ul73abRgyl;RfBv4ssO1I0Fmm28Yx=xYsXca&E z6yh5(6QO!lFuhZ6N^2J|A6K1>oLfwWC=}q2`50FV(X7XS5Kn*Q=4?*jIU^N-YnB#a z$7a}idz2dZLFpN{B3Sjfa#FJ*)VQv8hC`YxnkTjek`S$&$A|&v&_$l$t8@22Y|*PY z|H2iX+M}jyhy_Hbf@bihy~C}3_LfRQ?c}>jRBm$cs9`*rwuf9ZfnrO@J)KVT8i@FM}gs$6Aw$-KkCYMGTJe(~Alh=kU4 zk1IfG57_f75$OeYG!r}AGhG$#w4#8&}H9S)e8;W^$F^PJwb2zdN>4t-*dh1xmKprBiisc^JW%mh=ackHE7 zU&0;%f;XB$p|fq8K5yl3N`n6W{rSmHxgCZ+2^ewF^$etLiOdUuF=6M8P#7;d+oMT zT|}LV0rh%=2n!$BQ=ooFlJOZ3o+7KN-Sg~tzLBs*aL_4s8IyZRd4pS7r=I4=!@>Vu zfQhfeIq}N3z@vlp)AYVeRi>tz>rgLUm2o#Fz>He-9s1}%@{)Ozd|*<QY*>GV6{ml0Db)ER@eY~NMN%B-0eMMCc;iw)sQe$hk4Nk zP~UnrYyTRFk%o-tGz}TKTssAu5h<0zc_b)z z<>hy^II5raQk!-lueb#lH)j8Xe3n{a19A2d;(?d@Fg?C{x#NHbh@T_n)|XD{rEa$f zIf)4DQ6utF4Siwg%55GntEhf40+%R;LkJ5(CFBq$-C8Wl`fxZpvzdykzj_1W&p0Sb zr}l$dpKw@p0>1D1Sua2C~9G{Z4H2?HKx>EGq4gk_Wf(;jFY zU#c`;!OR=T)|gb1s~?wq)KI*><3uEXU%H6rs$;r3alvTZxOWm@n8NqpfBTkY;6aBc zHM@%}I#JfB&NOGVy>n)NM^vT0Q5LlPoyk@uO6a1F=TXjdJZyt$Qd9AEb|#XUH%NzRXH;}zf~U2o*!uS zUQPFpbS-LEC#n*Bfu$hI&5B+9e4+z`P9A8X>;s0~W+o&6=$0oL&uOoP@2pLB>uzXA zakf##7tEmolwpAJb)IzCeh6v#WOMfN)fToNA9~It)EYiJ3Ce2o=O3k8FN8tUUKvySu35&UcBRwR+p53l+ zdIhQzjHtBH!IR3QgZZC|h~~u-_^0KT=6p zBqmN~`V;Q<5L$r(Py}w>MsaopU0>aXyca0YLH5j-6uXDR_-3V*?VzkOd#_T#0Qt(V zVpB3v3lyD2IbL(fw*(Rwku8=vj%__FvI0LLkq(Qcbe+~L(JbUv5at@NftidFWAoAmdFgWP=k&~W4v`#S`X1?c^H$$jJ~n(_x#!GLmkzn@iPDyhsG8SbfTjk0rcv@&NB~+>LEN# zsZyCgb;^$CpAj4*nTwYayp)N9u7R^4oGiaZ?M_Yz2d`0-rItLfohEz5eva`3$;u_P zb<1x9(RyqibRD`9qJ|1 zt?*8k>dab+10DTxTPM56yF$s?0M-w8vx*JH>QWVAI_FF{KbJrJSOiz`}zC^WYg}mQdxC zORVu}F;ZGE28{C)0*OY75LH04+cB~EF}4=ER0}Y)Z8ABdAyz;dk=s%4oKYP<4B}hIUu0Dl;=qUrjuqZW~v^!V)zcT_#6X0V;sXYc0Dr;<^i={9;<$7c-C|# zf`if#9@#ml$bt|a1V(OH^pSg_Q$v2tK*}br2n6}CiqGk*ws;3{5ep5YoMz7MX5WEJ ztrXH@2YY2{d=k#t$RW=Sk+Yy_)8wV(ZCjLtS4xWTz*l`tj2}2u0*nhyP=fMcealzY zqzqc(*iN4PLuin8`qFo4F=bHXHl+`Ru;IOeHwU`=-NB;Z)I2-A*yS{oKD%PqMK8T3 z)451Dt@NI4=<2DYNrM9!JU025V!t#Eu1Wym-H}K}G&8gaptFFC(jgc-+W}yPxEFZK z(P#ghcF5wAHPwO%rAVj1ujhBb7SHKp+h9`9N8ppz0s%)rF=+7--SirC$rA%Niu4tK zuB5%GM;6&JMKv}d6Vx;#-Dj=9i!INrpuK}EM)aBW2paga86&@CTe#0CQJr4P&U+r= zIWN2q;MR23;zuG~0;l0hk02OqL=RGloDDFRd)xt4-vC@=ryMNi>i12Y^)Y?`fTGAq zviBXD+Gm7tFu;4Uj4ifx35AZOqEiI~cD=P81QMqftE5*w6L(wb1i^MCD;m8qo=CCr4qZ!8 za!h{TlNioC*+K;q%ZE3C{6s~5&Vy!N8W~DNvt}DYl2!vi#0*f7fYN~^{S&HLwCM0o&dR+2B z+|@~IT-rpAL;53Mgf>(Yx*n2Z!Lf2$yrq8607J=M>(3#KnX-7`i)OmXc4}yz8*Z2` zHmV>h{mNfj>FuJXdPTG1b8Fakp3oVBe|BNpqjoP(C(xn^!N)M`jzhBvN~T-*5bai0Na}-2Z(_{_UlQ5Lv+$*W(&?@&}oqQuK6Bs{QZihU(?|eMlx+QX+oKHZQnLXPXu3+dDnSFGm5l!)L zbMJv*@9sx^EMBf3U=LFsgGcA4biJz}(Y022tmku0#ue)!t;x|_9RXULc%fn3$X3%1 zf>YfkOn`19t*c)>cb823bc}-^56y)>Tx^KyjPBVBN!$?QEO?yadd++#GG#<0g0($3 zfS9yt?<_+tyvMTddUNpQeWHlO9olBLC73p_o-okuvNzOF%!r2(98EAH3cAgn5xCOl zO>x=6nU$qT0NDV?DI+8OL(?_!988NqN4hC44Tc_wRWVe;54Ig1P15FxES5pc*zxhV z@Itu;n(>L$gWS#-fGV;t21L3L08T)$zrzZpB9sw>)}>|uTzKrX8M7T@5Hx2oB4{Ms z-C^ z?qU3aQ)Z7*5*14lP zfvo(Tv(pvvIfjRQMV7yez+mh#d_=PRT7#=ld4L6Dm#gqFbj1)m?^cz}Ib$oTgA*UV z&>S9W?`WA0;6P1$$UlYPkO zvvgm7u1sf}TNYnI6pivO`XJ!@oOLnBh^?BeO+DReGeI!8;LOhJIX?R*%nGHWdt%6d z82*%s=Lr-SqJg|^N_QYc<$X{hLf|xziX|aa2YdkvgF(fuZ&gWw*^VUTHY>)HfRS~~ zJvebalLQQZ=3yTxG77-&>K@gw)1ZE zVeU$PLvj|PG9TRl(^<-X6GE{YR zo++o&i1)Kgg5XYbqGg+ssWk?|FL*c-zgT86s!1UaWO{=ENzQ}!C8t7k;%pIF^O^I& z5PpmZoYsXTK0;jSTrJG}uASb>uXKGvAdg{YwsbM2Z6%NT;Yk4M=;xtyQrPuH6P@h& zC5fH7sq%h_86iJ@(1coBl%>Reqy8#Rdsb3J4iK{+MI{Jm9$PeAOr=Rkj9{dUB8DkgaO-Xu{faS{r?OpPK}z*-K9rXZ z{Kp4OwKWxaCONi~DtZk*K2k4GI2pouZ9k>i&cu~|Xv*srxR0yY!gSZsT`@9*Y zYaY5{Zlkb!BsY3E&StMm)Nq7Nv8rc0fgMTS)aOupV=#FyfO;oLl+x#C6I}H;W?6Dw z9EqH5fv8hj6A^a3&%kpPNaQW>rGub^;iUh#yT=tsG`mNf6qJz=J)#2Fa7e7&teZ4Z z+2Jud!*on%s3D-kt?JLQ+LeI7sZr^5VZ((rjAa}J!i*j?T4w2_DuE2>#iGv{I=7uC zEzr{x1)ZgzI$6mvcytbCkX>rr6f^8r3XD(p-d`&NMJ(M9M0;4qN@gDCBFR!tM|%hj zQ#nkMnF{ReumW_zOef(vwrL>Iz`+ymWO#%L{w$}_QFQCeB39%$54;u2u|KzlkUs`1 z)iN}~tSm*CsqW-;n*nExfmq_IDvIbTXirfI_JHd*y{Hy$hHO@=>xWMK@LvJ=)VQP& z7g?N^Mc^mo*pv+^KODr(3=h!@GH5ngf{rRE9cWn_A6)Wso6&IfWBCe|Pjrx|UBJ5e zWT<#wpHYveNes?NB0X>)HcnPW)UhWJY9604B%P zc$Qdb4KtC~1Xb)Yv^lMhtX?uCDnJ=Ivnjm1xE=5OtF{EL?VMywMshba!#X!l!7x29ibvXCFL(r*5fJX@t zLm6P>aBDjpgI+b^^79qw`n4UL{vsfi2cwx)!^T|lVOE_zg6!6K;)%*1>~`0URh9`olBA@nU!HI~Wn!ipZTQ!atHqhT zJP6_T;H5r%WvB1h8l~m2Xl}`wVcM<27_ z+QAR{ooSuSE;q@{7`EH&$;o2c-J!M?E<43ggi}v&&zIafg@~@f7;KRT-X;Od;k&+F zzR!eSp)6)^#i(PgP~^Uni4)s7VTM>oA!;X{b`Cc#H{dI&4S=Pn{DT?Eh6E8pg{neg zb4y@1;?1j<0}b;ZtBH_fT*%3KD{c@zW=C&?xeOCNAQ5H*## zMeKPKvtXsOyScLKAxF5$=Tse0lSyF6!~j^3>GWsZ&-orr5@tL!W;0>sXHnIuNt2Ts zrfNW9<2XYkXp6y#>bM{L2uvZ%KT^mf!YD^VvSFBiKKNf|4vI4+dF=%sYWAy7D5Qd+ zO!A;^hn5r}u{S|Ww!$1bGOeiXB)}0 zu{*%HdgDV=I5^4SC)k^6z*8YxhnUbT)`LI#X+b(;o_!4^E8Ap~><94~O{*OhxijW; zU>RQEKkt^f4L0v6I-Qn+XE@=*V>nvx2}>ND=J1XbizghdAHX^yLwlt9yG~64C*2P= zI4UwIr9XUSI>*d(k|=l^q+t2YJaQ14x<$S78vrS)?abJ~h#<t$8J#dl#w5sJ%N1ms$NXR#9`a z<_2Mc*F5OO^sNH-y*2;3DLT=C&)^AA1)YKiomit&Wbi<;{c+$%%m&iK#Af(Gi`gqs}3WTSg#%F4o?LHEw_3V zbFQ9RL@8P+3m78R1fp(Kk=6L|ZoUXI#Nwp|oK9+2%+dp)YOx81lDMVGxsR7kId-%d zQ#~CIiBMNhU3Eh6U*@EV3wfXAKUhY4`uVEuWfrL9Qgd<4m3=D?vC z`@tbP!b$}totZNrvIJP%iIqr#BA1&CFqsv~lS^h!xszu!EBEy9Rg_aBQby4(Du+68 zJ=Cjz&nM~IcuGfWfsZV~G{T6pWKBYiyuVJdE9s8oQs+8`2A`6L?TZ?fi z<3>^yF*^;NxV35wc5|L?4pNo3C;_(C4eRRWlc0CeZK^A2dPe@~{sgv_JO>yBy4bZj zC26?Id_#*Y2{?1b;LLUgMjS^$Dh%!Gv!>O9epSk?CY=0(X%yJWton{9bsG0e&-Fh|h`* zb5^tN1cLB$PTCg<;~?^G!iFrYTkJl$Bs24jflW8JEL8M-Pf49IKK<&|;}sNB1h=MEY0!evNblfWH4N{Mb1psE`dmrPL9VP!h?b4iX~JoJ50iB;t_O<=Ih*_0 z`uuc_ze4o_NyzuXwQRMNT<&`X*0TF14jmVptx9pdmeVj)HxdDiLqg=^Z#wcRzn$Fg zK;b-`Fx^ArNOZ;HDW*pjvCJnj@+n3g&jG{SzsTs+lyXVR0jK5Y_teWT*~}ne z>;nIryTG##6fw-6EV<%@vG_5a&#Rw&}C|5zVv(2tOKHEG3R%+Jm9g76(u-(MmO0qrC_?Mt2Sstv(@-X%ZmjU9|vv zqe7^oIIDQ?dR`#%t1|h#1Uc|W6)S%NyYmc6gH^D{$I9?ChM+>baCZrLbP$yj_7ryN z2#di#$*D3D>5S;Srvaag*AnP59`tGre${QB42+7G;~$OnlXDs3@$582QO$H ziaV07>}4ze(n(~rkz=H4z;xP9rMmVTJgrHjKWVi07<~f~;e3`D5d-_2qGv%RG6Vn^ zcd!=Z{s6GI21A*pU|#QXhe37xC<$HtDyE2wA>iV<>lCt55?Nl;me?hl(nf9A3|5=* z*w~ROy#=GORS~iFY88W7d%eSpR?IZA;sK6mu*FM`o#cgTS$-9=Dqea`%Gy)-0$m&v zm3I+nS#FBbxAsG)6w2V_Wyft0(HRoMOuthkx4?~TtFptjc$bb*k}ZG8S3mjkw^tTU zek_x=clwe}Eg<|CRyHV?Uq_BH7nd)Z2RTsHFsS`Q4?3n<4BdFak^35c61>5dbB)G( zmS;WOl7k~<+(U?mo`KBb)`dqY6p=>bm@yuf_xz{R6rBB%o?UTq@&iq3%LN3EcDaA& z>*&=wYG^c7J!{BCsaN!&H0eQ@u49SUBEl16t#}AlKW3%-wg_92qTkdMMH{OmBM>z% zD=ajt@5=TrDVt?CDfkIyTSX0H;VnT#PC+SN1GPKzBb3PeAkC6MirkUD-Q}MISSn&? zmA!;F2UX3;iv45@Ux&m&cLlCWzHlztkJV(V)MYNbn?a-TNo32uB~$mPy#m>z?xn6P zZ1Jc<5g*o(Q?hW`!Tpl|$z)g7c)9ETat3-*I@Yzc@3H0k5;5eujFoYiI=Q^~D;B*2 z{DL_E)N($Mf1=Kr=q-ManQ0ym;8p1<2~J|;q`s5;-6-|-FI`x= za~NjZLMnDT4B3aE&op$Wo>;01&oYqIB~*np%GJEMDb%xcpo~<}QEhP}%ObpWgk_GQ zu2Fm0{IRC&NmTK0Bzt63PK5CMiC^*~#6jj*V6ErS2$EJ^MsZ+(aCzHt z@r(n*><}wWDXu2~8K#KMd0wDak#OzZ7`2?z5nD*>SfS&K;@ZK7i>QLj93$FTx1%sl zVp&*JfQbI-O^90%48!Po~1hrEn!c%BvFynC6i)y_R-h`e4%;8;zD!uRsgn z%o{8fa!Ir)Ea^gHf?}1$BPFvU40UI58baEdCL?%N9M9WQsKK!ghWq zQH6;%TOrbTw21q0wlqGG$6gvYs)gE6>$-1d0<%OX&lJnLq;ny@k6Vc^6KeZTnOaGh zIUK8bK!`kg$t`9&Q=(dH6;6Kp285d*5c&_(<_I$sg;;GZdNgQ`DXyVY%4SXxEApxa zhY7SFrKeM|OnM|tcGePuP@l5?^LZgADeIN|Y9Po=R$S|FWX!}6vI!Q+arQN({ybS) z9)nZHJ7WZPbE;hIk(iUQE#;bNkU9H+_efPmz}dK}TmF#p*sa9$_yNsKw$<^c=2&ix zT+1F=NXCPg#@J(#!%BHIh^vVGqY#c~blaqK_x4|6FHRYo47j3CjUhM9kZ2_agLjCP zB4R6NivC(`EeoX=`K9x;yx>*q)A$8}<<=^1J9A(*<#oCSu4a-uY$Fvc_enZ{yMrd= z)DHcctmo=9&ULa65j;9_%DMYIZ}cv_rL%L^KA=Y9%qz>i8j!%Q45qe<$_!xO=$DFD zP&zgLsl_(0?6rntkDA&T$*=S**)o@GFCH;P!1tK@I-9w+X4cwfY#VXU4M zZE_EsG+1?L1~y*>-_bahlnIo>*;$FPvyyYNxTEAIAwg90%^?OqY)TMAhCMxJ{ng8N z-?5Kqt-;Zo+2P)Skf4emgLX1g$}K4AIDz4tVWauniFL=pv||S#{D}@uBnJ?mV9c6k zc7V{Go+Tlc*mEfF5_*1B%&iO_*G%@5NM$Ue8FU@o4LDht5Ro2jj04X#dlZN8=DM%S z5$L?jBv{>b+di>RKB0r?rvsp>Iv8bdBY3y2QcKF8e1W}G%j=O_5u@e~xA^l@gBk}6 zKhlw{5!)2>Hvop|^Dmf$i2y^be#zJtuR(b?txU8n+>)1dnr3%#=;rqVoC06uu7U*p zyLfj8KFq3R%2s8F_SD2sa&QLWd?_WRozr2xN!R-}6!;=nXw*|VV{S%2_3YvRF^)`bx#`Dn*kn0vvw`YXGhwQ!Uw}Z2Pi^Ku{xfHqwKxllhDR zXc!*%RzO0iWqVPjOZ=FK4x%O;2TDWIx9g=hw2)=I7)z%tE}`uz{3=BLBk>Z|F%31N)Q0Q<>NCi(p;y=9Gn&C zhV8mlKmCeXgyBHzA}FvW6=tfagvv^ehAvVwTd3-nT(s8CWR#K`DIDwMB5+s4t2_=1 zA8*13RgY5%-r8v$=4Ut#PBCkUE=me>IxB8)auX^AI4RA+?S3hSY;pey8FMVFL&`TnVRHmIK2khYTnCR0Evs<{ZOW#DGUm&W0)2TT9#l}&)5(08;msi0^l*Q| z=7E;1=*g8GsnNqLy2ug{g*T+cI1Vff1ZnT|(ykVq6$#)8ifYg={5XExr)xZZEoD-16|bco2KyM@Y7x4d}0 z)Z9W#@pcC{^p&B6{k^Ai5@+nJp%YCDh?JGvg08=dIO}li$)1`$^qe0@F(lmgVm5E6 ziw-)@ob_b7u4j%j1`-mwZ*q}#67RhfsLkDz<$AjYZ%Kib(zDa?N0t__3Ch_ivnVM@ zyJr$8dwfR69*TL446xRg?@ACes9ceC@nTv$L4L0FEdUG}Tq91K4$6^PMTK8YN=A!D zqfYLG(q8>YLGcCTYNk^h2}dEq2P*}$U9{lzgWg8e$QCd=F;DGZYpXedRz|XIPG#!c zBZ%m>8mW*4uHu)H0v3Zf=a~8C$X7pl&aK_nvyz2?*`~o+D|#a|yq64%qpA(#8APRd z25!lN&XH0z)2Pm752d3>#UY@pah)ZGr*y#|8@$n&hShLho^$!Ljg+Nzmnr;|(2Ece zGQ}NXXjH@->)8%&!c!v^Pj*g}r2HMJgP_P;fYdkRG%)({VF;g$D*EmD7XPR{~lsUKMqtS8gkW z9K9F?w}PLkqzk7}VQib2BpfKm6Oq_*IDR@4Vp3%w8!m6t6KC-S8SJpqQF;@el}-8f zO{DA&wZ#B((BVrpr(KLZ^_7&KLkpRz7GX|$Tz5@h6IaiGs6*U16(XH81AM2WIa841 z%C$MM#-2EbHwVtew0Pq^`&=(tJk9q%PgGnuxr{E1;XbMeZLQwfv4;g z>UP}fpCT)+_kyrAa?9kiWbi$n9x~mTFCcrPu=cHPYRw3(DAKt%<=&LLs{09#wbFd2 zZ`tTc-)ePPAAa~@#-q(=L_8VC1el0LQ6D|-Q#KnjyACzLij#!c&D1Fg2-I$c2A^^Q zj7bos4f4x5(#7yN2y5w9<~sq)(y(l(r&ClCZhlvhbrdokO*&ab6*5#e9Kt2)kO*0B zwg_!7j5l6=*6X~I<`gk=<*vxHjF_sxa%Vh?mLO*}r)K>VltZ1;>Y>2W;4iSE2OlaM{@-j{BRV7O0B(*#YbE7@~CZwz@f1 zzS?zV+zB+<3zs?`hp%%gTijAQ>k}o-kS;EzWXgWpB$%;BwcoPNIjnatQu`og((IfXZwajzcTMaVVKj}zCHCF(9vL2r z&jC;snJAYpu7GQNpwHLFCD)009?FwB14+CAO@jQvx3&7IZ~W=D?D6%dZ0THMVo zS_LhWQhSUr(HW4Ti-uXy%lG+l9TBX9;A1Hj;AJ>V?LcIaf=&ocqyyvTt;y3aT7E^o z1&)>zE+LNsxhhshCSCBO(?D7pTDu1Bp?P4y#d?-Vb*{5g@`c&B0$8-L!@$KF%oqZf zNpQJo-6sWf4Vox7pZlJDFy*`8r!WfEY#sS3$Y{>$G)0`qX(w%MfnaG>9-})8{hN!Wl|z$V$KJ9shVv|%c8p$DPystzS7|e(O}R3hu;UlTLuLI^#kzrh}>{$I=J+7;Rkm>4PV2G z)5=w^=eWw~x>1Dwn5XLj0P=O%_Sx4q|3x)27YIh#uB*to_T^ZN9@TKKY9sI)WO^uL zK+n%>l9q!cbTzLf5aZ=y$E$zLj~||Mog=lu$0;hor;UvdZRCKncS;nkRY!nbm2}kJ zwubN=4fjKDf{;uFNfJPqV~wtUqN_#Z5R}W=JPu3SMzYmsh{>5ostgo{%ynpJ`j3E@*yy`LB_D1++YRL($P z@XfW>f(chPq7Q;!#&EU#ATCqU*4z@G+lx3mppHql zJw1{7IxACM&xCR$6>XYfsLR|lG(`$uPI&V38dvQqTv8NPU>oU`m`?DZm8r4U)YCnlv$)E7m8x-hd=8MSYP(9oSp8BM*?Mm7r+r2=FAKp=m*D=*lPQwZ2kxY(cKkVZ* zQd<;|5Mi*MbJ6jogqQ75iduyY_i2xZF@n@;)QtAQH!xgMoHR(0Jvlvz z7U4gpz;^+x0dg+S1*tgIz`m-i`cxT9hy$~T7pOdY4fxkK_`E9*U%K)K9sL>*Ck+A! zQ^5sM$z!&nit9mBN&)!|dHP)CJq`VJK{)?S6&}x)SL5`IC?4x4lqQSwAP`+JC~l;^ zvf+UrkGNl@Q!?**vTm`wqt!(gZ!ldMPU2z;9ZvOK5(|#QCL!8M7&dpA6x?X2>LaA7 z5R(--l-XvQR39w~vEQ;(xR4_%h$g!34LVHt!0^=`R5Wn=iqM6#GIAJTQjkAnROR%m zCV3fI4&r?%&e7vPPd=|B9xKzmrv`!f}oKRxjP9R=(VG* z%y#uKQYkz%x)vQeo^wFQg#o-|f-X1JCpxM)u-mrUsu|IAqO}KS?d_~7R&J{%wMCB( z8cr!){7J}JVAV4wt`IPv!%=F>z}y+C1Ag!JC}~alWUoe0V+o2zs(E7Obb9ED#uV%k?Gt)Z-^7BIr__qH<8&>=F+A&R+8CPiYzx_tP22M3>$3GwZw z#$!2U8Okyv#29Y*E(DGA;X~LyJJ5wRoytxlYJ#~QA+b0gxi z*bbc%p${uLL-43%68U4rK2tNC#6gxlc_w_?O{Nv()u|ph$nBj)b3)82R~f?Oc&FEP zMg)UvNsjt)e%bzmMc`M@iG zvqo29B7uO>;s{zbGRnxpwbSX+UHPAsZ`m4B9BUX6d1yo0P=}uwL z%(v4l2KmEs(F7VX^@PdmK?s~UMAX1oaAamzYD91jGyu&EyRKB|P$sF42q+& z_PiZKP-QG9C-Rvfe8f;6iU}C0h|qY0T@|#Nm%96~z#=dwZizkWxSq z@8=arm{p->NnTF_;WK7rfrI}HuDBK(fD*eRXg6_%!{4O~&-lbldsQ|JaZDI@ zwxXTLVAD4uDY*2HF*+4kqp7RbCDU9%a3l_99(I{igO$_wJzY=~YHs$<^6>h>46_3P z-C3=Lo)z)pCyv|_+0VB8h0MsIbx}t0ym7Yf5I`i$GYJcKLigYXnI<#m&lZ9prBoYk zQEIa?cMv@#+jZ7{QLVTNbZ{k&o*+0C)USaCVl{u#|*3S7Ds;H`S@#^hVi5(LvvUikg#J)Kh+PxjD4 zVccjq@ID7mdM{z%@uENR_xMv*WqLR;YgjUvl}QeVlGE=YC~<9Y*1&^tC&t?jT=?|N zCsbCVgHwY4c(R_|*>{cbdFn)q0yHU>sgg7fRj#>?m;N9#zaB97zXw*0IAMZR8c)oE zkbtzkJlr~*eIGP5f8_B<3N3%Bt(RWp<`Bu8wmb0n5#dTP{fuQjwr|dh{tf0g%FRXc z+#zF5Cl<7B30j*7#e|arsF^ZkdaAT~=%<`b_4mnHLV+-?;bW#3g#4_BmGGewDzCAQ zHZMvRv{Qf#0Xd~=J;r0a)Yrj0W%(I$?i|@uJZNmhV;*;e!buLaGzN2+X80WRbOkTF z$JQ4HiTLUHz{n(4m-dl7oXn*ol5)O3=>cl8_39tB@a;F8D2q3f_1H#@;O^-@m!L~) z|EhS`^J0<(YcJ!DH2t8iG&oemJF1n4jOukNiK@E=BjAx-XQ~x8D+^Wq)rpz5=sDA- zgKpxoB_enJ6+Jysyj+;E*`;rItj#wAH^;_ZyL!S37oHYk%Z+-6dDdU`E|@vofJVg$ zJ!Q})!Jh#=LGfoLA>@bNCE&h?Udiw_h%Tl`{!G*D1REM3qd|tTRMOQWF`*=2Nj@u# zj4h2S#Mb)2!UGI#K|C9Ce#MhJShk@Td5J@a$(7qHB$Ikfy}a|IjH!60LNeRHyHtjQ zOw-53VK-9ZP$h}5OJ0vGWXms#PDnf#TWNP#yt3iTTFQCTnuTsAZOvy`nkX=xYE`lC zu}CqvHd-mJl1kBt_M&D#=6bq9!ZY%YL#k4O$D!fyLb@oVVpTb?Xz3MHr!rlh190Mj z31>96M){J<{gbv}c?x;w5lAxp_Z#_Qm6t@*m71?JYflKL#p+t|Dj%=5-@bnPwXglk zSHJqzuYBbzzw|4={PnMW9j|Xc^}(lp`)~i&-|;*6^R3_V+g`kSrC+|}2MIYWUJQH( zfy6Ok{2s0Pv}!OKmT=*=L$8r}KcVXT?|tLzAH96};-fd;(6}(uZ@>A`N1y-P=RSD( z3LnYoGQ%~dp>ytfxRnP+C&hGA)y1i5#5iN|(GGg+6$7qoeDTri*Lc1E_RSmS)d8eR zCxgEs#M^`&oC0R`@AU9>1b^E{G@b>Dy9$-!PyaB0DaE_3_g=ijLVW6j4}c~Mx}f7N zhQ`8zmc#CHDNpqlV}xw3TF3bVa78VOzpkCin{~5|HLm(=^%08!D8^aQ>J^dzcep$y zUPJH&UEOywm~9ZIO*fNi**%vC(__@cMt~F_(xsPp64VA$!SIi;AqDjo)=tXE;L!A8 zahzEx4{ttn`4s?~$(Cn1nNt|duRNS*R>0qOJ0BGgl4UbLI!Ku770gmUC^^O@*A8Lv zmN{Urt`igjyfZ+OqFla-dQ?m5dy_#zD(gyQglnX< zL{|#(pvb(DG*qHt{B$rZ2*}lHv#~zcXk!(M{|`X2N?MLuTDV|^n)gH3GIybBxS25-QAKoa@n*}gEWNhvN+!Ic<8NyF$okRl}c@1JdB2XneGp z#=}nPNDRRB@HVUY5e*!@EY7>NS*2(<7DX4qdv88|`SK<1#sBE%fBygc;*bAt|HuFS z_x`WH_p?9qGr#x?zwotR{rX3*KYIK2?TeR~^cSD~>}Njx=}&*#w}0F3{@uUpyZ@E% z{@4D{ANqse`@P@wyT0)1y%(=vzeW;%QI0DCiF9k{vqR0l=86%FFe*XmS(7T{#MyzD zyDwSX5B}hv|6l&j|BP3|h~T%9@E`0S`cHo7U;Y>WrMDk3V;{aooq*o7Dnk^Y>17e3 z5~X)d{tisf)m^;drNPyMMs`Qgv}156ZVLwk_#QsSw-UYe^0Zx0S!ZvY?w zo6j3)nN%AAG=9{w#w;lp3Q^19x<5um(Qs z3~J~OHYcZ3phGFvlLcTtRFLuiD_uh_*=5e@zl zw!;~C*43moPP{G!$_X{Yi--^}3{7n&>J*$+^mH9b84&t(!BRB0fd{HQ+Co%xF%)%v z_Mbk2)s5@qCMLN=y2wS-?qVk@+}u?jKuIM+qw&dkavYoh9S}`3JlVkQy0}4siXw|C zr!n^;0b&z}R)qt{ryi_^7oFA%B5VnZR7pWw#Z=Tq3Gz7oP)XBj&O$P#4|f3+!g`Qh zGn(KhTk5Eq(VC>fl#KC#acYB>Gwx%U7R-QHOUNBb9{ip&4k3eSs13Pj67r}eZ6tHW z(t5-iv3JNdSjfrcD343<(qIbT);+oqyK!IR7!Jo4nViiT`Ei%GJ5)c}0Q3~?8oU}& z4|WzeR~~Jp2mc1(vh!6HPOi=~gTm2O)e>c^zf#xf3wt{d-E zc(G~q@*2P0`tsGwU;6ql{jIh2L$88sa$y<(<-^eCgw4I@Yc9_3|J0Yi^y5GNMSd-zznSyu{gdk zwQt~Mu|GG_^+uU^!=t%ipHEb5ad6&Zj^_S$P&)*b@OyyRs-)=lgQtHlz}q*kfAS~3 zgi-UW0*wT5My265|IFfK%!vPZyn&Ej-@}$_CRJ7FNDPT-!4A~>AAj@f_|k(#bN-Qx zV_{cyd9a)<@%Y||hh(JU-prw)fh?fH7N<#z2PeRmaT13kosO|eJHrY}H+TL<&;knI z7HQNJ#6#=FIpy^A{X@ktr4ELB9CpK3L6Rk5Cjt23H$O3W&l>T9#W{kOF(*_>43kPY z+|yaAn6?yW6ri3+!!;mM5i-x6%NapDm`001DH71C3AVnvfY+%O%qkrA~lbxd`w zVGLox#*IQD?GBX8dFL#ch^to@{)J5-wCwkp{q)$*o&r3jyBujKpp~{qirP@B0}*;u zm;oR-Ybwk(>WPMt1zR2MFxNz1`!9uRMzvO+7k(nc;miRQoeA?G0GrRBub{;}5j#4d z8da?OrlhW5WOr%O4X*{$HJ&=1nA zk|kr)xpXx(*g|XO;%NxZCl0B5;fTbHSN>p9m?NQ<^umrX-vIEqrsBNh(=7m`Eg&Z1 zYE@3k_+ZG4Em?NQlxsRnh}ylYH2uD(STxQ7EuZEKy&4}>)PZmv|3jsU>eI6tR%yDMy{5weTKwqM*1t z#OBTUxe9jLO*8^zX#H7C*1JJx@fiXg1d7x~x+W*sz@%N;A|-Cw&*_ZqR9k|1Zw`kn z%XuznWMW^-Sv#~!xK-4X7H0g6#9AVPm+kzJ887``{lYK&;$QyD|IJ_d@BYe9efg)} zyyn09#bx}#r#|Ri4Ip;_^}yBZaJ|VKbK#Bm{tNsH5Z` zKX{M87t^#<77h3{$Al!#U^?P$eo<5J`E0XnH=l+#J+WT;^=Wup}m1+Ivm0^aG7WfmCfkJ2bwTiRtVKrxD|ie|)Qw~bTntnW z&Xh0&o=Dc#0d~=AM`~)%MT%ut)l#^Cn8v(NB75tdEy6<8V~^3O343K4W*^wczXFsD z7cokKna5GwL5F6dBzaPKzr>6Tv5Lcp>+^yBM9Z#urSLH?{8(x4R6)4#mlQS9LT0K3 z>I^RbE?W|xXsx@7SQuwtJT4b!SI4Md_T>(x4cl@rW{sw|ht^N`JOeb{+#pRk8l%|!=yYbyfPdMoAR@qvX8)mu(MR#IVh5GQC zhp$w5@C71ZJmELV@pAgrH^2GOfBRqmSAX#@{ulqx|NZx0y?pt>%MV_>c>Ct<8x98` z{GvP9aN*a}RSXRO_6ui_b@IS({ou@(#2>%>xu5%g|I%OjFaPRa{cGR<13&P`{@8!; z?ceqtulbhZW4vboh96_|8qR@b>+<@{B2@ri{sE3sJUkA9|GI#m?4uU_Lj#RgN-dXH zYOz}TVle|}Tv&dA0K+dtR30Sl`pPPz^~_cG_W;v=sXSm{v!XOC4VW0qYD5Mrl>tK@MY3WZEOu@XH- zg2Bv_TLf(eO4fj^%K=5PYFWQ#QHCsQ7@lXbvXd>PSpW}CUVcNu?s+_dE~gOQsvMBa zY{g8cY(^yj zRYSN??jy+p0!ltdn;olhLi8mNqU$tA;g_Tb$CT78jhUP>(>}Ab})EMoQ&N+kz6Z_stdyH;S?@UXo&J)DpwK{G!UU~Cady438DL;1E zsxX<;21-D0XMS-o4$iS}r^9IzS-{K93?F+A`JQoM$Z;q-NE)6>IWup(&2U8%=3T%V zb*(paf=>3e%AL!;Zy*?X&vkB2J}c_Ojg$KBn0wl2JRZV`H-6)OYGqSP|;Y zT?2Jb1|!i?%AFiKtOlc4DuGM)?ZxS`L&d^-#UZ&g8aLs1XoI(CTTI_QWzs2}`W;@X z&V)s=cqhdahgLyq&O1rtpJA~WQCqQDt`uh>+{+5=%&gfqH>|O{G*h9a=9Y9(xj32{ zD~#fzzQO|2FnHBBj;6M2a}t`Z2_<%Kqy#kwc@XSnd^l~AF}>Nv<&^HYyoh)4wD4qt zJKH+s{HTC%^nh|NTGu zr~mYS{Ief^_~}o5`o*i4{NH1}_x9B*MDe{mUti-wVavHk3^M#I8)G3(vRT8&_}8(_ z`TocF>fkG1`Nu!}pZ)N^_5J_m-~7@40sn@TzefZx-XK7uz9Q6mLW`pNk=G4a*f082 zTi+I|2GoI<{|xCfbqpGItHdEDiz`=kwto5brudVmR=7jLy&#~3TZvTjpl$QS$slDN_OZNfe^|UfC>TShC&hx zV_Nx$>Eoa@M9%=+3UE3!CDkaR6LVNwGVpO$@wJNbMKIwZl3lk3oEw4zV6J{77c^NZ>_UFF!=lH0;7+IvcQ23!>q0q;Nfr^F2um z=e1kJ)De(EKK#{zc0@{?XDjjA6P8(pr?x<;B1&E%|POi-o_W!yaWqY%5Y z6f2rE4k?C}uJT;KM8su;n8Vzju(ka-N$9lRS_6S`m zb&hIEeT-ZMc26Yl8g{_revEoaz6^DQcet20#GtGm zUpnKZ>Zd;S!C(99fBoP7f$#r2fBSEL=2M@=FW2x^g4fw^_{{)rar_%M{M7#Cn>R>- z_Y5yT{prtq{_~&zmd}67XFu~q`|rQzKNN-sZx}xQ^ar2% z`#=2;{`i0R$N%h~`!nD8=4*W0fWIk(S73nidjNjW!@4lSDt$3y=m?^4WJ816IXeCD z)xW!;FjN8oPdyG3nd%4NmsVPf&ej=qWM#6sZJ6PC!2EQe$E3pqUf z=4df4&jqTk!jloL{c#4mhU^IgiZNXxo=mPOeR|W!B9jvZNeXsm)N;v5ofeAxZbBqs zbh>iNFi4Lq8t-_l&rvLaTKvkWj~aHn(kfUPh@|Z*vsO{U6}|vQuYmC+Hk2wIFcHlp z3N0OjIpee#qQQ}I=5y{Y6iFi<0NxgoNh^;OtuO)?t@fQlDTNjl0YQ*-`SKNqNj!VI zmM@Z{F(e0*MH9FmfsAHEd1*RdJNJG^4wj~qw;R2{!v2kmUHhJR<_urcuPLscM`*>M ztcR#LITMEC9juX6J$a?K>H~{PafE1g)sFDuN5|9At>P|_PcmSNTnfiwmlN}5WO&$# zpLI%0&-JjIB51d0$Be!|i%5Y`K$BW=<1B9a%ONA*haj5uG$1|Mc7&F^=g#7VCRL+4OlMYX0y(o;^w%DAoc`j3E%0|RxM+4G4^A%>>ilSTV3p=N+g zE|oyscQoknFm%ns$K929>p3oPo^*doo(QQ6#NjB3rTQ9(@<>J(Dr?{izL-{5A=O9~ z%^}efE)r8N+etyZj!FVx`RS~HpfQo{R(Ns%(>u(|@~lFY_-t6Qiw0Q-m+Z{&GFB{5 zwcgj+)fftYYd{DO|4z#}wQGw%xzhoF>G!)sP##P0y13cPN`^x!Z3eBle<^*w*!U;Uo%`R?EId;j@=_B;NW&wlQ6_%$EA zr2XZ8{LBCFAO7r@f9lI${NfkC^b=qD%2&UN_YJRJy?XOn-w*H~_2HG`d-$CqB>#~g z{?C8%OF#K%|IDBJ!tea&zVZ4aeD{D9{MDX08*#^r`XfRN16*?4bs40o)~}fH`WcrR zzf^#e&(=P&T~eTveokv0>9l<8AwIDofZyBi+Z8I%>vc?+{vr=ffW3M1x!?BLKk+C2 zqi_AzZ^0b$Z**|l`Kdh@Fa`h%T+WGnuEZm$^jxlpYUMbEFW&pyXFh{30azPXQ6sD; zU4NIN{lJ*?l-tTVLT0tiCQRwk>-^(MjZ-d=Uo1C=RdG(F<*boZ9-JKV%*khm!{BNO z*@K^RBsIGkf~C!>nKvoV>xksFHp`EhPHePP=eE(T!*uywF_SB|st}e8CcisoC||5- zBa^@dha@g0bUc*SkrQL2U%Sua~SSOiQry1KO(y5M;k_@I5 zUTq(>EVM?dG(5=&)zKa9$N*2(=P0Y_BVO+NT0#Ul`DK}QsJJfJBr`cREG;+BHL2tQ zMnoD}uK2w_7|ZL(73E5)<}UJq#X|o-(%u8WvZ74;pUypz943&1doit$gI3rT?%{=}viUMUJg+b@OG!;ptSmBi#) zpoM%&i0oK8S$B%qg9f`mS0)RsqmsborCn3SX;W0v4~)>VgsbhIZCX-qsU&krBETA$ z#BFOe;})$HXNGC}0~L`D54D4eEhXhI>DIQ|B(mNun0&WcfO$$dZV9>4t0dhZG=0-(nd@#NX`%1f8m+np zuu3x7Wgxi0Pp)K=x3Z*OXj1|di76{xUEEW$b|N_)a&6019(gUz1epPXms>rZ zXc2R&!M6n#OgRJ8gqHOB8r~WF?P?je6<8)V>u%tp(9OzfaOj)%7;2@bU}TM{)*&cM zku;Ah8*bs5pSsIX!L<{y+t651x-Z+Pv67k+%l?YHMtdEX%OfBor;+P>NS&V~1; zl8^(M>JyFv@|v$>ttsJEr?O%edusSDjUeP*33izM8^OtR&9C#(j4~HhXUv%SFaP?A zjW?Q=iLEsf`se_Fiz&%zt?e7D#zT?{QAJC=bje7*>&UkvsF}ol38gUyPRm@R#@FtAtrf)E8WRu8_7&XBXeEIVa>Wd?S=tc(IT21#Nal>T+8c*ubzlm ztLAnoHEp}fI*hii5K>qT*HWE(A_Ox)tiyO&n&>nz?lV?bR*HCXrI&z*N0_U;1rrY< z(J8=IIOc0xwh3<)y}->)uvrwELyonUQ)`C)I+lQvP-RbJG3{cEEOWLKb_+%{W7#&G zy(*#`Q0c&-DNOyFBhQS|wzOX0;)=sEqEekPRrwnc!{mmhI=-)EB57!=OI~}O8_6fJ zBG4q1SB<0IPg$*}U{>f_%P?wI%IhwnUIf`rC%;o>5Y!LOL@YVQMp0xBf!OInFYkXo5l1!6&h6|Wj8Er5*W zpw$n>BMk&|l_isl?M6s8DJ(NU@=o-?_F-G3ImE1yRHUmL7(z6m#d${>&S_#vE50Lb z&LCnLQ`Y`}l`s-}f#fBmPFRl^P0%V<$fRdNNnbM#gqemK>#$(mRBzC0UPx@%GU+9U zBNZ|G*DgQ0Tux*kkuOHo)6(}g&@z_>L1*%_Oa)R^g(SSdk6~nlq-JZ$BbilcZGb__ z%j6tQ;?d}65EU0rCqkvrn9sy|UB{{3@RbU(WJ}&O4&CTFTR@yuM^ zv&jMOIr-4Po%_Wveqm%#Z}^GZbr2XB;4b}sZdBQF^DUUm9&_xmTW+-#Tu$~+t{De2 z_IqJ?O&;EO$E`yR#GylW@xNh-*DkY*FgKC7YO1Fk!_p%bY;;FYu&fvgzh4g0!*M9{7Z-MMKkOg&)zw$y!t;;>j`< z$t1l-0?%L>DH4zbEB~dVy}HHZka2+3+IFR%cT|c!B$F*J02v`16g_?(sSAcFD0vJEeSVsjb>aO zbrceW!bc`7kGM)6297xRvOz79pb8~=Sp2AE&&4AjY9@JCX_d@pP>H0HWh5naku499 z0@H5Na7hrd{g8B!N;4tWt86ejF%YG1O|7Z8S0X7G60R)PDAY~nKs?#(;INNCu=0FpU|r_INtRK` znqKE2Km+UHMl~4F>E3X(F%&2&){1-=y7?Kr2urbUpc^K`7cc8YEt|HuKxoubtpm0O ziF#!xh1tl|W=uu6mTx3$I$EJvDr&d%f^WH8Em$I?0c7WkVh+?AU1~``$7_C3E#T{{ zpd+;e-CjvCQF>!!!*YuqzNil?I#HY|hDG4>(TgEEvvH>Q9%FTeb z)lOGB%(OcX?7V@>e)7Q(#St?SY6j0r%~-IMHYDC4={x#|hK4Tw>}Njz`TycoKWXyB zZRl$zp|c(F7{Jj-AN{qjfBob)y@|VhCdS8@{%ex0p!8}6eYI=%;XQ<$=0+c#5a$QN zDBPy2q>Hbr3mSQ#gccfs zQskur7ZCd)Ib}W-QB#AJdfKhbZzNsAO>^vnDB(<4Z#cpZnD8yx3SmJAMb5@2#HA1e zL%4uLNan2=fF>t!CY&xY3R6|95>ogV>bV!o;0psblC4=j+>4`&a4^kqZNa|Va~q0$ zghRn+X;(|j6&%8r#)Qb@#3a)C2=5lR?si**ahmiq-KTNj~$YuMd2q#D6& zqflbxxGA2tz9L_Qxda>G~8wpm6YqW%ChNT+4TDXJN2vxO4#>3j2JhnqDA=)sJ zGzt~UlvPWIN>TI{6sXYBzGw;r55IB%P#`1?LUJtzX&@z1VhNv*MWvkDVJsTv#hxQ$ zhPZd#vX6Q!LoN1QQCH`>AkbOj+f|xosuK06W&dp~>Bc_GJD0QvH$(c_kO%=7#F}0x zQjoQ#^rW;T8rimH&@rQ42#umAvj@vX+$+gf;!dR_h&Z2pp2a^!_6wRz03C+@F z9kMKKGPY1x(>GGY8faCbi+-tuQfby-?XjKUlYEh9F4?j0KqSTIFhW2S32}yeLg&bt z+;fZmkFNUhg%@2opx4Q8(wdpS?$H?@9%PQc>86{Vb=KJ*Iq&?r^XBQPQ#9Fx$r)#$ zxn-XTfo7i^1@JCUvQCp}l}u_rrCAkoM&6J!!A(Ukec6k@cG+czAAaQOiSeP~;n-@} zD*5p1!N2;|FVFqZ2T>msnBH>f4K_k^zsqk28VmQFZ1HhSu%nc8L{mVr5@avsf6f^k zB(x#tl4~MnDXoKUiroeU%ZA1?=*~*SMN*{ghot48ie#deD#}kb8U|1fcd(^BjI$#e zP4~5GHU^acLSH!Hp4wKfmcm5I<3M0)gjB$Vin>xC6ea0%z3x=0+IjlEo-#|8qI;lI zQL8kZ5iO#~DVr#3ln^E*Qb}r>GeBZ|nh_%Z{pJeGIB4$5TY4K=pCY_#i8# zQP8*{n*r(HnNn1c=q$x8<8miPE>@uMZx1z=1?(&!r;sAoB}7$3Q^r))LM~Fv{%Cs< z$yAfgw7*G1SNEikCy{X3^_C~!bpM$`z%4nQgR$?TW7>A@0Vu5TTrpO~5^_OQ8q_4| znll*=S|j^zHu6eRC{pnUP5n7+`6Nk=ep8LaBFzoNLCr~*Q_3ywX$ZSkqLOWS=V(0o zU6+fZQktsef(SLWgec#gCdRW}LPOiMEP%!bn2<;Op6tb z%1}#RjkJ+>(c6RqRgnW#9Y-y7T$Q^@GO9I(>mDfH01(jL5}YE$s16}17pjqJF%z@^ ztSQL1Re}|?TF~^6&V!*rQBA@f3W~Tbgtab8kw7~GShGvgxFe>Z|s5gJy(&a#D| zN>L1|ch|*o+UF&O4pCDpf0I;MWpki8;fkvhBFj`t9U?g!MM5C#5S=HOfl!oU2i;|- z(pJ^t%4GZpMq&h%mJL~~D7|Rr=zEQ>lShGIs=cq;38nX{jU8G_o^X3PlW~)t(}Hn6 z{-$6C!#iZ|fAFER&OC#c)o@D*-^x4(%y%(2=<{C7?YH0l!iz3C_QfyOApyStfF=`? z=yPN14K%Xs@9anwUnT89ApuQ4CdVfx=Wez2CqMPcSH9wvTtkRs0Hy(&8B9(N4-Q@a z^~=BbrO$KIk>>`2C1ZMrW|SOn0IGZOM%y_CG%w0xZ1+towiaAcmSRX09Fiuw8A(~# zO|^H;NJtT8-04A;k{2kcMUF!tOuP5dl%uTdpgnhXWJo&kJ#N1Q>FJ$Y9?;4wtx}i? zre6>fNmE8mf#a=9`YAU*I3V1Z(5croL*|^bzfKgng3XXQ@cev(GU45+qft$9nac4Y zLBvcCefHLT1ScD=$QjyFz`AUPs#Xpwa-c-SzFsv{oH+ZW_&E;b3Y5%-nr4B{q(JNn z0mJn>B$d|C%*N!2+>tg5VaQ3m)#P1uXF{MRD_koG2LVK50>>a{P20zalrFrd+ZT2|>~>$Y<=}g((tftVOG|5tVfQ z4{j!Egi-8<{KqzL;+1vY#l^EfgM(^9mWNTKc!zWC&OjHk&uB^mb{wpHb zf>T{T3Bs#{^}{0iWf6XGL~j=mk8D%u2agCS_{!29Z4*d&d&_pkbSaf+Izp=UUpy-$ zAq@1=3C(pb6@avf%UIb=7)YwgO5`ZfD#2_Z5L%jnD+S$Sy%t$l9XoB{1f`J#EAiSQNuvib zw4_+}1nV+hc(z5$QAg;l1ZY@yI$D%Bq=LS`IE)htVJmk~HXu*fN?F>VnmtP}daWNrs*fsqxWX)C?mS z!aOoXf`XIakPfvP9*I&4Xc40lrBG-VqD^G;_NK+{)Qmg?Ro;RlsFsh38>{S^0*UZi z?6l(xBysE*i>y~kn^mIKG&J+2D7vKeRfucM4uWJ+ z@H0?{euEUXb9K8G~RaL8Kf4m-Ve+LC3a8JU9r2 z?+p5wc^%WNjpl5uNxBXIAj{K~$AV{@3Ij88)+MB9BG0UU&Dycmysl|%&E&+uH0F;0 z_A`WL&6=ahx_TL-ToyuItK}0#V~K#(Z|X3P z0LE60^BfD0xkG}|H#jh3bcB%bSU%C2a*yFHO+w(vc=4hq9)0AICl@VTvUthR^r2@S zb_DrFTA@1>A?fi428o=bsbtDkQMBy|wvW5x7A{=)@WT&1{^(P1g3eEjjppIEpsjB=@wUjmsmdnV7yZ?XC2n{KuleXU=&h;+ujTe(yC zf{VE68y$!xY`bPMPFog36(}2}^6Ql31s<+Km?oX-E{IhLfb+r-i)%m6ks(`Nm3%OhYKjkJ$&8j+AifvY_Y1D)u zBb8_dE?fu|+vK)Pm!+)go>U|2xAnwH(Wu&thRPzHrBLfMS{4dhOL*=M%_f3dFX43C zl|WFF_gN$yDlW@J1EzqoV4_ysMy{zzpa1MC3|F)92BZU443l!?d79C~Ufp{Mu<|;E z-{2^3g4TDNb~r|?V56GU3Z8`Bs3g@6VgwamX%Zl2-IOvRz|xIJD@=#UK6Hd6!|qkG!Q6+T zriJH1SW33T19_F_0T{^zlfdZ|W;o$NBm*fSZf9YwM@ddE666i(5_Q)#7D z&^3vq$XyC-(6PXH(!@$;Y)BHcJU0}XA}mq0hG(&~gzN3n;`;uM-q0ZB2x`TOB`2PE z{PLwM^y^@p-W?v&yIEH(fBhTY`07``YR&kXwfflr^$Ojo)3piJ{zPZlfPGMao&s{H z$kW>cmwfJXpa1+9rVkJB3kEA!u9`J-=HexbPdM>4uYJwy7<4*FRBx+KIJe9h-t+$R z6IcJ_syVY}`8+*ugB_Z`&HS^^K9}E&;1Uo)0lwCUqKg(i{884ejUuLoq2}Gnv6jlQLTgHH zC|7c^ub+wNTTeM<>EcBwV+)1xHETZj!GAmWpr?mL}nDA_j-5wx+OT&9Te8(vWvC9!Gr>las&y-EXeB_Ub?V=}-K?-?F94R2yH-d4FKz6jnpjYfV zr;1nymrxam6Uaf|vSrJD^{Zd~>}NlvXCHp}!KKTVlRbbq2f>o9Lo@zq{nKYm-)yr@ z7i_c5p1bd{?|%F3v(MgJ&znm(anQxZM;&JqoN_>fT@o%`Nq$kXO)az)Z&LBZ!pTMky0IOiODD zXt8pXz!F^y@Pyfz&HA$K-ATg|rMt+%QX*?bMe#U&xaHBth;xi6wDbxv@ArsTQF%Ew6!IhVF-^s2L>(t zNLs9ta}A+gYoadRbb8iSSWn9VtTT6w#Re*9@<83=k*4f;U(-mXjCsR^8#6a6L@Jes zzuEaPlY37sLs01`7VMyPHXXf)#%TtE+$yE2SJlkUq_nP@ZI@saLS4@|TH6a%j1ogA z8nvnfRI;gPwUSll7QOpv9^Ei$DD-P6u)xE_S|5ws4u* zz?S(FH73BG{A`$urr(sj?SPH9SUF zCMv*$Co=&PwImn~meR;}5(b#OtZ8!Vt>?b>q}QMN?ssB@N$Q}Ux7Nw^3oiKRHrp(C z&U2o_vEoP%yS?6}NC;Zr^?O`RAkm zS%c0qzm#T>ow^6_zyGG2Zem6&cYPSW%fg+ha1RJio^UOJAD?();bUL^^5?&G#kcOi z|6XRgGRBAEyJCF*h$hu5SFil*U;ldRZMS~o8<)>tF#o8d{_(iuPuPCD?U)`inM8%b zqrPV@>4O%hSRqg%A~*0Wjf`>@Zm~Dzs%p?Z}3C z*;Pq&mDsD;yN1`;freEjRuyeGx#U_=EftAoDQ77=5E8l7h;-G~B|>lsT{R88uF`|l zCDW;=Jb;^^yT#W##Xqdad8rL-TL#Lr7jHy<_ zb{abdQub(rfoz~Y06;T?G{y~QlZqzWEOI?{8ttxHAepSH)cv1^L&24VOs4^p)7Ij{ z?rckqBF(&mQ&pz4?ku2dDp46p-O*MEV%pw>A(xF6OJQe?uoHBP6j`@=EZPDYCkx8G zY(?uBP3RPQ@-9q5)F0Ya}alEoyv0t6hy*Awn`IW)+SQ!(?GFFN^_OXSflj~_fGe4>vDq)j_1wl0rDR?QTFAfkz? zhkI6_$uuhx8SXa`sn$4R49`2IE5IZ#*NqOc4=dutzwIQWP84b>U8<%3MKTSl092XwnWiPPA7*Se{#{O?>TkplBGPZJvO$A;|f5x++veYe(K^aw%A-RZ%uAS zMl~pO;}5f3?)M(lT$4#2Im3eHfjWV#e){%zzJ0+3=il+yJGdX5-sTcPI(v=?MYBWX zeO_>~kLw%51H*X!=);eF_TtZd^NK5uJN~%Wo%DuTvu1Ecp4*%zr%mX@v$sW$9A{23 zIU=W2gOup;40D`s*U#H27n}xXlwg_Vt?3)!om3ZH__42k^(*6JtA>Y1$f4X< zN2ASe-Z4&f0fRxZrcb|#t*JkjFsu{dYuLVMk$kfK-S2+);}>4U;jRv9xRNqBq;Vmu z8n~!x=yJrM(W_2?IOLnS9)E1%r$6NxqbjkNAPmK_Q{%ZOO0y zB1_AbEx+#iKm7i>>n^?Y(ign&MK62V%jV5nK-^5*6>9`S6y8b+MA(Ca-}~NoFSzi6 z`|i1Ccz9SBaY&y1Maza1wL%31yE;OaFN`&A5hJ<>AA0zK2Oqrh${+2#%P#DT{_&`P zs}EVhyC%M@yM$4T zCkkXajxYrL%MunZ_M=snwNQf-uxcAeLj^%TNraUZy*7;@RWMy~|39Im8>PM#IMkVP=NYoRE?~Q@B-hrsDfMv<;4kufzs-dbK%yt0L?m!=A zq3H{r1TfXU{Ogx7MPNe1H?AfhNUUAU4Zv@Ff-@g02^0?!;1d%rrrDteh@VCGH?Js`y3+890%QFo{dDme)U`57x z@z_Bsz3N_3yz@#dtCr2t0+A^(3~VMfHw&dF0{K zPk+y%MGN)tDndA^87o%PmSLm>Cx)NOK#m(^PJi!vZoloe(a{lZmmw5x2tkf(I6LpW z6VC%ELgyeQgqf0N`vedMWI=Oo%_;jrZ;@iN|Non9hA%Oy4;R?=2Q}^z1g~RYzU;pz>hP4}S22L4V&Pk3903i$8tB@vpf1 z?mIaIQn*f;6RJ>rIYgQG@P|M2&Ue3y>pvVW(x%V!h%=6<>wK>m2DQ`i;ec-#BO+|; zt|0Gr=x5x+=wENY{p_>O{OW&yd5B9DWka*=vQRAA;^a=`P%PIJ;NMh~zm@`dP%^;R zCJeK2LNRAqvvR}zQCJd+MvTjb(a6`eVm|~~hEr(An9)jy(Iwb~s7}Bx{-V)r&Lu!sh6uHi`-=s@=l@^YWlGh!S1+7l;i}R3BPqQV3)q z{K1lF+C=qbC&PSHvPx9lGKWRQvS?@|s`#q%pXGX}Sg&g#ph~IXJ6k&Al3)=+HgKUy zPbH6ps{1l41j;h2TB%S|rSwxLqpJ-}mw^b}DNWv@mOSeaCi%{P7jBCXfRUGKs8lj? zqogFhOwARMH`S0m%@{D2#+;bye4B540^B7`sS3}U2>ScO8Yw-k)7p(nN=Snmn0c86 z<#KdfqFAA=C6jQYdzC2O{@KsX0KfV5uXPK)zH7rV-q$(#!WX_^_uY5nn^+S9CtS+- zs)bkCg|sfV?24@Ti^^OjP9=Abze&jf(=?_pYgbQ9obakwb8CvRHA$8vGtP-MmtFof zJk{fr`rfs5Mr~7MMJckpvg(vZ$H*g5w3a)p6}~H$Z(sz`Di+jMY^p5kpH_fG8aC$vej zH#$6g?z!h~xBd1^BDudy)8>ga)2EOA;ZHYw=tCdEs{y7E!ox#=1KTm%TNcq5C=L$w zf8vv$`2P35KYesM5iuVe85!k_`^#Vc@}vLx7KvH^1fNuYTn#9OH2oUaoMBV2~S8w3RUknQ#*Y7Z&iJIUzY@at~Km4Rk*e zZw%Pkf94T@Gw(6Mg@G@Kpe zMYmXAvUJ&7-*WOVfANbsvo_Kcm;)e;>b$-WFTA;M79Y3Kmv4F=S}{;#+6*dr<$A>( zcii==SDkRh72lXXGQuGNsqrz4u5_Wr=wPxtfkj@AMa1eN!qtsKTnXXjID|p|SM!j- z2ejOnA?L_{RKmP>I&(lyRB05mQ<)gB(^Zn~>aJ@s8rJoea( z;ZcnN{}>&f6G-2*0Vv$gLnfVUZolG|F%>18PE~UJJg$cXv1u6h_wTyvZVTpbqb-D_ zs>2j)Y%tWB@t=48hcCVKbEK6!sZgcE=~}vskpnC~5TuN*nc$u#9zwt{34l)AyctXp z0jq;l#*p?@n{2$vp@$r%V@D!XN6MfgXBA`CM8>RpSgFl@ZMnRVdj4cudu{zf(gx>z z$|0F#rH-SLTwv{w`k>O4NGYkoijVE9lBh*$s!GUdSu}7({=`vkWi|N{NOCKpwSx)X z^~Td+0hh2fgz{%9hfGJlxDy$GpuLeAJxcPS(aACaWRKRRpT)Hf6;m2}Ms_E;mgGY; z8Ba8?S|m02uN7-q*Aufth9-(VG@V7UTr`eU3c;=36iamoI~E)vZ3+IvM;7;XR_@D9B28s#3f`WB!rPmGh5Xs|ux05VnUbz3rsl~bUxnHD$! zkO+$zwW+x&9-ifH$teg#NrYFf)D}?ES6l|p`jAt>?Oe}mI3<{5Ah#X3loO%S7L2Gt zN_^TCYBD*+O8SjjwvYf4O4x4zM2@t;iH4Gx{7AOLqedwNLK;!RDP~gzULjR_Q&uf+ zKSHGRlTm2pLQQsEN+lB-x)dZT&7wV}d?$mplE5l;v$K|MiWTTpkXEr)=u(vrDq2g! zVA?-xmPE;X)ssZEHgWw6#TNmFnY;;vjc`=^3;yL%-_L(`EjQirz@Hq}XwjT+)6F+M z=GYet390zjlV!ea>|-7mZd5Kw+f8Aq9<7kDxg;EXox9Xv(P>WJ{=rWLpZAduGhKkr z^p5!lCrW?$i(lUV!2R>*&PB!tteOy1CUaGOYOw1ok?oV0yU5k})R~oKyOupsBfFX0 zQk){Wli{Nh65_ipnuSwB)ONg@QX(x!h)XmV)=Fcbgro#hKAX%yG^{}Q%kp6~b5MhC zNw4LdNWe#5p(v7`TwOECUE?1-=iHNC^BQK*ae9^hojHB_H@@+W{rB6CA9W_tsBuab z2)QjJrcgTO;*J*2v-{|jA6)t4k6&~V=dKBeDI<4uFhksD-+fLy?Q{$&)AX8R2S^Y~ z8EX}$i<%ZgU?o0potjv)c=_^=o_YF@fApgnqceE>&F}~_T~0QyWlqR_FLSrvYKI+m zm^XjHtXZ>AT)uqy!w)@t*Ijo#{P4p}9GRo*ppfT-bqbk))^br|>C&a|diT45dg)7E zLf}jpbqai%jy*KW!XbxeuT*0&Ax8Kr@{j)0)2MnpwXgrw_q_9GKmYmcS#wsbT#gX; z?0}-_IY%A+eba|W=FFVMoPVQ@@DRsUH22rD2b^5zSV4EP+GUGb@a~Wl6q>mNM%>}z?Wx)iFu*v7aPdk48g*%%HoY|bl38DkO zWK)vOE}bBl6@AoGYAU@3E~|wkt-LP{qhSD|IoiqU5*s{q#DTwjZIekhH2Sa#c+=V&O_O!icuMF7e8~ zWUHo8<&X6Y>Lh!-G2|INe03HpNwsMU%^1S%S;F;Rtk6qtCxLp8flHtlX9LK=wb8Qu zA{p$EPthvpkoBLU@vfi;S}3n)88}(HwQFd5(QfrdHCW1QPG)w#Tcd@T#Y}7(Y2zxQ zVWD7UdMM;pVV4SO77-kk5HH*ft^^F?l^eoRm-s8IhL#M(KHUlM%b)+E(h`Sn-$M>Q zXy=`F(iu6OxCKYJ;hOrv#!M9_N^o<_Ymq`Y$)czEQ7S27nOr-Kr!8Oj!lOTT$>+Gw zL32sVuvPH7z+eCR{~q&C$7tH36OAFBP~t00h^j?Un$RIi)8{6$q>NaZMNCm`A}!>q ztFaW4ngU_l3!*JpD20#+sbvn1D=c=GK0hoLDO3^+2p!u|mAvuO#;tm^WgSf^SED{W zl2$SZPv4jxcgR-*?|* zkNx-mJ0{&aw~d+*z|sj$O(k(;k||x^Uv9hoy{EsM^R)3cAza|B@XXQC4}S1m-V?>? zTNG1(3c}`#A09$yYp?yuteJC`Enmv?osd_pT0LXt^dk;G{OF_q z&t7})v+>58FxBMf0aY}acQYBi^)G+9@<%_s>Z+?2J-LWG@)alU&MaM5B=tl%}Jfo>Eo~P`oM&3LgJ*+T<6^;OSBzgJDRp))=L*M)E z_h!$Wy=?h1+~8g~0GW{Pw%e`;AAHE(`|Pv*_S^I8c%vhu=nV`GGZof+o1zXOgSuVj z!G|8<636Yg-^z=UcnpJqIi}mOMwtgw-)HZ=UVQ9}4?5(~dGqFT%%kCI|41Ki#eL$5 zCw}+)-+t|@|NYDV`%7G9w!LcQN<87FsaZ2--gf(6-}d%XzVKgP-gx7UIFGKb(jZVw z%D%@emv?acKmYR+_uqT(jOjBuvk!wqBi^LM)4&Vn&wuGlU;e!3AGPha+c*j-mgG<$ zTQ&CRV~_vk&$n<5ggf!>x%+N*7lQ-Cr23gh9=`WJ`|}=Hwn&b4w2jCvadSCkaM{Wg zAG_eA^cP2ox|N5$hz_i#aqw{HGY)?FOaEoRzdL|8x;pbnwa2qQup0+ z_aFcC$6x&N7dQOr2963iLg2MvgQLUT!o=|78E8ytAmwo`&8P=W@n?#wU5lbw!flHL zf@qRdRNAGa*}wW%i4GOkjG9*IXeOSKT5`$hDSW6z0hD&~28K2^Wk8mkj7qq>8oC|y z>}0Y|NV3r_8uSk3b~8gMD-1)=g)^-!nOe!FTg75cG^d7GhZl`lt|^wLgn@+KWkhmW zyeG2(YWhI6Qm89%4Uf(eLnDMsH(9<@a=q;go-J#0svOU3k?4ZdEY$VFR|45WMPgiN z{3m-o1&VaBsRIBSc{@R%C|0gp59ms)K&g^M0pb(a7+d`0Q=68E#a@%C4mmgFF=xEO zgG#4z*m8v>g#?-14w5Ati@uhlm=jIyA>B7ROGtA3)#G8~E(?sTSJb6LqX2dY6uFvS zL)n(qy?1m?U#K2IwNhA!u1H43j&a?H2R6bT9tEBvl|%tZ4M-jG7E9EEDT9;*5{=Gb z6x0n`ws2aFq#n__Q9SN}?9Am(ERneLJ*5yy;gw+XxJWc{BSwNjFtx2@*lYlykh73H ziKfs~O~fd&*dpiT2Q}|;Bv!Vt1_wAMb`L%J$ZfaX#)&#XWh+C2L(h5kbC}^VV;~YM zXB4pHc86^@a}Yw3vu;a2nv#hYH9r1cO~VTlOd>c}eefX%f9E^j8{&n4x;W6Ule?4C zn0_4ne~ylCJUJsE{-dbv;{y4m3Jay*68f4nAxpQ8v&A!mr5sjis#r3CJv+#1cp9L! zbCCA3p%6G$5L>6DaZI`6whgt=(v2gM2##?u7iP4UxHHQ{(6$f(*9K4wLjq61ni{Oy!MZO{Nqo4^doKr;%*aeXXo7N(q&6ed++IA`^wii3C-k; zXT6m>-$22moai!##q7#eW2e6R?GHWp5R*tw)?$i@-PqXpo8SDFr$7C`@o{ecv7GG7 zBmHU86d}nOoj+FKdcIZ`)=uMW{k2zL!%Oa$>)gMtKCNE0`mkpl`nI>fec%264q48Q zYhH*F6_!lR2m7|zYO}599e((;j=1-p`#%1Oi@yK8?{PL77gwzs8yOvCxH3`aeYNlZ z!25UDeh1yarnB7ST^EEj2aU9O6kc~TU{0mG3)Hm?F|KdOQ}rREl^Pcxh*x30=d@FAzWJuvGiI+ETS+oG zxz7Dp%a*U)Zrg3&_V#zY@P#iL86M<Zl+7 z@JC!3yY05WaP!rw6)T5EM^=rEab$A+_1FLVzn^>lN6zQfto&TYWdG!VZnNS^efgj% z$=Ydm-F^3Wzw`jF4G<5ZjS9(XjNxnQP&$^Hh*6qaBJ@~g z*_stJ!BQ9u>QHU8!~nX_{U?e9EGM?@X1wAQc`Q=G3VGA2MOMq~(oSAjG_+X?Gj<|M zo0AY&R#OH-%}sL>jC8|I6OwPCE2J#T?A@YnXB61E8iMsQ=|Tzvaxm8#Uae8r*=B69 zSk=x}{)Ei}(8In-n2HiwsyDU7T9@}Wl;jFl(d(}C@IyjHHQcFsp?Jf9CD^cMVhF|E zYE!`0_B8^tx7uq(uUQd>_O`lYo1&*y@nx2>4kJRy;EfegMg2tL002M$NklPDm|aVM)rRH5o7#mcx*{D0TYe`m1INp5$coZo9^}ky66kFcdZ%obY}_feVC7Ms zH1jsz=rxFRO~y||tr~to?p4XwQ0UZXY=vTO=XHiduTss`yv|a2T@1S7g)GM>3whl} zNMkNbw3(UOT4z7=y2Xq}nD_1R$pPV}Y8_RV?m9pvo85~w!c)j+xke*f%1-2Vh7eGr z6|iNnG{Q|S0a-E>*$XxZji77A&u)osN)paG;YQxr_^Q71&O3NXpYG70Sc)maR$Feq z_dfelDR|zYWIwc5D4a|z>);5Ae~009v`I`vR`h{_!W4jWfUJ&u*5B(iEaw6>|MW@p z{=S=TykYtB6?`?=A**J6PwUvXvUhb!L8vZbYKj{gS+DM);(Axb`&p>rWawm^+@snt zXPN?0!La6rWDt*?ZBHTe184dgU`no;k6_XCM-|V7U{*7mU_y#m6sAK;-R1pC3NyzV zzDNojr4}c+YlRnXa%!A&y<8B`@4InUc5=;(k?A+zbjulMoq<)}S;>v~I#!W`xTRZY zxHpTJ>YV#;XaDv$znL+8#`yRcTAU1L_J8={&wRrh-awQJUrL%p>pVM4TFSRSM$;6T zma?Fj#h6?>G|US($7D;d2^u)%t*3nEvzP3<|9)`?dY&61FI9)42S(#Q@dXR!UwFZV z=bd*xCz6>;ash$U)g09@k7n}CRiiV`dOtVK>&7N-+VRiV3Bvyvj1Xm7PRSoukzL~8 zEc?Cp-oqK1vGFnP)qy`}&g?VJKKqMb{PHuOaTv2%jx==3{scGWGb6>O-ULMd(XSrH z9KI+IpS$#utFQj?^pWXft5;!Te|X+=zxvhx=H;6!R;?W2g-#spjEt-v8{>HFvaetE!yjGAX~x5rH;<891XitHIX*eg4?diD;)!4X`Zq7S;Gz>xI*Gm@ z1`=f#iAmVVhS#=?Z#lJt9nb(o3mLq00{F-MYHKDW4X2u2-plOTh6q%x(zj|^m z-M^AuugzM*n@>*36qb6DQ@ql5)mGPUy2g@pTSG_*I19Rm)TkvpWU}lAtOaKzFT6o$ z|K^g{1*VHoIH?i>EK8-5NM^L+U#pPPcG#;VdA4UHQ@>cwjHJ<9wT3Iw$)d*z0@5rz z!^lppsX7vxJv5SPG^KVcsE#I(G9DN#?GcoPV=HL12C6JQ3>I=P-! zfzvF59Ls=gcpcFpR7ucchvN9M3ZtP|N|<5T%L18gHR8F3NH8TEHI@+57R|WK z-$x=FR+XkC->vK1TP6!unCwIat*`FrI<>5(WiG197qu&LCS|eUg|_QO7D?50I(FKf zcl?#<3t)s}1fBTZX{R0M&fQAi-v*P<=Gvg*Rz*cxF07*ESk*!&4T8{(uV(?;jNP}s z7l`5lbDq8S+I#lw*`6IF1F(1xAU~C=nTBl2e7GtlIVWu|{dlR)<;qO(qJohy8=3-9 zo3-Jza%qi1p{RImQOB#ANML3(ywWTxj!!%#X2xLul_yDY7Ma_-X>dCO6W?JTxnM4) zn;R&42PI4DT8Tp8P72eJhoKKOSK#sbdFooH-D2`JR`QJAWgOwFx)%4iON3u^I`gcv zu&&uDG|sJ0tl?!c-}=_KF8!}dc^4+L9d7ZT;H2)_3Ed_%ja&Z*zwwQ4e(4KeoH>IV zNOX!A_c(Z%zhK@u=bXcvC3Qn~T+76?Cvn_>P6s%>>VYV_0^l)*FwQV7#53I)zGi}% zE5}7=eekTeobuMe=|cGql*~$ty5(9V#`z%zFd+$nY`(TDj&vXB4QtcG|u7-G9*qALDhUBpC;oM)M*jegxoo&wbvf zFTP~%g1M{5$9$|y#7u^jMmZ!;wDLluiaNhbz!Neb|HOa1-~}(@iUgNmRuO5wO2$hz zSFBv6pH{{>B^mqPB6$9IVBq(^|2-MN23wCj=NCE$_;HW7&6+)H^#nKY^u>{OnA4DH ze9$p~3OZaD0b+7;<*HTNZnxlV?|jFWTW-N`Cu_6QzZW{iPyPmYnXfM@4$^IggcDBWfC@w4U^L?Nc8DClOIim4Q~>0N5v$G{N7Mgw%s(A*#E~2spsDnx1O>dm z=}{4#dAdSTQ40MZ&7R9p5}uSP5d4v80>={o!9YI0A{VOmU*12<5TY!-=`o;4D&nCp zjErJHHM=Sh3QYpJ02&2usf*%*24lspJb(60j#Cs=pjrf$mjF-gdiVy9u*aG%2)da;ak$ zZWLEos7K|})KObLq&kxhi_wu2Tsa!fd8u(@y<$3f(8H)aaX~i3wT-A2T?G;nAc9#s zBB>O`R$C4Ca+{j%Br9gBLkivYWviBGVdRX|4*`cjppukcQ*AcYPx`bNXx)G8r zQz_V;hQ#lPDOBu4(k`oj>0;Se?*WQrsWd)kwE#&1nK)$z3sA30(e7flj8xp9dxYfd z1S$M1fHg7HgF?NqRLdZFUFX-dkSXj&xpHbls&taJ%xtp4$)to*Ol=6NWb!zxasFnI zFeVSzqgvRdvMxWng`h<2>r(6DHCo>80=5vfM-l0+JMZFKKA36yTf3IK|CwxZo3b|) z>C}npu%rZ3q>%Kt2UO`6roKZdN`AsLi*FCzISqrE!B$&tvDH>vJ-PU=%pPbW$8>%(5dsrhCSlx7t|EW!xF$iK|F{`6oHPPGLvqNG zZj8ZObe1e%vgaOq%$v7>)7wN#Vn~Tj$|9--Ik0Q`I%jq{IWTRI6RSrYe&icZe$)Bq zpFf?q!RWcn0Vd%bjGXt8kL zH{3sT#s@yI!;U*>?&v(=;JiH&SXzaoPO$i zUiy-k@yZ{B3510tr7UgjxfBFZ()64RBBlrWxgTlYz4rXrMIV3NNhdB_vXlcNt{!lh z#5rpYl`j75#V>rp(M+Am4R7Qe;O-P7$O~0cQF8G}V@EF2C(fV@6W7KYZS?OSIq%TJ z4&%6jF4BEc(b*115h5s6{N}k{`OEbH4)8Ad>}R+TF=KS5GNL7eZ(s0JsXE^03JKJTX%}A{_u}i0u zfVjs$E0U&G%2TJ{RBiVf5w=rgAgT0r$HGM>%jL98WvUn5xedj(2fc)Q&>U{3lXW{r zm&P^t|6e?`QT2DArmeDaC1spd?8i1R4yA9sqUzC9)-PQ3VIPlQMM=Y=j1N=Wwo7dA zibsv%OiYwoI@w68O2xY#eY^H5$p*|~4Kd-iup8y3mf-5Ce_&K1X+#G-A+~^RDhwJg zIbw*wajp}&5{zxEjg_QY#MFrWc8U@jcD);?CNp75`cacaxR$z-n=DUzAeR9 zk-n6hm8lRIlMH;dTx@~1DKr{RQo%9Orjop{Be&7bs>f5m;f!y*`*c0 zV#`M4O@7!jeb-W@j#E0b%kziWbc&gR4htGeGSi~RpLqO~ zQ%?5DW4zW(pQ)1FrKP#Q;5CJ3;s%#??B~VR#7Jg{C4!VgPcB{ZssH%oHrs5=UDh5- znDbr#A?O6FDo13G89ZmxIaB`n*S+DU8*jYwhgXjBK=8zB&gj$QoKZgQ^ml*tE0@jP zYHKFI+)y&Mf;UW#o%*g*cqW_oNNU8<_&u!grycNjy!_{Vr=PAcNrw(xG2Qpz0}p@w^8Y^hN$Wyyx1>`_K5mW}9uw?4O*IJ~EKH)DUvZuHlknWuffppmAW@s@1DDo-^lt z?|c6V$G?&-f^ni0@oxV9X_tNNt4F`^XtIGZ{Lls|W*_g@T)AQ;6~%7{xnjd@NF4I< zBV-&9YD~$Z(xc79o0Dv-C{IFhP7qW2c&id!!B`McY!+0MHCH{B#`I9Qx-U}8O-2(N zZ?rLoP7DJsJ0Oz|HLM)^Bn{~_&_&mvmqTGG9ckKGjRSL%SvjJTd{I;3nv~YjYL<4M zW@1MmVTvwH=4FiW>lkxR43V`_4~Nlju)|@Nax;s{!(4ZbwrIKTsun?vX7q!j%zRmr zt%OIdL4_76kT6j=2@*CnYZZ-T2|O%#$C9&VpnztnVIU;|^eAL0xgd25Pa)Yu8A;NV zhF*%ze#wO?VJwl+(g?{-`;Z%XZKRe~;>Zlu2r)~?y0>{NAg-)QPKz4d^#G8l!L4IS z$%llML$1fNLh0gaoASnyA)Gdi&qmAfgxN0rhM>$zs1j6Mjeu%0=1`F;1(~{;=pqSI zD`4~9(F8A=(V3c+v80#*I=l!BPy@b(Z0fFsK+1+vMQJPLR!U3H!N28=h(*&(b^KmIX1zXc65IH5F?n6i8V%Nzg1GxKQo3 zhg(LlR@Mul#42X;@G1mDbx9>cdsUBVeH~Pm+JbefGE|duy~c2SMZQ#O53^cYhjx-= z<+c{o8)~Xqr><9EBG;?{&H_2`kZAEBRqd*pb$hRcXf%D3x}eJJg!ldNeGH57s+lS1 zeSMp6v3bjiiBp^EJvux6sCStS58bqT@s5p`rVnhf<(AAWVd#`MY&pJm@!}_YZ&ArD z@3>S~1Q|XmIbf*5Yv(3tG;>Y8g`IcZbDIt)NcoF}Dk_}*)IH}auESn4IXXO@2Px$X@iMhndOc;b zbF}oeE~Zx`nYp4~H_s$~fLHhIwDXQ*o`%X_-sw3s#JlR>`@Z*Zrw0Rp6WycJN6tF) zz1RKWPdq=0LC!;SsIYQu)saUW{^pb4q8JEI;LOIcEBr_cO?QU;k{cRO@i1}P-$C(| zm<6vWop;J9ZL{&SJ;7oON zs)daGv0qs!3#YZ=I%pnd9WiYnRkzyokacXArG}ofBAd z8g3Fwx=I+lmf4nmYP4txIMHgKvYAY))S7D4vPc5+3J#x&W6{*E$OzMF6B0#z8wL_$ z)exnlnMf5i#zI0Dz2ZrFgG$SXb!Y^(ms!P8$%&5eGD_`EUC;cqojaM$yt&qg&P@xkr3@BpQjtjy=AiuoSrfqCrXNX$aY3 znoP^|B<`)bJCVrt=NQ-W_ z^aWS2i$bnA%PFL!9r$5aSuK#oXree5@-@Ec3p-=84w@WOF|uX@OtPpK1*UalD|tN8 ze(ERyWIX{nd-fdr$1F&LA&+BlvfCLX+d(l+J8V#7C^j7CO^#}JW))EAsWNAy+0}-l zG*aYe%2P^=bs{5a8BW+Wqs5E_mjhn?LCyc>_gKIW;V>4-3OTdFQd0Ch#`2-&{N+6O z=tcRWaL!m;mRLH&t@*A~1Bq#uUf1GW#U#rp3fV-fIhjn+L}BVHNTzI)V`It*ND*P! zg21U48<1^#rn__eZXdO{Erk25KK$YHxDiFCrZuzI{Uki&`-2}|`O%Mkgn2TwFMjch zmtB6@tm!j(N)^-0GMQd)vtYqFXP-TD7Ka3S{#8eea+=e^zH>*u;Gf5gXLGWz$eC&S z=u&;DkNxW70Opuuk6AE(zUH40<)wm=r~*Jw=z6_9lTgkY)9_b_bA#uAb23^tMIru% zli$R7ZBJ%FQ>2)=B93;$60nEoD!Iv(+RzlpX9I7QGK+fa`d%! z*38*+^cas`;3Z6MjT-F#>CdjY^RByjj)j}}_0wj`yY@)Zz>ZR=6~&ckNf(y)T}7xlGe%egQq{V(D`qP;f$&KR$#oMv@MSclKSuE^WU`t1Vo}Sx(KJ96zz2`JPKhVd$ zKBK%tb9{WxtXUU*{NtBhcKJQ`+}S59d_c7fI|fN z=*48e?hZms=jl}oXNgCe$vdW|_C*fJA0~>E8*Rkf`u`CM4W3faNQ-dfH225IG;g*< zev+pW`eiIm7;Dz3*T!Os1w~I2@3+r>2kgH;r|ro;qeX#o^Vs;r&wh5TEYc;B1z8tC zp4l7GfdEnjW)(UYjtq}jJoRZ$lM-f;Ry?60Nll6^pU5;7Efeg4M<09irkifmuii?~ zTiM#lJ@(jr-+lJcl@uLI`1YOTf#Pr%sw+jy1$Bb3dwRDJ4LvqJIkDSryYIK({;@@9 zlC4V2tsMpZia`A)I@e-7o8{y zBqFaAdsiE`B6S<6)&*u9El9b{QvxbAOj+Loz4%NaC|tYg=m{Kdug=gl28wBq@@J8&HzHTg)I4`)1?}OfTpu#Tze`n zY4mWW1zk02XvU;%1}(OriUQf6TBSlNRSQK&7g|>Zp~g#Vw8v34l33Qyv;fmN4vLAv zi7twCgiJP}$g*~=5K`HdEK%~_263x0nv|1PV<3Yz>qxBH-rF?{JC&UbGA-@A8eclR z5y%FS3|B@UEs*PGjylYyWob9sS_eJEnN#BNe3hJ+RMhp+85+o zQ7c6y3pVEOP>5c)h1&+YS_a0flrt!;%6wJr(l$YQr6io(&VG9b6vUQzlIumw5MC=W zMX0qm3Tn+xW<2h){T5h^ugay8OdA0awGAxsEo}L)6{=ux{{TNS>hD?li=I)Ib()a5 zf@cKLjo4{MhK#P$c44X#p-U(G9oj@@8hP@7atmvEaUt8FT?iHAH9^#k*V;G>dKmy! zs$15yjY9B1%q}ORhM~@=0qsRCAa3j3# z_KBN+^ltR@;f^5AvFjF~HT)n{sBoJQw{3{X^P6iXc|==>8F;7rVUiI#k%Kvs6eGv+ zc~)tLLXlINQgZNc+;Jxyd+dw3s|O-?igS)1TimvO_PJ-g>crz#EZ2)=kj6dEUN2v< z@`P8P@cg5W;^mhqOwWsW>QqM>%pl`Dy-w(=$@g*iq@}EzCe1`P-zUw{2| zJbTJ{ZYQbLQSt^fP5mWrXePiK`jCz|@<@I#ONRrXS=9NolhT01(k#J<&I;wb@492r z!iCJKeQzCud5{;WB)VIO0eS-ngtskK-G4Jtnbs@U-{5`{a{f_xtOw#{w@< zqW8V!lVOe@bUfhG0Wz9=vw!MEhpC<_MXbo44y1rO^w4K;paL>OThEkFtl_;(KfC7I z?|kRm4BZi)hv0|LlrWDBo1~B2PP8y6BdYO_5iM0o>u7EGGOHm*AFPBz-fZrVr`p*EDsGVkuE)n+#k8&->8E7?EyjL;Nl~|;K^VLp z(RNB(HjoQ`$yHBbWu_>2kRc`&!p$itP@M>MI|~<0YiVJyBwC=fTUbvpsbd4Ufv=6K zo;Xe4#vo~1t!)(_jKo{yDwM`0lUXEqIxjlX!fb;Y0s2pTgQ!uVceNTr890fkXVnur zjpS>EIz1A|f#<&PAPz&F%hXpA4m~+503(N2fn-gaO%iRWuHFgH}V2S}>stF-Wl$IMTJu2KXk{8ANW`1&QB$Wqz=B z?aEav^<~UCR^|brnC3t<8XGBuyb7zJ;ek zxhw!WcWx|Ox^(fvCr9**!8n%)xNU>CFdg*t1K;)bck-eholC|yPZQT@h;V8?-=nWf z0T>@p&ShJvS6aL|sSep~++|L@|Ni^)t{&|O)d?{XM24e1_`GK>KKA%ybLY;bhd4X{B{YTM;v=F2 zM-Dk`U{uYRKK+0L4v-`UNF;DMuTs+PSHsN3y@K0mqTU~`)*T!icD!r3MTf5Cm#sLc zqTK>KF-H*cn<4ByF?jL#d zF^)Rs&YK^ed7$VB=zym_ZU4X9|98K;j)ws_5aI+p({kR!KQugg<@f*h>K|XV+n&20 z_Kd?0IN)i!?6T_?TWmf$eLA{asE{AzR8BhC9D;dU1mB*^artb|Rr4l5Aly#&+~+<2 z;)^eS{Lv@$2*g@4zyrrzWtws8t+&1NZExLW*Ik}@_~B1|>VdoOzWe4|Zox69vM-#R z06b9ti6Wc$U=9TSB0;v;TI2x{M7LPz=uZ8WMTO>gSOQkjwf*R5(0gxNNs_W{Y46N( z_y~(=fRuv-W`>xlt!gR3%$y<$pSs9iQBkZa7S+DlER*u063`Z|qD`liY$XWMg3l*< zRdi>TRU?~gUgt(-ypW_y(gfmV#{#(xpi3k|)efw837BFxgtR?GD5;%|<#mPCg9Mj8 zZpKA+wpgi+x^CIRw$o`?R+e1N=2%O~%}i@#GpC`HS_p@&te9w+bmWpXLLMijStq3+ zc2_cP=Fkp<9R$fWu6iShCZQ>QvGGCb(K3ledJHH)o$It6ci4&iBpidpiO7~WsgW!r zEPKgP5DkzhWra&zM~*EUd1=&6s@~ysQO%VG-O`yHYi~!{50RP;s}gJzE6+owBBaew zmZAi9(}tSNNz0loW-Gi5lGY(exA8}h)oxp&iy&$cJ6N`@9U=6#d8vpSbgkU!(l<>S zS*I&`Vo_R%mJh0MbF+f6&IT<|5=IN(+mp02vXSg`#*D9w+zOCrwRD0c1Jv59tjjvo zYmmZBIt4WRj4ahe)uRikc*6^bkS}nF>A^3v{(@lbj@NqHG+z3?(Iy*PFNTGnIkWue z!;fOX-J81)e%{)xTAS&pgBMQAYFBT1eKk5Nr&hR9z?jQVPK<9ock4?o{lXTT zZ_1f{9CiXC!enH3&v@Uojb_jK z(7ET1%$&{{<#e3E91%nPdcWv^r{>Jmb+5Wk(x)zx)0FsLr!T2t8hrfctayL_Zco{r zID7|-f^xSpWtFc3K8jc~0H&hj4W1}5^~ZqZjH@`cdl$0Rr_EomVD7wmxBvAH6BRvX z7eBd#ACZ_pZ=UWnkzgcWYEd*$G|faFt}Ybd(iU58vHcF)D}6pbiP@FrI~rONrRcao zL0M0IqG=ij)%V|bzgjxNQ_$SEGdwi1@upjGGd#cRMH793ku`-N6a32q*O)PvhDWmQ z0+Q#t2~IWsH`{DW-j}s%CGRHE@ZptRD^{#{?6F7Z&0m1Bz;(-<9+2RtCf@#zcb)j^ zSFK#RnjaA0S!AA+0F7Vf;x`H~ddrQs+=biML*L?7tbJ=n|MyHL8^4kSt zdiQN#|6gyr<1c@?^}jxMDK8t?VF&$S^HZL(J9g)9v(2WPY^TKO8=3~HjG#7cYA*gmte!18+l60NLIuq`h{)gIRdL=o4GBHvMw zP?cW%v{LpZO9y(OVSuI_cS-PVD;WieNN*C>Dl6u7i{-!zc4oEV^7lsOrZtJRXA!lC zVIg_eU_=cv8agRRu}qg)aZ~~!(1N0+VU%iFjVz0leCSlNbZE(Lz*sibvzg$8TUjpA z46gpop^;pi4#!+AjI7~x2&fv~YJ|uKDJPBMg#9cntEWX{wNPtUM3hMEVJ%|2q1Lz{ zG2`cSw^-IQk`tL_M24V+bv^uCGqo9w60DWf?kwR^rqYU;RQOT)Ci7z*{j7PvtlF~H zuGj^%nLTVhcP?lB`Cewu!BkBb{`>pyzUOXpdwE zV?-&E22PlTApjW`VMB`JE`pJUq-=ob$u?LpkSgIbTOveWp)+GTZ*m`s`Azh=ok4>& zfr#c=qizv5f|rqSxskk%5ycEl6L38;0Y@FeBJxWg>c$m?o>X9RXNwywG)L!LIAZ)Z z?OuECdBz!MzU^&qojGHMy2K~=H34#~ZzPYbo^Kj45h3?6q;N=JdW!mJ788djK_|{u*ga9K0cvqoYY)tewVFC9inp@qhlypD(}s8w`5=4#M~Zha!3~z^SH=;-W>1 z7d~RjU6e6vlr*>;=VcH3j`z4qRH&%L(UW}9JjeTy6;Lpk9?gz~{9z7D9W zJE7-ZHBOM z-l&B{A}efFTkKl97orggY*7VEA?GlYo%O9+#&9Iz1}cpLzfPbc7{flx${p3|Vs}P1 z22s^;HM+@CPKB9bSyk$PL$OZrMvB>H0$FzxmoyIToE196?He?WDjmW);RtS+s~IeY zdbBiV6fA2IIz~h0Y7eQ@O2)!rkd_FhWG93&bqUeXPF{yXirRpvOG3~sG{@U{0AS)& z2E&j*!f;PH*}{tsS=f4NI9S`JMrH7>0m7o=4yxMrnu)N7eU6pbv z89c8A>)2QZY|~b)D(>89%SI)yB+w43S)Gf*VlzvcTE`IW6jrj>`BtU@B0--2M$5FwCsDJ}1N>yP{(Lo&*aBQG}bP)tpEFdCE z7yQkLj?SPsN_PaLNN5A0r%==t^y&xEidfuaH`J#aH-4KBQRsym)j7o517K|l6ICUz)-~&ogolgL-sx~z+=9YaR`Yd za>k6!WHPy=(!xL<`^Y2z+qZ%@#OpE4NK#^DElGvsS65&Q#WqX}S8Rh8(GmhRG)p(h z$ozTp=RP}!oFwmF#lVj-t-rwrN*l#(10*D2t(5uF(1&5rT8W51N&`s@=ug}sv&PG; zro?_tBxQuvhi|f*-`u726Y#;v&qyhoO*z}uQ?FE&?noc!pxZ%L^PX z>j6svOLJX(j+@Mo0@=YZG39vjL9^1{=-j?Ubfm!=XQ+c7RtYYK(+?gjUbI*tP8SHc zim|d|)%d3#f9e@fbTlYU*rQYTxyT0pQK3r?)0K>NUFtajzmoi)BPQ;!@93PM$20gn zV0+4;;DQAUgeQ39F_0i~fu@7k@*j5CN7kA;<(uC)>*=SSCb`!n99qd!-9CtdS(;Qh z&eGuuPpS+)|NIN{=FPwLmRtEX=nXbl@AYqZ!+YNQzBj-5&HRit=lD5ifH69ZWPoIG zR;qY%aLBKVjXmX*(|KFb<(FN-b1P*0B>y@nBDl5>Hia4jqcwIcm~-Ym_w=)KZ@Kl) z6UL9@hl;n}daK>vzQ?=Yz0b^SR|)|;ZO=xAC*lO=Ejo$-na z(C!k6S}01gY2`RcZFFV>9CJ~Hm5FUUh1>#MxLW{ih$+dWEDU=KW2U#P;V7_TS0h$l zvJgotm8u$qvJ9EhjEbkHWrpLxkx^TuV%XFb@ezgtFRmgXGhLuWy;LBFbmB<;HlwiS zzSXajI!3BSU%gD#Yn4@7)m9+wEdrb=#15KmD(P&zY6BS7%mTS1w8~S5l5NgAMl`cQ zK~*@t$^y|)S1P4DpfY*2>##cn@{IHjG<&SBVW!H$w8yU|S36syn4LiWhG>X&4Lp~; zmUc>5l6p)k3?`<9L(^!Nb^L4TflED&D5KRhYM`Xng3vw0!=zgTP!q#bOkrYZ2b9~c zHtnQgn0g5DK`wJwahFT#fuxTcLGj#3)rOpG*9u8}$K_I0g9IT(1I_|3YEfm743O6u zC@jm=v{q*}xvh(iBQ%4q>FSbNe>qd(g23!29_N>gxGWHOh}A{b>ti!VvNiZUJusSeHbcxAEDOqZblU(f zMcntddlxTTf(>!sJOX4n+?lZ6y3@!~JO<)6E}e$g(s>iv0ST6}nN-YRf_U|5iyUN? zY)r8Zngf?RB*v$3+9R|zl7z6bfj1ik6VAT*Va@4?k|m4t;bCz&URzR|6oga7|HB zp&cD=nZof2W1+CBi?49R(W-s7YC6D5eyU&J#S}4-bMrnWJ7wqeTswy&tGH#~uTtXC z08YM7;OK(2tNiG;KI2(Zct<9eT~y_~dH|rS>5hY+yTeE)__brAO9rnX>3B#p zkyixKhZ!=7R9=S|Jn}ol6F&T=+k5tT-<4Nh`H_!&Wb)+6wphtDK_yrBK;fJ`P=e0y zpz-U)gL)S(G`@$)T67R;y)1PE;J`|bhoo^Gqn0jR@z=ZWJ?ooiA8_FQ zr<{7yy!rFGCv@tyLa^!>N|V5|Aco9i!hm>)@+g{E`c+9jZJE0bcX&}V=Vcv=Kn8ccZ4!I*$yPzDOelfN9Wsy8Af2&>7ntOawV zMs_XwLX2%q7HeB=FA*gzQBRTTSa^G36x{S4Vz_ic3$2*}+gk|?hJh1*@U^-Wv@im; z7@1aQES+>{C6{86a9a`8RSNu;Of+%E$7{1yR%dR}Rabn{_7sR}r7hpEL9G=AB`>Wj zDa?9Dl*vX`s4~I(uIx~wrl|?6wUt$muuhBwA??1BHq)C$lFYE)uFYwCh)M1lK}8|8 z@JOS3>Sbn)EhAd$52L7QH4AEpNS(8ks2x5vOWo`zd&ZD5NKIylGG(P zC2u;>%SJcQNlP}=%z>2UGiJdNTxn#%%mv=)ePWV;#|Ac)07#7>v5dBtT4XJGUHDvq znmSa=Jh2h*l{|Ci7VAu#M%Dtw51O(X`_fA<-*MNSK3OM5Mc8|>%8;Txo0GL>4dJQS z(pg41MliK@-B6x-(5TW-D?eH3Q~%4ewTwDV5fArKpB{k1QYmg3w`eEzr#g^7?Y zOeT~jmD=cvUdR>ARa>6S&n_h%rIiurMw%dE88?NX+WA#85b#O>4%j_B0jnkdvC^DV zO+}uCd-5wv%1ZsEkf$+N`UI~9GoCUi{*+-e?&HRG{O$hx&OGB3Vja1gm?M)~HMY0A z`+^J3zvY%ccJ)l;o@`zx6L-AmzJHQGo&iuA%z1Bpw=mGUjYrmcqI-kVS0$Q@Yss$I zxMguzXlr%nlkfado=3V5PHK`(`Vj^>j|!HlR1wgm;YV}zYJ3D`1fb}~C=7Jclfq@9 z0US+FIGOC}107jSKjEZeA2^lAjV`3yGTJvRg&EdE6i_&J40*lY9VGSofCC=sJc3H! zIWsQ9vfJT2?kYj1C|Wv*<#16A<;;X#Rqnh((qI3iAGiq%Jv>svv)V9o#LN+b9TWxy zZkaHa_c(Dt!mln2Y`pQtC!KuCFMjcFpFjE--bu=X&Qe2sl!a^XVF#BL_fSDm7dgz} z5DyIKhQaJ>Q3PP7R0t>hik$0O8ChT+bIdppom-ka!u-FLqSu+rBtGs{S|C5Gj6qqK}=F< z1&cPtXeFBEamOPVU}@G!QQ=mjE{$|hOYmuG-i=&jZ9V+S7LU&GzL7CnsL2duL%hAy z{o#Zhw4%ZzuhsCPP)Dh}5>pGbq7ekl8ZKG!=CF^VwQ&oX5sZznyJLg^RG{s*0MUh1 zBLjSx+K`eoYMD1J%oT3XC1fKSM3ICTDApt}Xb@U|dA($B4gu;fI09i$!mZEvBoj_?7eMzLW6%dN`A%M=cN*&mBVOS#Y!N#Hx8uo7o7$>2wNmu> zg5O~M^>^5Q``^#I!KV_H{S#d1*I$4A{vX(%q<~v}(W9Hn9VywM0J&H}9rt!Ca$g82 zb5c5##WfM~4ECAFr=EG{_CMdwc~KefJEyURdl+_o<8GuVnOzW8pK(&lqeopI{>iGG zImujo5aFMw*@$o;mL}H(v5j7Y1e8~aE4w5y!58tw#4N*8FT;o=XFhE!9!Z#iRny=} zR;xrwQmj&6CaB53vhUcOBCJB=zhPF0#N&YIU}WW(rG3l)^-KS{aN!bu@t0hZTg(+O zzimc0EAg~w|Il&Aef8?Au9>mvrrg2JEYQKC9?j*74EZ_NiF68=Hv+{2yE|s_8Jm9gb4MTfsZT#K`>}iOzWd($ z?tSQyhvv?iw|Ma)4iywK?(C8yT-*T|NcpcfLapk@B;I6o)sJPFMMIqiHi^Tz(LFVm*bk>=7b`fmqvi*uu^Cm zJ@rZ;MNWQ+gssl{#SmX+qUPdg=7!edYTBNJ!6RxjCmWWk7P2tfmbA1OB%uVdDuJf6 zxq(?aE4b0Cg*yPn7$lSk+OSzXF(-zKnyax6C|RQ#IIZ0ywWJDIDRp;IK{L9tRI#GfKn0HNM+aofMwIJTL{8Q zn350lnD>Dvd$miZgp#b2ma5Sdmm4gDO#-RYRWBqXqgm@)A<2peEw?Ip2}LtH6VfnJ zXb%c@$WXp3pr!RQfoyG`kr79D~cmCIZ{^sSE7jVA=sz?d+ z9-49EHr{B%op#wpnJ4fzGA(`DCxXT@e?(9nJCvy^b1XYYg-n{UOe+V#7KiC!aM*8) zwGdxZgf3F!Tb_s}vrr63YnA$WYfxLskyAN^QTRcm$JL}==4dpnl9ikYjudk>lj7)c zcU~ZCsvzt%9Y*mYaYdp-Vn?#Y@fi1!fAz~>oOS;!e({%7jLCpICOPsOWgZB}PNMit(7SoT%nr5l)4xS1GuR;q{m6_!Ux|V^>7P zYs=t4Qj^=K^l33;;87rD1kz#_8#VbsU}?l8wk`5rOyYFi{Ohc{E-*d}w4zmGc>Lp} zlfM1tH|@HjZv{6~;gZloLe`_%3(+KEFij%rVEYij7ZcQkp8mo#*~9^up6F0i)hRdS zhL|-bVESOj8V6Sm@(ck_bi8_-SHE`KZ9g>TFy1RVfBth%J@NP>k39U)Bab}x*rQKB z`Si;RUQ7rX?*ITm07*naROWzz8*=>2^4P)t{w{tkZm@6pvK8DP^}FkTfBt6Q{n8h| zqL&)^NQ#3r1;V!5cjlMk!ek_q?;l*rxbWJackQ(|FA3$*-h~SmJpJ@jk3RaygAYFV z@c%x_@xV(jy|i@6GBy-u9dcv2ZomEZxFDBcN*Yy5MI+)8 zV;9ZDiZxM0Rm?SVItUD^whq}WXmPsJD{<1w62Trz$28-HR9IFsNgi=yCe+-WMb#FY z+vc<}niv-IDukd3ME^~(T+)(Mc(R)#KrIkx#l#S+%!5Az=RQFc0ac(ZlCYX08kMBRT?L7*AV|;%(N@z;>Mf0^7vank1oLZqrWm#X2!;`t6%$oV zU=xioXlJiDtsykkT&o?s!kBTAytb?8)0m>Z8QNx2imB3fR_mfAcY~_}tBi1;0hq3)hn)Ar!N*cu z1*KFPQo_bZ5(mzkt{i;pTXx@I`UWq&@RIWeoxWx7AGkqDUs659Wq7=Q`{Hb8jAoz!t`d5V--8JNqwYHRm;Ja;* z>0Di8p)Hyp2h(urV1|d1cUX6)onN;LMlt;sy!`UhPd#z>U+=l)wp;GJ>n_3=9 z6L-i-f%xo_1!zR^bSU*4{ZN*9Mldi77Zz3IODIa3HApr@^z4G_Er>}(_#^lH}%~;1q?uc7| z?FAN!>1Jy*L5t3I5wiws+K}+s4-(6pMlw z%R)?Y_KL0;^7dYsAO%&{#TI8{C65n?#>aSx*{ZxSm4!Rd#V1PyYG%mm7AX-e*-(>) z#7@g_8ax;F4^*>L5^gJIlp$^O(o71l(=3TE5J@?^ZVQx{LXj1YjBVog~^-X)IGR7jClQj{pXE0-lQ9H z$S~q8ne!CBz+tPs*LAvNL>Q|^CYV>iGCfJT4uzsK=#6eO$-Z(k2cj5&XUiho)uH$xgDYEUhm>~G2pF9SjbHIwFB9lM(NP`(9 zk+6!2Qoyly-FcU9O(B3feGFB3Z3rr`BG3|`B%q33$fRaAT`9$uRnXi5^;FQrFnATW!5H zkN5@}P2Pbsc1*{ux81@+y_}s_w$4d)Zs8w{d-*xG)vGQkRH*62`C6)%CEJ`{*Quuh zyy00bRdry2V~-YZnzl_Q$rSk-F_TePX5(}?!DhtAP5{BU>1#wk;KjQ|dD$yl;KrM5 zyvMuVdHmN-`1k96b@C}E^KK>f4*|%q+7Er`g9aLd zE_~;D1q!-4yKlSgw)^h8huwv}^e}{0`-~#BNx7Ltqahl)9U09ExS@e=Q87`d zM*vg9#E};uuuah?t@1rSEzIiDf2=%jqE+h@CADQ<#tBlArlC*J0>aBeh>?~r$$kXw zs+zd12^?~)Qf(xhOcRi;F+pIOHjM`fWhAgbwY6Ol)^d^NTP)TOs+jBfTs8nS*xw&H}Gdxoj3*lLmB3n_HKRTxX99ZNyh3~F>mEu#oxxNuO4Osb5uMHHMC z#bu1>E+{P=p>?#r;%R2cYYcNW3>>J`>W5V5c-}ST0F4d)f z(gZ(!h#kDFaMxXT-FfHNkr5d+@~(s|or(cXK?bY>oFfRfTKg6Y%;g425|1v!J84Hqyy0@)0#HY$xsE~+)H)F=chLWvS< zE%~f)76M0G!VCNwcL~PXH@Jmjg=1rANCK^?6)uR5mPI$l3OL@B7=M zkMW~j3YKs*cqx!nJlIGXMo3j%$ohgonf~OHD1Z3F?|I`q=a`*hs*}|DRa?Jmt+m!l z2e9JGc4^JOR-~?czbV6Z(J6J`gP!o=Nan*VGa;yW-Ap+2agKX%aPyfnr%#_AU~Cm! zY~cdXZMWS%fBp-D1O3Wqr7CnvUzi~~00b_aPq^w;T+ymt^Eyb14}Z)qBv7OIX5(0; zuxK4}30pwkQZ*wYBbOgCJbA7-VH^h_JYB4}CygI}=wXLlan;Ya+Hy;1j2aJbFlS{WHPJtCJq=XWk!_bP3mj2R6 zn8U;>LP02%A{|`Nfze-hq;AptseneW26yyl@Da5Y;^9bsjRk`dmHM<;^LIk5F?)#=+VYGgP#OVI zVy1xRFlf%Gtll!*PW)NULamDc$_%9iAv8Mzr6Ji-RmWkzSn8s!T_=Xcv$V!sq2}(OZcjt(xm3Ft9*U>3j$hFxDF>`2Lip&hlMY3fr z{bo#*#^?f0#WG^^K-)+QRSOG-v<+>nZ9x`?xnxsjPcSrdSK`uFNGVmd(4x}x6u(4D z(=+ut#-*MR8?v#v+k;m_V=NabU2EPW)QAG_rh!H z^m|E#FMSC(WuTHQ9vB+jcH3?D-g|FSJ(x=x#CP?*_uPB#x6k1$WQWhmGt|^WBfdls zYysdg|E(@eQdR2wQ4M|&D6Di31PVAc+R?$)|C3Mo`r?JW{gYFqVkZM5PvW7*kALh4 zUVx~B1vw!!$1s#9Qv~bD729WIA5z;0BAr;`lR<)%EyBsAn$Yy;cuVNrq z+9nPHA*f7DzH8~az{!5dlXMK=5*oD3!=#acQPDAaQkNTHgnR2An+2hK$tI7U(r}~$_^o7S}Kh7Iqv6=JE>?wg*YwF~)&pxMjau>I`fBmFWx7>Q`!Bs=! zJ0|$OmSY(y?%My_313^n?HRbknLM62)y+ThR!`FE)E*x!h{>QxMjSBQe(Nbu4ZBjr zl?xUw{PnM|Qv@7!-R$GER4&LPrO5uSuy^tJi0~X)M_-D$zr+v7KK{fLH{E!n4^AA$ zuwmS)U3Ytfu0SZVHWUdL?AE|K^JTuNq!!;$sJ0)9rIgqL7P1>@#WW^jFbGI z?8$yn-K~YjLgD(m3Xu<+L6EG`tVsix`a};dV>eZ5(Um#X^!~w&0mgE*CE!rNCwBgg z5d|jAa_bo#&8o6KPuILd2*@Itc|>U3!T5|&GZd**Nm#lDsnW{omg5q=bOYMT;~w)! zw|YY$%R(CN=x_)}ENRgyBdVUcTU2KOIz76Q2}Vzx5hl4g11uwI9~Q;AMsBQjEtq0c z_}X|(Pc(oCC9y1srMFO`QqA@fV(sZvkjY}nH(QXAqt8PVSi_d}gs(a?Kqke6$*M9n zlqcS*Gf~<>8nN7C884av3{~dMY~ExGexbtSu}tv{x*S?hzHHgxmtlmwIYO^w<6f-h)=DnKsS|P z08JM)=?&eiV{Yn;A+}X@SiBV@@n@oT;p{3APHxlI^;q3tF!ZTUep(MN0x59iU3`y| z^IdlNKG&pBH|F-Q`|Pf6(hoE&p9Kg};G%`R|uSgDv1WQ33I2nh_( zuetV`*^fW2S5AsSNts!MpsFUigtSx-e{Ql+KA?W?O~(dat$+DtKjW9MBnXx-BEhxx z)Tz7e_9pqyUzx9(;|Wt!nvYA&PdWgQhR`0pn1+5c+9uU(p`35_o|a=`@3r^4If9ix zWSI^>cdcA<$&X)r@g*j?j3k~qx>2H>VUw3a_9iN;>C-YMEVs|oClc2W&>~>st4^Hu z^9y2@91)inzL-ZvC+TflL`Ph9+0ILaR0>9P9~wv;^u7Vz`Lm+0k4JB}*=8F#%VAll z9y5lY3*=!TAsHh^OK~&02bns?c;+`@vV=lt%@T712CsFy)0kBQ{eAC!?|y4dT?=%* ziNNffBj6~Mr@duraw5Tm(&UL93?D71g-pjcq%cu~MkGx$2Q~VwrbSKx$)HB5xP#q< zY7wSpLTf8it94<}z-q#PYTLqMaq76MuqDydZphS{MqeGKdW(;_t7RPmER$p! zB=IO}Z6zoPHC7gt-mEK0rBwjrzBahlW=J%0!-R$rTIEa2K4fNstd86)Oe0oMF8%p` zM4m#-*?$nS-KvpEPUDrdr zP<&Ho1D3!+OktO8!Hg|-t+14e`gQ82%@6e#J%w477N`W;s)SZbZ;~Kf4VG#atAHd? zvB1Ih>32o6Qe)?plqO4ZE9vsecr7kB`V*h^4Wnl7w4X?ftP`6ksaOJN=d^I3u$QCL^{( z(H4+as^PLxHFy-AAtQZLH6YC7s=sqHst4N}pyaL0;TyA(hw!%Edh1Udc_it$Zca$B zahh$_Ip6vg4+Qfz75(s#^bj!38CjG50tf6=fVYuii4v!Nm?&WbhiiKGv6e}i0)1!;&V~b>y?UDt_)u@m;>6R9)l-Me@l^Tw0a8H@AG)jmL z=S6B{%foEif)AgqNEVG!f>mbd-#`&BwKV>rO*tSBicXmD%j+eB?Ga`F1s9i>~X#+yn{2sIyM{V=dj_j%dhzNU;bN1 zHxGvhfqS`?!)nt=lay%1nIOeniIzi#C9=k>8j7*})bzYL^Ugi@90E%u_>loDCbPBq zvfBfOtk2)uKf9=!p!mUG9!TKU@Q%*gZ@=}I|Mp8RZh(NJh#=4XKfCVw#x1wpii$Uc z5JH7QtA#PCXt)&c9AbpaMwV<3c8RzE<$(;3HC72RhHcl~-n7|fGawR4&j3Bp^3;=0 zo%ii?JGwf#pPuVMM9cRX$zP9AAOj>c*=q9?KCqP;@^HMS7BBzjXTrLAI{OC(_+em+ z1puR=*`-P6sf89?XW)3&SK$>X8+E7)5ye?5!+Bi=(Ltg7cL!#3S58;AwD2rGnR%%2 zQj#cgD7}y=8Z=!&;e3H6IH7f}t#}zxJc<=f4}S zO-CTs9}a*SLlFkh#DwqxE5$od5lqQ= zvy`o#G}2wLl9o^rkn~xp^_GrO38q(AN*P+b*;{Q1chbE^a7-<;OSq{SdUX9T26&2F zD`)i9U78pgzcPeLrR;B&bt@)fYlrlO&W4_LN7}DxcrD$5O>SAkyDw1tr7llPx7Z?4&hADuAnZ^bC<|?0(cypC{Mv?CRp^i-RDt z^ZvepLqB}jfd?K411GR~MitMv4uH+4op##&_usD{4e+_yAss_wgIw^zp<1@qo;n6` z4mzA9s3hw{0|Q)PxbC`NU3k%jI$H9{a^k>jghA)W<(rmBX{90nh0W*w>E;;V**SA~ zLFAI9OX3bEddX9{iqX~G`N2azs59|C(Qa|$2-Yo@st8^b)0y2k#bYNdi|EPjBzUDN}i` zkxUi{GeEJ_JCyjXV>Ue~{L-BH6(~i^#W8eKI&*;|o~PAA9D1yoBb+5mmu5j!KwH_> z)kP?bWOt1vWVK|UKj|_b(6p@tMc$DB7i()y(#Zt13MDGGGTWUkRXbT8{iVZ|k!Y

Ln$of3fS|5#ciWfp%yt9$Btt0K&zQ~hBe^_rQ;yF0)@UtJcDGvXloD8s`M1Wy7Gz)GBA5q#YLN&U3On| zu$gcqF7#wt#b4`euNMnTrIjOp_X*e^D%x0bQO%#~Y4uhCXPwgoETwW4SD)4IViTQpggPYztYPAZFLL|bsG zM&M)yb>Nx=!$U*38elafOs`nrmx#1zw6r6jt*IMowAQwlm^?9)bt1XAZOi$x#t{p^k@04u8BRp6X*b#Q>`*XKt@dHNyjj{ zvjbgIFhrA3gk+*Xi6psR`!ly(d^RZXEX;9Dev=`|k4|Za>iQk_e{wsnViD z%4KKFcQ4~qNv4rt#T^#}$*e#C=19e)qpoJas@Fvss8Sp!p$?_E=njswRMuUW=K)@gS;@bwc<+F^(7{SufC zvVU}N8v6O?p8wj{zPfPXq7LpnVY;CkJ@iRN38s=PO7Vyuw_`;&61?ZJzpuZitMlx$ zzWK9De@5C!!bq4nflcI{8HZ1D-Eg5oN@O{=sEzO7CiE|V`AZKx@DO<+;5L8Tg*68S%b>BV59QB#mk3HJc z)1%qI4Nkf~Af<-aNVtM$vRfbNj^Wht6$JG2TM&;t{IHIl@gGqQ)T-$lPG5WK+Ojl? zf~(`q2WI{4+H0?dfgeBS7zAaSfZi=389I`T2g8#_vavaUz_(gQN7t;s&04%*v4_kD zI6k!7bjA$bE-0w#KDMBay5K^k~#jQLia&S|r%ur7f&> zy^VjQW>GFAn^cx-?w5kAu~%sTqy0)t^Cww>6n|9-dPN)^t;mheqMocNaUm6POM`&{ zXk$yEXbVoAiF>uHf=s0{CaTdHTT$H`dWQAhBy+`r4yC1LidEHFs_IsW zbvUJVU}`s`Rw+%3TGJ@~ZI$2v#fC>Gd4P;=gz59XFvS#=P*Qwb@JzR4sx{_19D;2r zjhdKy?fqg-mT9GWtXo>9nIaw+0XsC627%{)F}2M|ceSE~D+Wx8Q{CuMm)M9UJW^K$ znkK~v0u_$A$~uNe;=6RGb<_T(xV=H5jm6_g`?LS_as6N6LP|t1w7_cFKYXh7y17 zLy>MTX{or!3rYJ_q@pP^3@j-B*==bHU>WXkg%3@hJn76czq!`bsif4HkG{c`gWa87cieFo7ZWt9ej<}s#GsHAm?JGk zR7D0v4UJU7wQx7tFlHRr88{L+_2iSzI_u08{R7;j#5-ujL-=K;K2&G`hdOR(Va;7V z{A9tb2OjvBPak>vZMX3&yYP~&asUG>hqCKVo%-3&e6G6}XR#`oBnW>LbV{~VJQ^~B zr3F<_7_7J1Xjd_J9K}*KV|2LTSzw#v9YT{Qt@ZgYd>*G|ybw4RWlHNv_t-Cg>Adfr zH#oSG`17J7f%ze9^LgTV6tP9O{BRJi-#8xR2xrlvMaLfd@>A7(q%CS^kV(R@<4()7GFr zHR0EOKw_e~6bqwSFJ(z-!`H$ZJuNJSx3#h+Je8(kmo7eWIPEc0D272nlQ;U00!Nfg ziU=06Sot({aG3!)aY zZWv@zuayn}REovX%u7$&1`%>gBU;Edu^+~gp{jMl$Wf~|Q+36JwU4dZhC|e%>K(-m zdO*@AGvsxWp$m$?j@wn5*bdwbsRbg_7Ty7k}IYMGmHtZ ztTR{Zx-f?8rBeS-wrA@jvL%gf&aWywaOmEHpDhD6ORNNTi+xl$YrWflJL-Sn6MNxTR* z*lZV25#USrO}p-T=9yoJ$)U?W94rgpG=rAzPq=VoRBPpPgiFb zcf4?K2nix9eg~J-gr`XPC&P#vNT{d|K_Ai83y+G4Vk8eDH*@frz2SMB|8;ugQLty& z&FN~gxQ=nWdLKp<^C6kT7|h_`reTgd^!zR7ZKWTPuqn(-%EKTSWR$Eh8)FoiQyzcC z1Os$Mg~)()>7_sYf4{i4djdA-dJEYU+BuN>%u%1+V~;(!)k=4Y>ke;6+QD?1sMPbd zuX*k9C!C-hO>;z}GB7kSski6KE3UlaYHn!hQr@rMY7nvjjKV@^bTeQIhlV?Gp_BoF zeZ`7C?qm7k559l+M-IE?)?2v^sT0TG(_t7^fajwUWAV+zbXv4<;rZV^{}V@ij2Bh) z_Vo5+KUaHn-_B5XPf!2g(2<||dXv2$B=n;03Z;al1?|lwBRsU4m@!GYkvOop6+grqH*=vInRFX=+Dl6 zVm4VgTMLr0tx&;E4y*3}+kOA>o8R_y_byzp@ci$bfAE1HIQ#4~AA9UkCK2-tHfgjL z?@Dk(5HgSy25bciN|xx5jeasiUB6fOun9;uD$kpw*x{PQj*;f#_6Ul8GBD@VoMz!V~+pf;jv z+q4F!sY%7~BH>o>ya}0Ii{WxmT4e&)Wa6Y!302kLTivpSIYKlVd&8QHVXUdC(R5Y9 ziL0XgAFBTLe1%Dsj0kj0@%)a6wHwynMy4XQS={tQjqYY54)Y~(H$v?k8C|~@lS)&| zl(GgV_p;8Y9w9YE)0!a=Tg6%sTnRySo86n2*i8?hEGvG6TRWk=$25ktc+#?vw!q9? zZ5AL8da%UYWP)e2Eu8e#YGqM^EtQ0y>9Nj2tn~#}c%qU_si<-%_k|U-Vi5IJz`EV)1v(){1f@g|==!UarSw`ZmclX{YgE8Zt4s{ob^dP*zW}x8JNG>I zob!%5_AAdlH=ndrw<>dowoZeO?c{f(#;khaz6ZW`-|-jx=!d(#X}34;_SWsT+irsm zr}MQ6o%ed%M#;K1OX z?|jEM&peyEiU)`IRWed(UPp;9q+)gWQ%LC0@lW#6&ZC$tilqa=saAg0=$?D-oj-3L zi5CWuBzqvT)3KJ@pwN)b5fSy^u6GjB;V7xsr$v>7cTRZSKfDf?HE*rYW2`_+c`9jA zbLw}+@)h^qb03MVP6A^GhFYxfD)hENcGx0U0y?{_@zcKy7L%+c=Htfb=aVhi*l)iFAAIn_A703hh4GrExTl;$qSL=|28Rbb?Xc7Gfj+W*J^sr>9PDuX zR6rgbMNk`sV})P1&pSik`nI>+_1C|2j-AlAypLl5ezNz@JO1*qkACE>Z+*)FA2?v= zU0%2L+UszLL%6U@(_v`k^5x4PfBcC*{_zjL{mpM@&z{ZUPiJ=*KV3{kM~q|m-QS+R z6@&Zf4U$JN0m-R}w=!BsPFAE?%F$9VN%=ORq2kSgE@}0+XNl5L1Y1?kAEVGk7F4&3X*72;k)kq>ye-QXI>b(&p+<_hFy2vXyc71PV6Pe zC3gH_C$o3);zhW5d7Zj!13a;<;yu*qZel%ob|w2XP$BX`9I(YiGdA2 zZ8?i^KmFNH_*q{zH>PgagpT>oJ^#b+UvTx6KmUg}ync_jzk}a=;0M3Dy1Q|mxg_VE zPDhQqv*72Y`&V6k)dLSaFsWza@)dm?bLyCC>^QW);~l&2!f$kNTap}KHI9vy$Fe6( zxbliim-A#oXD3m|PB#3TZn|ZQEw|YHt#8?Fw>NLI&1|vvQ4&Sp5L?MJuVV0RG5>ja>BxJs-KM6@%B_pYxSWOHB zQxcn2P03z0lvI;Wp;E$G!-dC*g;g7HX-Z054wftG%77x&;|41qu-l3t@_#@(hQXzd zOf1z9Fxd+EA|sEA#;*wsd9%oEi#9#TMuMbf(V(;`LG1DNMo z>T2A`Fzp?oH>ky$Osv+~n;@yBnq%hllvWTG5iH1VD$3o&>W zXsPUH<;s8l#1XgLb~}Kekqk*p*B{6s6%E;MQIqAolYv-LRgsHq7Fw&K;^Qd7scvsD zLLOS#GpXm-zy6QSXKq0Ra7$M}P&#fb@maKF(ZL5EG<)`JT`BP6l1P&8lM*S@l3tCV z=Ojur5ZF(}7Fsv@`6~wMNF=QBT3*0rFJ5x=7moSL7r(Nizb|$lIxryr8asZ@ zyyp%(^pGbXe-Z-*I1tj?mUzqR;MQAhb@>&SuRnc#<*GWiq1YaIYIhW=FjO~F@kquI zM||won{Va935Nt+$iT$q{VTTJ_BB^te)W{eYw;*Bw+!j(2{(h_X{2Oh$8~j1IQgWn zU-+XR@k_3LE|h3=3=H=D=z@zMd;C$(Npsqm_X`mk?%3f*BYuwz|E6!S!OWR6H{E2@ zDQmCI)e+uL^}=({KmOzsPd@eZ(xpr7n|3uk$BfZL0KfE5LI2S1yTA3kZ=W}Ltw}@j zRhPyH7&F`Zs4%eDf{DpE2Rd;OJ%8X`+kflqr*@Z@3}HZ|hB) zMq1BNB0j$G{PT0>%q0sa7iT1On7nFL-{62|)hcew>Ex&SX3W_1tZ$vAM;&|=XPe|J z-1pso-(iP-hK{`nIhV^EmPB<_qRLE|bF6m~9L>);oEXKXrS=FH7E+HgZ2mG0@4(q+q*FI>1_ z?%a8^pLk;S?8jby=_R%u)NzH1$;JUH6LH0g{?~51%_Wyyy7oG2a|}gr6=1Kl;SqSY zA?=z{agaUqY(D-f@^N!a@yz5kvTY@exTZ~A)QbP%A~oMD)&&9tE8o~gU3=gHs_tB( zUb+&wp%z~6-qS2^4A*2WEE#jn*4|ERC;VUt1|yB;ZK2?{TbJ+~)n(6{Y|$QXbp_;L z2%TmPy-l@1C7?!I(-Sz%gv8^$!0J^e&b&f4_7|BjY^_iPe%2zEHYJsD86a_cl`(3B z%oj+S9fZDNP+hHd9T@G&2-FRVh8%9neq_O`?`y3#f$>OL8l7hFt8jq1n06Bb>zSTA8{8 zi#=&lB3{QPfMRL&HKEisry{-;kL=IQ)-~wLkU~7IWK41_DlGW`5nfQWQLM0^s_4Qu zmlmo=2}n&b7dl*%ku`%QlK+_}`jbFN3)jwtaDoWNMyK2Ki6XETwixTIJw($o=?`|R za)%&V_-H`mqw{GFER-^MHM6=K(P1b8LN2pt;7UGbyb>jQVLrZaJY+R;0fr;$rP643 zt#GPU8hJEY)Ja=#C;oj;u6WuRXH4H{L#~Zro2(YJBOte2 z(?HuV4ag9;Y@KoX83!M7$a8b&abUr~VL)$Z_kH)>f7%(RoO{-}JO#l0OM^P|ErU5q zRIFVb=BN=sT0`7>MK<}FfBBaeUwHoFAOD!tkY9f8ThYgl5Rk{?6p8QLdC#p}d7F*V zF&2Nm`-*HSqR-}%n@U;FB@|Mg$j zkLBGs{-q1@a9y%M9G}LGU%I0I(f@t?;fEhJf})4B6dl4T9uv6tMh6ZYZ|h)_SvP*! zvK71RyyIDCe{1Vkzj|PxPs8n?$YvSCFL^KN?(ThwU-Isp04#ve!QsY!W(!kt$&zLF z&wA*-d++D@4g04IS;wflW}P>*i5O6~$ujJX+~Cq4$paHZn7uYPsg9k%B{LUGUq zJOU^M4?i@EUk2d%5~I(zmaheA@@V^Kvl%!3msFj zgd*9eO42zTz&wR4ww53%dc_vk#IoAb@Yigs5o;9)#<1GMZWPCPHEzJv3M0P-qJ_3? z*A3OOaD1dP)VB2HMw@5=rnnC0QMW$ckh9tbB5XnI_0^(tQ zfZ+Eju_BRGg&o?}LWxFEr|*J744os`D5PXgqmP=v3?OPkt{EiPRzxCr*^v;+qEN#~ z3u#C-l-Vp3n}xxXhA^&SEJn2o`%APzXllh!Dy1IXK$i~`K`>3KnIeAacL{lsN-g?W zLSY%EiDQ^5tFBBpw;AgNqKtFEH+00Zq_J~=H6?O*qnf%c>beYcC2XUrk5|zZof1%+ z(h#EEzO<}2k;DtZqOsQK2McMdzD_hZlNC8505U1l+J{7i5Eg`2UXr&41SewGS$pkM zPdV+v3od-^cH1fe0o`+m)I?va3X=4Zn2@QE(fLmH3B14(3Pnch z%%5~>DG7Km8!wc&`EZFuh!dJ|4xG%#=iZSp8f)p#pV%RT$>D^%bWf2q#SRozQRD{l z5_A_#K04#j0c~{3Do0P|>LWN;5R?ys(!6v6U4}EkYXy6(7=AI2;jZ@~p4*m2%?NTg zqR1)d?|$dp-~aA+lX|(8qR&Z~BF}-sF`qx?jl1ruA6b(!$HwXcH^6=L1sEMFBw;f* z-|Uo=PZ~F2yyL~)LHvsF;G~}3Yp?yqRX@Lqo9~G*^>tFohmoS?{Bl1n$APtmLo>Jt3dL+P4ZFGheba zA!gq^c8RQekd!_5Pn)*RdFOrm=wps1za@-p9%zy$noCRz9k!7kLzlZfJYwC|(M6j( zoOIB^RRT;UDjYHqUY^26=J0Q!6u@>Wf-D2jjc@w-)z=<$@Ihee z$`8k{+CGUn$LTSE-1@b0JQuC>;E9eiSaPW5^(OwOFkqmpZES{CVQt|$Wmb8m6vAPxj?koJGR#u}!;1rA( zlc!Bmly_pe>2B}NT??zIj$VMS(z;Rk^5A5@TCL5FCh@i=REv3)T~Q;sqhTv}O@FZ^ zHpR5Pm{Yq@`Nju9+h2yhAi2U!@?@&OR1LvA8$zTA7T!_`_RxkY) zCeue?ZMu>LNvalZ91>N^6&FoQNeZib6U%ou+NzSy# z9`Cs7%AcQf^2u9nIdgDefcJ8s3Ps9a(Ck!C$*mG2zI2u5+61RovI^T)4h^i-1CTsE zsEhkbP&VIuvu~bx=Eav>^6G82;f4_K0)tBUlI=4lGMbT<(Sj8=2I#w9>g_RNazO

|MO0ZZl?Xnb8QVYeiPzToDWTlFD{X+1gB_Q1)Eei1sbLy7K*xc@kB<{w zG}h)6adgHvAX!g#$L~c7a`0Oag_chul)hq@$kR|jhmauq3d(1g z6;S||Quh5P6<*TnLz0uL8_}AmD`xq7)p#?Ook@CKrb5Va3uqN#p7B{T)cUr$fe?&he*h^B5|=gI1o_1;6zrhmJ*# z$_r$x{G2L9g4Nb91FZ%*gRIc+%D%VQAn^1EvH#c0wlWjE(gw*>M zVQI%!vZ6G<<+HoZGbE|A8$@ww!Fm}+skKd3R3ad=*4QBY?JKNr(K2j17*Nk0E0p=5 z_WHrDDl2gC;^Fbv*>RfssYwoQ5o~<=5}0kLr(uw`z5O=9NV`%aDmI)(;y*lin?0%u zWk7gs$2eg+e$liEAnpPB*ZGaCA?}yBCWzjnzt}o~n##%4B7I4rs>m-8F@DSTF`*di>+__v4S#U{pBn8^7}DJ^LL7R zx2qJV8Gun9OqJz`Nkj?%{cn$d2Gqx+NIKzSThylF1QU}5U-bFV2}}Lf+AsBRN_~YS zr=UAC*MuqYgB06kSEg!O*m~LB^ZpgN+ejwVXi@A^-;0`m|2}V++ zm)mAq#aeohEp>V`RifHOeLNfiUw%XDlv78&He4uH2tNz^l zk*z8eyEjyy4U$sWSxPL zV6R0>HXdcyAXK|;Z#_RaC5aj4fk_C@XhI^Tcd>Z3HL9Ctcw&|N04C31ZpaB&kvxsd zuptcN2(47A$7Q!Nc{~G0ANH_~f52gm5%hpM#pr>)Ib ztw@~G^W*^oS&w0{MlSQs?BvmY27fnPm@2zW4pv-+(&R_Kj=w|zHXw5`o_I#g(KB$2 zti}}A6lVev87Y*3S^BYSLx<4yz$g!*Yam~ z{KFr2=0AV?zg4c^x0b!b><|^{IyG?D@b`cI>z_V&9lZ*B*~KN9-!Y7_Ic zFadisnK#=QC(>@=O((4Td6srn$SYL{uj3$k6ISOF)VJK);H&>x$+3$ZQmiaw@Vl#n zzDa=Cmvv5V2$r3rB#E@+63GB*Tmno=eQj+t586;&=@;k{;}#AWo9ASZ+piV9e$E$! zQ$3iWIjT?6h2|6JcltU2-ib+UgvF)Ux-Q9D7J28 zBGw8q={}*A;*KGCQp;|*aq-nSY~%E*9P3kfG9;$;A(NPZ4=i@J!241lx#xM}XF?n? zjdQ71Dq~9MW%+@4p<}TXzhyqYl3q60+8-qKQ%J>QD)E!&_mRIxeBv(s&LE8o<_EFDWY~=FrYJTWtgJNb5-y&BWly zPH1fF_2-jwN_3v`&RaxB_Q6U1^-w+(j^h9XsrYoKL?a`oirdX4rL7uXs(jbtA`8#9 z?ikJ;{UrAybv&68&(Sw9isswq<+~l;UHUU|LC5bdTO4FD@xiGwk^M>f&&gSKkZu&eK)7 zn*LFMiSw@YQZoKic1Mpw8PPWuNshk{K!u~M@J`%C-%F@s;fP6eX%0tBmsXIpUm`e4 z_00Bi2CA#boS7U!*uV6~^l)|0s%@GHPf2a?K43>t+$^t`jBFXl%z-<)QEruYRLgo* zzq)(n0}(wF=w^HPkRJa=sqKDllLpZtFoPbHBaB%07a|b5Ft?{P^?SX{h7=R>+Ew}OePN;uj$;D zS0gLOiB}WX+##AXkE~%h%#lJg<`xdDHGO;D9B#`&|5E4!<;J`OFFILP<3`Dc>`@7cA1QOX7$@Von zq|Ai#yaC!H;9Zsu z0al;peexH0`2ik&Wqj7Lm zk|_N_Xzz3ZUg#?vouG7*Ibpm4VN^kl$Pf)dUW&vybQzbQ@G`kOXho9%25K{OyGk54 zj-2z`6fMWIS78g?(IrnfuEZdGNJ+=n#GIs}XN;{lI_EMc!XX*LcEh{Oxb%jXd#15h zz8B;B0+(?s79RpVhNfi);J(M@D2w@_ke)>PQ%k z+Z6aM_2f3f)kW&`&3n?@<>Le}fhPJ0Mbc}}wV3Bhn(^~gi&G8>Zy~pFO!HhAPTzRJ zmG_xCx1;$eYbMT6LpeY%L4A{niPLQ zxHY2Qz^9D49vGZ7EpINJkRF7$cmMM2KvjhaU{ty_5m77WpYHHPO*ugR;N>KpZU~-b zCd8q?uU+afQ#iGkhFKmbPe~M6=6+vs{71?gX1kN5dJD-)b&yL4gbdcjs-NW-3k!MV zoY`sTbAdwlQDGieWe7tAje1`2^{Xo2S4-fZ;^BO`>{|Nj)=`n}vHeZI0`#w5qaL6H zzO=}CBg(m^*gfi(?v2xe6H20ezAioiOBJgtql5n29CGCsXuud(Q&jxa05$CXE`g@T zS!DU+E1ZJk$|BvZmDgi?B~60hJBdhP=saz9G5)plsw&z&hR2DJia%AUcbjEJD_A_0 zu@I=}$n7G5t7>AmzBn()(p^1^30t-{L0xx~;ow?3Jop;8LW>c1Ut{maOIy6NB?pSzNv={q6%1J=mWK&} zn{u%qH~1$Z0^Af{`pZhi9;Oj6Kd%t3i~@OjJvC3VDq7GK18u0gmA%4{(3hHCD6td! znqsy{#YX)GAOLrcE=Q86kJ7vbx+vp$RGlrFn~mrPXo*M?va@$%upyMKu^vyP2*6D^vd5OB7Q13TN9B$-pi~?WO zJaYU?+jE>cF~P`da;qf#z@x0PE1wYBF0>f;D;WfZC@G9M<+B(`pp`o^Pf9T)ZEmLc zS2~kMuis}8lc%Q}0S;vL>R8b#v;D+296k&DVx;iCnc8&OrOvy)qW>{N>}Atc0+2|b%9)$X=i<@WwbvNX3`z2N05XEo{(aYJ)h}sReo$Zso0U=@eNO5eo*xV88T%t ztSQapJ2!bv?8*8Pnr_gxo0r4Yd*`+)7#}t@oK^E_uEhTKZ~gO&uXWn_hlBg(tUKDz z9FtFHb@h=pWEx$}e!~AE428OBBx#v=UH&y@tv<5PyOMgxDyG$DczxlIz79}n&oun@ z?>e!MWxzT)BRlJ<<*0uq?R*?3J+KJr-~aWme~C%qDvJbO3EcGeuopZFrwU_^EzD4u zCfnlT>=$Rf@l$L4y*;6FFmtHcJYn)x%It&FU99(%_8P*yMpgu8b9lUOfyvna?7bAi z0ln>MlZGYy8BgD;j7J|F-Z?3YO6~BtMqH;@Y~b`E(Z~)}2{Yx%>fenwB&IXQn{_*9 zmZDR>`ZerO+@)zE9DD=XE*fkodM`WqVMw;Kj7Kf)QQlHna%v3TCkbe?uC0-w^07Yv zJK3ebV4T03t@og^20$)C^V`@*ecNwRBP2eVBeTekF^R81GG_xFV+?o=j!jJpoN-CO zS}~^P;WjwRmJ$z3Ya&apkNX$*vHg4`tx8BvB0P&vB*rh8`jtk=q0dE|Ge1f=4xfTt z^yA;O?&AOCMZNPk$R|X4@@Ozi$<)@W3wtkIMDNacmlf{;u7#ChZ}W4WxY^<4HY5^s z6-TsDHIWZ~FABStALI$}!T!BE74KwjnpHS0-x0U0NSnlgmwk!f<)luYhtPN6gk3pS z8W+v2qHunZ(Kz2Q(R*a*<0ll?y305NLRjhPIg}G_desc`6ZNVYa#=%@SYEaG;#pno z)Qs5h%{%U$Cq}>qq0FLxk~jE3YFYO}uc&ncJ9|-8#}vXD=~3T6uUYs}0JJSRa>kP^z*{BR7px(XYEu$)UU_cxiy2 zdd7Bkclc!;U*SRb!8Oi)=0{~up zir#J|zx&5%UjEtu0xIu{hNu?9L`*)3o5Y$*v-u1?u1T(xIk$BNg*K1h2gF+y5E^yY z@{<20l9`KVWhn-p7c!8s(;<_*QeJRD7#Q(x<*_s7Hjbjm6@~vFfL2y8kY`WrXCj-G z8EDHkMG{_^qqPYd!nK-wUavQOJB@n<>XN+Ki`>N1dwa2Zqqf)80C{#!`VKu2%`y%M z`V26FgPXRcn!(N`zQkB*jxj<=w=x0D!*7#+z@{vLxXQ3s3%}RgrjcHl%39N`UT`%10PMSQ> zovgUCd|9}j&r@n*1-M2MsH=IF3Jc^;v7#9Lg82}@@ShEx?2Y`RGrja@A?%m4pVroTZIzr0liQckV%r+xc|4Bw zqM8L+`Ep{DTjda#PgO*XB&o}pkd0+eHBd>vaoGAF$szm^v_UCBV}u)nmRr+u&2b6S z$gzbm!hvTnLpWg9ck{>}mK`tHpeWU&se z+Z@6i)x!96axQ+5N#*q!8Ovw;R3ONp5N2@^Yq7go2if{-1T{6!3#DaRzq>Wd0=|d_TMtXcv{XaXUL^g5inXF>fo)yw zb?9Ym<#~EX!)77znc^2yiRKqT^$F8kAIh*?eQx>KSm$Lq^IF&qL4LVvXnv|eUBEiAf1H!4kJ3|b2U+K&9NW>7Kx^0wES;8 z@eA9Snq0=?DJz*asOJ!PEY+D@8ULZixW%^kkGo7;Uo34;AhT!f-}@p$*?x zf#03J*RC|vRJr^l&R3~^AN79?vC922hnMok)$N(pg(hy>Ch%599-mle`4IL^^E^Dh zW2AmLY|V61De0m3*>*SIhxxGnLAnr7?xu3IGIbx5!3U+)#h}UG5L_vv%5B(j@)O;> zz*KO)we4@0v(8U+ha%K|1L3~Qkf+Li)gj7w{8*^>n&s)Kcbr7bvuUG2&0iH@MJDx5 z2jKpKp_#&CU>_~s{KN9y(~-UzrWkn#mG5^+x6^{^i_4)DjG*gP_|0Qj@=N4SHcM>XNda zQK_>}rULXu{>F^si?9JBr^7OQ0>dF2rtoQJiehgHW8c*DD6~}hSX?F{A=;g|ck7B1(>athm zt6k}Tm*CYg5%S&l)01mQi%R}_+_)9ebWdTG61~Ob(Jq;H8Pb%|ZJZV@eKpUG=0~bT zV5r)jY|`Z8kaQ4{5J!gPw#4RzJ?d`cFa)A5CWe=L*lc>iNOgnFGUN98t?Vc})FLQ# zAZKw!t1e&iqb0q?u4*GCXL;?J-2PxGH}cMV;G#?h9fO+72aO%$6>~!|2{OEKye2RU?{mC_95^9aBsY5Tbt^%p) zzEz?`-`fD0VZ2g|8J)EeG(L}6URvU^$8i}!zJ8CT#}P~($75nju$)Nbf|S)z)z$zE z9$Y>S=D#Ho^>%#~+Hh`?sou9YYGplS5qhb5(;xy#iA&Ih$n(O_-_E5NXoW7{nC@Gz z;W6(tGm6;dQUd=9oJq8Hf;b`D9#BRH`ewip-@H|T${+S}A|AFDQ}|`_51*}p1z$c7 zg?XYaQIo#F5rj+b=9)&AK2`w!$kI*%7@Q0S_agzHY>dkFyNrk1GxdRRKz!YEEJQuN z#7u(s*Uh1^u*+(TLuV7Es^f%^&xUs%Md!JMr$T@ zr*9qcfb!0$epVetQVB~TD`>BtGw904jv9)fCbgX}HEY$i&Ju&7d+phh7!M*4hbCc% zYb%K-e4W(JFts#`A;A@Z{cEe|-0&h>u!s)nUbQa{3)V5md{cnU$IRrP&y?;}GIC9J9wMyDd$8AJ}#YCBIXV&i&;l@ zc8$(rjiFqMtH=@GRo<;<-Kd_Ar6k%(d$qJgmj2SdTioWI`LK-;w%c+Cdo@Mbrk*cu zt<-#fAUmVpglZ*?^KYr*$ATdo9;qg0VJQRMVUHZxS^q$KxI7e1qKbSg_4<#_9|Qc3 z(EUo+-bR^q1r%alNu%Km)kiP^Xslf`j$@`=ubc9c{HS&?DWe~&gnWs;^$9^>XXXTW zIprZz3=)PzI$gx!iezLAt_f`++Ei9MpJDQNHy_{+q{&d&E!&lBwzlJ04??x@eA16C zN9Ec$alQf2UoL;+&U@H3w%H8yhU=2b)XOgCcRFb^UzE!fk7+9kRQeneJ!xV`nM?;G^(4h^Dlqgb`$O%9o2f0;lQGFr#LxCA-nrJC7CJ|-sH$v4JX$l#r;q< z{LsdB5XbUCQe{51j>{&z`gRJLd-Xyo)3IhjfSj0j{EIdu%0#~Yucn-(^W=eX2@>}c z^h#$i{XHp25?bVMmBM2a!EzZapHbw1k;fs8Tf4NUT*iyjpf=E94sGR7_Urj_;S;>_ z3|BLre55fR%C1Pde{g1{^H&0ku+Wn)#}u4=?H#&hu3E}3OvdYPrV;@M|0Pui1bw0* zsbWF6nAlDw=1|rkO>~n1&6TrA9iZsZ@k3Ft=I`a6EA&=HR;cGuALVrN=IA`E`;Y!{ zR8{Y176LW#9#o!Gak865r;@;VOiT%K__r5s4kb@{@e5LMI7g%6f!Ve!1 zQP}ZhCg5OzsxQVoqF!uKTXe|HIvzph=SBNS9On z_5JTRRd=Z#c_IJ^sx9uBmH98D;ZA~pK(LUiX1+v(GYyjME`YzMZaJ)bt8ysvstDc; zU`uw~Y}1O2m{>uMX%*;zW7tAY;6OtpWjs9AAvHVyPh5eUvz98ynmVGI*n`r!GCu;Q zn5%GeJFQA!r}!c(PGE=QjoTa53sWmTW4p>mzMtjK9%Eb%VMQq^3t3n8m~z=V__gYT z+`#;h4ibNW)aP)9!bdA=nPhxkfsNMC;9-(W!fu!!&866!c8gc|vdz4ZcUF@?E1)T= zz6&OoTQK`kg(+hQhC4(%vvGSMOFy2S%X$gGHVH~L_M%BRZpPw~)o)@ts?>2{tj{Qn zu;>#rO6NW+Q2qKqh&~7a^2#bHJJmf5O!iLBfG#A7I$L|P$xvzV)varVk>h+)jh=i8 zNi>{Ag)QWbmqsF^%;ljKVVM#-i^ySshWymvnutQcjO92kTUfR@bQpn_ZM#SY%9SS; zTd6-JhT(&o)a_`vf)n%$t7VTA-RK z+=sFVK-)ic1NRPXd@uo4GVX8)86ork1Wd3xhZ7D*vkTRoVa+nBbg;_FU;tW2Rqm=M z5~bz6Cgk@OZ~ThKeHg7WFo|=v=Y$}4We8NCsT9qjGXB#L9$PKi6}B)z4VebdjJjaI zujr$b)dYngbDQm@)29B3Rc{wjSXt94FRrxCcV7s}0O#U$t7lPMore!~yxlqu)`=E| z@$KS@cO!DIHnK*6lIm=$FjfYO zOH&coRAyFucR&OX_T$2M$N{Aj=1b66zm4Yp251|EkDIhj%%6C!JOsMd_HuW{a9O5U z3<@hF_v~{Mj@#w!K3p0BxzJHW5}BN5a`l4<_h#)V%9;A&X`LF5MB6l-9*0@5-~LfdasoLr?{wtn)b(+mei?btb-Y?^2+S)bflv_(f|wf&e9CWR=sZLtF;H!=C`wYlIEn+&hahw zCuJypLQNlPZ-iH_3EWB7DufSOE%m|e^f2tenDnWuDR1<{wq5@gd8L}rk97Ez$3C7l zPm1MWkLi?^^O#eW=O70^@A%@pUy1A2e-ssxD#`oOn9AN>!?_uCr@>bS=1(EH0 z*oWX}p%R~_nygoc);|HDnq=A$JHY{-M{(NC-R+KnkU-0mZ(J=KYEKGt3@XxNJWyO~ z_og7fGj^WJg`b9mkA>an5QY#!BsEKOBB3&CJD8idt4(Fa8l)FC^j3!?G0hzF*%6qM z(BU)-tuRGVB3B+sF&XP|pcvH>DDnwq03kyQF5z9X~lm-w=>hK|q9yzo!ht)&_UP6-x>7wjGV&znlf9GwKi-Lyu0OV+QDE6_> zgVAlMRC{?SJCM;@2Gf!>rJ_ zGCi2zN_}hg9XS4cB8D7^d$CjM~2rifi z<0}{G`>}8^Qu{_5rzgWE#P;Dbxon$fWWU64QcfX7li+6&sa~JF@U2&Pj7FQ)6eg6yIVu-4m1YL;^sc2glwWa^v#c5(TRyX8omcgb?=bK$F+I zLNrQTC+V*~26KEcoR z_NI0xE>Is8@W^lBZjl6m-U1*2Rg49MI7yrm+J)#BcbH_F=AbjDJ(gH1vMy4Pa zesmIa`DlQroI2)VStO=GyLq(U%W$E?5Q~Y3l7oQhcF7k2`H6%7@ge?8RNTp?4=@A? zX1)-^gK|r0k!SMjI!t6j;0!fw1bH1U`D1A_LgkH#vM(P3%|57gN9k z7~z%VRr3IUT0ZpQNejlbs~)A0EpxPHab@Bi9JcVZ*YO`0nF7vHf8yIJd`zOyt(@PK zsJ*fO2{F`5NQk9)pZf+p1Vr%un8;t$oDh%^OM{p&X9FPL@7@j;2=gZ!w&Q z9gTpC@OHNo*zm_bOt)alTlHxb#XDRvEkh;Iz3{I=c@$AFA*@ zpvZ~|KWBitmPri9y;vo52NTu1Io52fT46s08FI2ofX=JZA`w3Xn8Rs{h9~}b{!Xe4Q@&lCP6t6h znPb+v>&-zLB-S)5FqW?ELfDqRO7wM~rpMMio!jGTT6hR{H#7RURKrS@-iKWAhb{W$ z=LYQci`+GKTLkDcRK40RLY*nAJjI_&ABM`=SU z&-2&Qpo;q@JOS!f)BAsCm!6CdxYY?SS;N(=T@_&iI8xS(<@wu;BUk~SP{|n^e=GEZ3CY3*NU&;5uc2U19 zf@gG81UL3xs|gXo6QV!!4gkO1wF5R`F_b@V3d_C!K^iSTUyE#spYb0p{*C{NE~ejI z1MKYWrfSb7-9%lRHh5ZgBfhTWj-MvZ2~Yw-8oED5iXYc&vWXL`>HCC^VT1yak@+ay znT_GI0My^n(=V+efb?*?yvDliwW^j(aLQVFgSyD`6979OA3Mm;?LCDIK%FLQdFfLq zzeN*mm<>x*#E{h9uy!T3)OXLVfGfpUGKApK0F%AeeJ5{yNRMkK|L#LA%>bhEoDxv! z0v74>7=Vymaqu&5C`1~oG}!6ZVr{YV4^NMFrEktdKlkBnj2AG;wI3ScrVLpz_^*dE znTjAtgg%(mHohvtN`hHbgTJSaoTW%3;5&u$M3002M$Nkl zV+8_jrQH9`zshPT`=gA=Qt{!JuLsuE3?&N?D7rlM&3nc9&buNH!=+_$DF*f@ zgb+Ew_u?yOSCiA-t5@8uS?o4w{fv4X0@hlP2D;`vBI0P!HAsI<%^^*FFU+xWaBB7JHkMg%@JB$(CCwwl3H174-YK4I@3!TQ;o4W#Ceu%n@viIXNw<(l@HX; z!4-5OXylDk{V*#5JW^qn1PIhf?&bUyMgI;+7j06Yj|-o~iNQ=F7Cxq^|Hz1|!~H;! z^k1U9W|Ws_XLjXLL8K$$Hd)9ER4i>cGo71%I}x?tM&LLsk{7AP*d*o}D4Mgdv|J4? zNa702Y1N0+z4ah;MM>iVlBO)Ne;7GTbV(;)yIx1bnKb2g3}ZqigL=;(Q0H^}Ok>h; zNUS=%P82BfJHvJivf`XPD38y41jLw|H|wH5#!?px8R`l(5{JMaeajW_APlc%CLbIp z149X?r3Q3>($j@jNN1R7zY$dPvd$1$usrI_$YLT z`JaE+R|a9vIO$Yk+KD{`?4MpWDIxV!NCWcIB!O01bV^Q(2jMqh4 zsbvbfifPw^cgzHE7ME|+H?B*iG*f73O3xZve~5T|ptl8eyR80<%>-+8XnrlldxF^S zUcFNNBBD0aebP`>4voDP;(i7O_6z3W`HzLaG#J&K>slPOqzO90n5Fsb$2&Gzg{co8 zk;vt?oF%SRM-W=qp;-EutXPhH6QS<{>n1GUfR{_@*ECU=?jg4SlWk>X4^mf7{%|Kz z`0jcl9|8W*va^ZPy|V6&>I8UWbe4@s%DvKD)|U!OQRaI;M>)^k>O*)hu4>a(u>L{op??ebX%yRatBbvI?6C1V;63s71s6$^**yTs69lA?Cxhx-rTSme&Pa1@*P*l&eDj>uk??kz)sQL~Z!~cYJlw~XHKiA*v+HQKhSd!O{&F?Ai|)0_ z+U+RYe^73K_1h8rrIXs zhxglM=!W4f=94lR_L!&WmEI@Ehd$9Co}qmp|MTUC7_MM(w?v>XS!hV?J2@hHAKW9OqDd<&R{`Us;5g0NX=Vsn zaW}wVTRy#RHQ!>2SjXGH(J75dnl?{DKQSs#5J%K%l^nhq&D1L`^ubpHN#rs%=-aHi zw&(Qrn~!65L-oR-y-OZ4Sp9ff4p;fix`f*Oa(%U;p)h~f-m?{MQ7ADZrTo%qJ(vD2 zM77l41@$2bxFF;J#~a^6Vo3<1@#(rzUQ`!eovD*>y(11L!D{2fJI-H)u;NG>f5h`0 zZCG4PxLl4{^Bv>l|E|XMD?qQjm7@)ARvOp{pV1%c$2pJY)U zkYLpnfHL$KM!|W{sv_*VjH@vk2L*4R*d1ZVZRiiPlMyBvhJ^~DN-~RIdq5Ws<={Kb zW4a}PWZTa0NFgo!aF@ zPwR03qq7aO4H1X+2ty(FiQ?m>b;9JPFu7ZGoR>%U8{^?AXayO))*}yIN^r)Gb&ysU z-IpnjXE(k;{}-9KiMt&7nb*t??y_>@HHZ#Gj_}LMoAHM^Y%?m*C;rJnj61gkkk=?D z}#Bv69E(03QQ3r2+4+q%NU|Yc)Wjrz- z*yh>tK@JlnLHudK5A0X(I?^d(5>Beg#^+)zFm!fSnxK%c^dYiS9kBXo+eeRA zj?qjB3jBxsJ7GN;m!Da#!Y0yZfpS0i9O{nJFOs~2(xvf(>xqXeZ-;b&c2iICDqnq>e7FgHh}-+3;QTkN<>wM7GJ@h!9Z0zk+rEIwW^ zC{_1e_I?9`*7VqoId>~>V8fKg`H^tY?TLGiZN2CM@jE&k(isr*Ca+!??B%e@3m#o) zirPL7s3Cg3d?0zPyyf+UWQ_J)10W;Od67Bv`LC}QBzadqCZYqH-*)l%s|C&OPq2$jm%9)d-=-KDuJzkQ?ptE3^Pn=jh zT<&GXG?by7y!Fo46lL(i84sWhk$5h1Xb{PN<<};wAMTJcr4-;A5^tp9Q_^Nv-veWf zjZdizi)(pE?<>og0&5F%>5&k#=hpkCaDQc(5@q^+O?3G9p;#+4OH+rbILbz3TfJ>l zjyLf9T&;Y>n!m%~eI$lH9sZ?Uzoeeh4^7oDvSON_~#>$9C{OqZg};Zf!E(V9|J%) z!oxF(Hky1b44zmexMh`Sus$dFR(U=w@tqp|#Fb94b(593un!l)LrlmO^L4O}lhsqszD1P4Mp>}~DrF4dqs%}W%*td z4IYA0v~%WR)qN3-M?Z&T>x(rP(-3oqXq6_;D|o^D973+k(1a*142ZMz;?e+_a4ZUZ z>3ZS07Y3I{SLS8!O>O;+*ZT*Kh?GhL6O)ftMdq8+y8gjaMD_q_4q(3;Sn#5hd5!Rk z0YHfIH30JOu7pW8?J;p$am@~tq~u0Ce*65~UG3d-oI4IH%DyOHA!VlefXzSsqf`HI zrz8LZ&s;(8*chu-fciD?&O&j*8nnkxgGf4-MIt}2sH9U2@42km%B%|Q3$Gal~fIypkxEcd??Bbymxd zP!wKS3tyRp(O&7;vsf?Bmw4l(&q60mW4C+Py{HNbS3~ewj)%Mfo#12DZ}mk&vdz zj?ftJvX(z9TW}bsDBDBod;nh8L!ScKOF70DLaC3>5=#lhmxQspIb&IdL9`clD*fw!X1OI-xvUn3{TX^%sCEZc?IhOwFbc4Fjx7Pv#JaicLYPq%A4=Lfi&S@mH zmec9k5$B5yYvo=#r|?L*Q3H7+TrO|yQgzK7fkCv=2Lo+Pml_rPe3flGir{jNWL^Cc zKj`=b7JR#mQ9-XJ57>yG2Zu?lpTQ?8VYRB*UxOC;#87FC&s;0LcW6D#$wppkOtq*A zHIr^cEkPMl?TXVLg=kjbmaS{;2LCB}3M+4jRf==26D`F9)zSG!lV}I#(zOY?)P|&s z2jWQPSIg|EMHy;lY8@;>Rqd@elhp+b-0JpZ_7|?<*V5)9Z`42j`Okk|U!`MzcPW2} zI&wo;C%Iv_Y4qt`!bLeADNLNw=c^WF;&M^gg`pZZ+mF$S`2A}59rN=Lm&9{4Lx1J^ zAgw(cZEt_>ij{6fP~^U*4iv__#4<G+ztea zCemKlDZU;4;Hu)*wvMyH%%y(q<>*V$SJL%9fffbo-aNh1uapC!Nl(|SpRigf^D$Pe z=QMe}B9D7@wf81IcF=HO&5b68)@Y8?QPf%$OII|AZ?0RtRwE9%G(>NbtuNg+H|&4M zoYa(QQvPdF7}7=OW}IPLtld2tStuJ9`VWetddo2S2zmb}AXNdEX7VCi2-q7SYkvD*4xyy#GDrEAItB!@hO^KdF;y;vT%C&}bu!+UEm*Z8}B z;!>dx^SHVJ{dhje?k66D^?MU@m`_#vyXiWAX6b4PSeD~!-Ea+)u|Ew2a>Sp2Z|(9t z>G6dJT+zJ6OF0!A#_AFN69f9TCD3vWt9xbMPthH#hCkM*PmHZM>0@#0t{2s_p#qVz zE2xybDUxgNoAu*dzQr^x{o09-WW$mad6RSwi6eb|+2zas51Y0@go*xl zXWEwh1@Mo1X!^~S<(sj~XcHlc(GN1|T}|brC03&y&1=2?Ad$D%O8`)5M@;##Cqc}y za@Tr-$%$^2%S$Bm{u@I1jUe}2&TbiCsGGSmN?BzqVqlJwP(D}IVUGafaV2xp7xw}j zFIo$W{>LvaWg4`r8b=9nsUG>B0BC5KXWOxi9FaX!<@05lx`pP zp<9&<0{CsjlSAb{Bx2A9OMQClhjTE;+fwk1M&xWgFj@{Qv;f{RTKVaIlu`WKZ_7Jq zqb7Y4m@D$u!~|P2W_FwP4Sfvtw*(sy7mrfClC?#188t2Eh#&kG`Hx~KHr*ZQmEa`{ zDc&VAI2pJ75i3=(|Ga3i3V2+G=QuvjcIOD-X=*a*^HgyZ0XYi`JmJisRT9bAwEnK8 zO$m82>J;`K)?rIAQ|B|x!xv5$~@a0hG0_wz*uB#JdDROw<>@}(kSnY5}+Br87UU>9(R zIX{77eqO-=qZ53h5vq7P%+v3ub!BJ1e)k6L@P8r@m_7K%|I>d7I4OOCkKP-{d(f)F z!IDM9sV=qgT8JdPKOdm#0tj6+gS_~!{~SS6HHi03=BVFqGCn@S^ah~NHLSA;eDux- z9k;C;0n%9zWL*_!X)RJzp9yl;8hw(rv~!|gaRWk0)F(|9x8mJm4hk*oWE}<)1I1%T zca1C{c4RzbbE;m%$Ksd4C&Lkvp#LNC!c!@sO`bT;B*DzTL^(Cq{`QvCbCK3WTr=1! zH_9MlxY)+lQE(A;_j>Ho)ei^4Li5PI0kY>pupYtYm|7fqo;tDPs~G?W+*nrBAMx>MY*ik1>+{&mg7K^}xcL%o%&Vp1StAkOH+n=TWOx z9AGvsgv$d!z!PgQ82l{pe5fKF*myo6XU);{ybQSQEW&-*PY_u*%N|ZQLh6&kE>PA zs$VI6;@k3Yc)t`l52yF$7wx2JFYfnfPEf61&zpIIJmKV{H$D7&L*6ir>HHmZ_z@H> ze>R31_WGk-RKoYqNZ-5%^yUKkGurZH7yDr)sa}ux0lVY*oF>y}7i>eL$cJ zzLf2Jd59=)_N(2va8)84LdnaOGo=t|W3baFryFq57?A(2+<94T?qDm--4){Hv-z1h z+vL$1j?Kx*qs%5wcdQY2MImQ*QrY)43BXJYKn~$Ar>mWfrx#ofX$h+6`i*a|VxG#z z(fS#rc2FzW((HpXyeg$70;9Vk43h%ykJ zQ5f|)n0_@sl^W*|bvSwp4qWG-zcxy*doPaBDTV~@BzN*KPzA&RoHGSE=UPgy*9`D+ z9fpVDw@U~ru}o&nd4;0_>@6yX@^YY;G;X{xKMm>!f3l@bo@{XK5N$cS(&QP^u%0_= zRW#~2X_O|)JwAdwmU570D~Y1yoxAj+>L;2}15J!VfOA?l_mkhgm319zg+p6;ws%gZ zQ!(5(M2B?>Qs-qGd*x75ug`5`j=gk9YhxOhqieG4w)JP@U2BrI_{RS*{c)*d|Ku55 zC9}Zouaa9_))BkQQUJ`St$6jGj3THFJmhT9+*l&Ql7+j9we_jSJkHnyUO4i}#4@s3tB8sq6x-2Q99ZLr zT>2*Yp&&e7FL9WXz?Of-0+PhZfRVYcBxpdZ&MaVI?OH)?7fmJMW%3XTK@p5cER{q< z?1rN+8IQd+W5T)8ZHL%2N2qotEGCn6jnR{qwp)vSog5x#j7vQ84%Su=Wl<{Rs#Pv? z5y_5P6{30CZCAqngl(DVD!e$$|3(pzg9mz4Os!gtwmz?gmp{@TGE>AMj5_} zq*lJaPz^{zu&(VIw@Ce-iR~E=Ea54f)4dgVEfRq9FLpfUfEUWRRl7a<^gCm zVXkX|kIRvaon?es`joO7%?;104r1Qc&%!obIf&6F`!;->o$*Jj3=f_ceC(I{;c~Hm zvt7Wya&ZSexkggI_0d|ymNj3SVZAkW@X!r!HF;Zh0p5$VtSCGg;QAl^E~{U3SeSgx z_8)buiC7!FPI7`rUw(h(1U=+=vURt(LF$^0x%MRHhh6LfrV%D;r_=@s_SS|^B--tFvumhM6L{MDll!Fa3MSdhErh5Nrj3QRC$CcN4eBL{7zctd4r$79>j3u* zJhYSa4Y*c9cCbs|i`Z)n?Y&<+7EWUG3twbXMru!sXr?GVXtg$MKqKJl%Wqi>Gk`Bj2MQ zn2oH~TehY#1s(5eB)jv*T$DO>wGZJpD6eqwD+LSu{Aq%iNv8W#nxHD0pptUKBiOZU zVLM)z)Q%{}u?_@TR8aG!NEf0Z56~vR+hs4SCWi>!1b-hoq+;MrgOg9*Ns#XJ18(YR zm#c=?MR)dsJ7HRMY3saKJ}YK%7-(4GPbB z))ztX&OZUsK|CLVM*uC`w!~G$i`dn^Bxe62%cP`E78g-x>in^k-?L$BH!D3D&Dw`X zvrJh1rFKybpA5s^N>tZX+?VdpE#5P|*Pdb%tJ5pArqs%We2$ryr)NF_AiiJWkMDQn zJ8QPAa_m%JfNfw_q?7UsvZ}X3yr!z1;x>sR%aDgsnvH*r^VEP|pD>yrPor9{>FLip z5drc zNL1WA0UqBp;1Ls^`9wHZ-+$uSP6aBbG1j{BIV^un+JS!WnZIiA2)(IDj)tTqS)n-_ zGmpU(gi%-fWf4Z}>ZFxMtN8cpoAIwh=)|f&#Hq9X3jD*Gl^R77D5?lnIN)UHT=Liw zCXIG`mviWnHVP z*~+8#bF#mw$bQL$)%zX#W=gm^WH#~42{ITw<71+nfj)$@Gs1HD5#$GyhKVEtzPme4 zMnlL+4vizUOdc5O_fBTpjkE=1yCR<}o~#LKl>1PO1mcoKL8Wxc^$1E5M3>zN30{KE z1I9c8gr|i{v7ggKu!HSDp|PvhrPY5!HbJ@NXRn7Y2lXPvUP^mgsoR& z{?o#fDg5u~Xv9y-ac{;yo7+)jJXl4E>Lr*<=jKHBYj!5_-UQcyHc?YpK3 zv-%wmPFVDZBu}KG9OISK-@15&)N-z6LcPiv$S2{@=vlj-GN15A|3{)6FG&f>PS2&c z?DdQB<*7WPPG9o=;+$Bp1If zeL|JK5^&uW)s}3ptn=HpJBKY#fVfvwKS%OH;5{7_0zvbs6gyoh8gF`~G_maspd6>P z=xDfqrQ5hKgZzp6`HHpT1H)rn5WENvP2)63rOX*aT$3Gq&!z&PS_XUM2?n->G0Rj0v*?S#!cPz&^`y+J+-=*&1sq z_v$!=@x1@-I)bp({Z;$ijH{wPTn#Pc0Yt&VjtovNsh-1A-KE zudwB!@fF*O;z6!b3}9($gFKLpSrPo@zzP+Z7(mRW@8P3rP-GH-ly@*G=McX5Kg}DXMtY!t|EEp~u*oheN3| zJu^w79^3uGFZ&PTX8r*>W>?$=cn*u2+}T_=gslv{8E(Iz^H(b{aOEy97H$deuSqo& z2;keB9s-Pg?FDRYC&T*E} zT66o+XQ}gl=1W8_kh%`Fml}v(AQ@lnjr%;SjAzvM;Lc@k3n%3oeiwQOavT)JH&`XD zEO_5j7b*4Ou}V;FtKO+_7M*Y-O>3rTDvaObA3|)Qkgx*9=XJb2uZSD$OoUC)3hj-k zeX$J`1@Pz8Jaxn^+dy`GyAY@-lg

DcDfASG3VMhr$=X&9Ua6GGp)bQ4aM2MszmD z$(GSs6xj(qQgb{Mqvr1bgme%~xHrSelvM;=!;^00z@k^4aqd@R9Nc2Jw#wLy(A^hR z(LTOc%hv#fX5zo(?XT;#6lb1mDgfb2iU=~P6zesFoQ!SoT4smXZ zI*Y^hz^-VqRH&`rWwk!A&Wn6x0jO>*wpz4(il!H*9#bTk z`*F?;F?~iDlNs$M+oa=gJ3J=yIenWmm}>9UvRQPm38z$vSs5ozLWDeH>vOK=2JG&g zpw`G|4wXDZm)ej~g-{C{yWXVM^~%(76TqWYO}X@cq3CpmZ_;QD!IQu;MbVI&QFyg{9g0;9CpZ;g&NVR}T(LHqRUyZr{G9-|b2CQY;{)F6a_*D6)L$JI?tiQ3THP_q z&%^AI&~6>wHn7GU>Ss)q4XRp=kM{|tu=Uh?QAqprkWd-XSz@@)+qGO#o5s|$aCQtq zjy}SiOfLgLr^BXq0+Mn(t`m7EpcR-5H8|ZGt!#s9&WlS$@M*Tj?$w+a6A{MP0-&yI@)~8?Qez(-_}V!s zT#j;9ESCn4E`tUcMLivBL2gYMya?g}fvhACh@}@6mX!514&K`C0XLQ^*53j67i>4^ z^7rB=yFe$=Y4mji=2oWTbXtVlBln_JSRR*7lx~JNI%_5DjIw{Vt&bdMcTA@2D&&Sm zmCS%SH4Aq7#ZbHp$qstgY}zLvpx$AboucNxMzq&S^hk|lzPmf!JR}VsO)K0Aiv7b^ zHMK&&5+qZlzsc#cuUC9qYJMP#jZ$+ZyjBAofMkIQ2x5O55if(rBMl6NO3R(f^TNYJ`za!2L}`4c18`?!881dy}wCVH9w z%#@&bZb^BuELFrDdC6}(Q_!SP6%G`q?e*H8M^TR`7yeo#o)YDN&Y9c$TZlh8r5smXXH@Y4VYo00p@uP5lG@pU9Eqx;AI z{M*#}7u3Cru$tB@iSwH>g@;IG&Ra99$ z<>)laAw`NCr9DaU=4I{Z`@aFJD9x_fFwweu`Jp}woQDr)*70t=Nd(kvvFol(|FttVDI}&%u;NPHE4aMMxS|}<5)sGiVGDq9 zxC&8I?}K{kfF`2|;mL958J6}Jz`uH5u>&NPxvqt&y4?cn13U5SYlKIz6*=(XeHC(^ zU<+&eW;+gXzqASVvMFLcacF?9M3PrY6}jn|Z;JtKF7!;Jhq^BahJ&^{>S)GUf|VjU zJZ*Jd8u(~kW-p^wL~-zG(*_cEnVJIN;CFi(F!LrG

@{bkrht@F;Q$#p&E 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%0AoaqM7z0=h}6GmS? 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 + + + 4.0.0 + com.fantaibao + fantaibao-qlexpress4 + jar + 4.0.7 + QLExpress + QLExpress is a powerful, lightweight, dynamic language for the Java platform aimed at improving + developers’ productivity in different business scenes. + + https://github.com/alibaba/QLExpress + 2012 + + + + Apache 2 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + A business-friendly OSS license + + + + + scm:git:git@github.com:alibaba/QLExpress.git + scm:git:git@github.com:alibaba/QLExpress.git + git@github.com:alibaba/QLExpress.git + + + + https://github.com/alibaba/QLExpress/issues + GitHub Issues + + + + Alibaba Group + https://github.com/alibaba + + + + + tianqiao + tianqiao + tianqiao@alibaba-inc.com + + + baoxingjie + baoxingjie + baoxingjie@126.com + + + qianghui + qianghui + qhlhl2010@gmail.com + + + + + UTF-8 + + + + + org.antlr + antlr4-runtime + 4.9.3 + + + junit + junit + 4.13.2 + test + + + com.alibaba.fastjson2 + fastjson2 + 2.0.9 + test + + + + org.springframework + spring-test + 5.3.30 + test + + + org.springframework + spring-context + 5.3.30 + test + + + org.pf4j + pf4j + 3.13.0 + test + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + com.diffplug.spotless + spotless-maven-plugin + 2.0.3 + + + + + src/main/java/**/*.java + src/test/java/**/*.java + + + true + 4 + + + + + + spotless_eclipse_formatter.xml + + + + + + org.apache.maven.plugins + maven-source-plugin + 2.1 + + true + + + + compile + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.1 + + true + + + + attach-javadocs + + jar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + com.diffplug.spotless + spotless-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 1.8 + 1.8 + + + + + + + + + + + + + + + org.antlr + antlr4-maven-plugin + 4.9.3 + + true + true + ${project.build.directory}/generated-sources/antlr4/com/alibaba/qlexpress4/aparser + + + + + + antlr4 + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.0 + + + package + + shade + + + + + org.antlr.v4.runtime + com.alibaba.qlexpress4.a4runtime + + + + + + + + + + + + + + + + deploy + + + + org.apache.maven.plugins + maven-source-plugin + + + org.apache.maven.plugins + maven-javadoc-plugin + + + + + + + + + + + + + + + fantaibao-fantaibao-maven-repository + maven-repository + http://192.168.3.25:18081/nexus/repository/maven-releases/ + + + + + diff --git a/spotless_eclipse_formatter.xml b/spotless_eclipse_formatter.xml new file mode 100644 index 0000000..e1075ff --- /dev/null +++ b/spotless_eclipse_formatter.xml @@ -0,0 +1,295 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/antlr4/QLParser.g4 b/src/main/antlr4/QLParser.g4 new file mode 100644 index 0000000..9514560 --- /dev/null +++ b/src/main/antlr4/QLParser.g4 @@ -0,0 +1,436 @@ +parser grammar QLParser; + +options { + tokenVocab = QLexer; +} + +@header { + package com.alibaba.qlexpress4.aparser; + import com.alibaba.qlexpress4.aparser.ParserOperatorManager.OpType; + import static com.alibaba.qlexpress4.aparser.ParserOperatorManager.OpType.*; + import static com.alibaba.qlexpress4.QLPrecedences.*; + import static com.alibaba.qlexpress4.aparser.InterpolationMode.*; +} + +@members { + protected boolean isOpType(String lexeme, OpType opType) { + return false; + } + protected Integer precedence(String lexeme) { + return 0; + } + protected InterpolationMode getInterpolationMode() { + return SCRIPT; + } + protected boolean isStrictNewLines() { + return true; + } +} + +// grammar + +program + : (newlines? importDeclaration)* newlines? blockStatements? EOF + ; + +blockStatements + : blockStatement+ + ; + +newlines : NEWLINE+; + +nextStatement + : {_input.LA(1) == Token.EOF || _input.LA(1) == QLexer.RBRACE}? | ';' | {isStrictNewLines()}? NEWLINE; + +blockStatement + : localVariableDeclaration ';' # localVariableDeclarationStatement + | THROW expression nextStatement # throwStatement + | WHILE '(' newlines? expression newlines? ')' '{' newlines? blockStatements? newlines? '}' # whileStatement + | FOR '(' newlines? forInit (forCondition=expression)? ';' newlines? (forUpdate=expression)? newlines? ')' '{' newlines? blockStatements? newlines? '}' # traditionalForStatement + | FOR '(' newlines? declType? varId ':' expression newlines? ')' '{' newlines? blockStatements? newlines? '}' # forEachStatement + | FUNCTION varId '(' newlines? formalOrInferredParameterList? newlines? ')' LBRACE newlines? blockStatements? newlines? RBRACE # functionStatement + | MACRO varId LBRACE newlines? blockStatements? newlines? RBRACE # macroStatement + | (BREAK | CONTINUE) nextStatement # breakContinueStatement + | RETURN expression? nextStatement # returnStatement + | (';' | NEWLINE) # emptyStatement + | expression nextStatement # expressionStatement + ; + +localVariableDeclaration + : declType variableDeclaratorList + ; + +forInit + : localVariableDeclaration ';' + | expression ';' + | ';' + ; + +variableDeclaratorList + : variableDeclarator (newlines? ',' newlines? variableDeclarator)* + ; + +variableDeclarator + : variableDeclaratorId (EQ newlines? variableInitializer)? + ; + +variableDeclaratorId + : varId dims? + ; + +variableInitializer + : expression + | arrayInitializer + ; + +arrayInitializer + : LBRACE newlines? variableInitializerList? newlines? RBRACE + ; + +variableInitializerList + : variableInitializer (newlines? ',' newlines? variableInitializer)* ','? + ; + +// decl type + +declType + : primitiveType dims? + | clsType dims? + ; + +declTypeNoArr + : primitiveType + | clsType + ; + +primitiveType + : 'byte' + | 'short' + | 'int' + | 'long' + | 'float' + | 'double' + | 'boolean' + | 'char' + ; + +referenceType + : clsType dims? + | primitiveType dims + ; + +dims + : LBRACK RBRACK (LBRACK RBRACK)* + ; + +clsTypeNoTypeArguments + : varId ('.' varId)* + ; + +clsType + : varId ('.' varId)* typeArguments? + ; + +typeArguments + : LT newlines? typeArgumentList? newlines? (GT | RIGHSHIFT | URSHIFT)? + | NOEQ + ; + +typeArgumentList + : typeArgument (newlines? ',' newlines? typeArgument)* + ; + +typeArgument + : referenceType + | wildcard + ; + +wildcard + : '?' wildcardBounds? + ; + +wildcardBounds + : 'extends' referenceType + | 'super' referenceType + ; + +// expression + +expression + : leftHandSide assignOperator newlines? expression + | ternaryExpr + ; + +leftHandSide + : varId (newlines? pathPart)* + ; + +ternaryExpr + : condition=baseExpr[1] (QUESTION newlines? thenExpr=baseExpr[0] COLON newlines? elseExpr=expression)? + ; + +baseExpr [int p] + : primary ({_input.LA(1) != Token.EOF && (!isStrictNewLines() || _input.LA(1) != QLexer.NEWLINE) && + isOpType(_input.LT(1).getText(), MIDDLE) && precedence(_input.LT(1).getText()) >= $p}? leftAsso)* + ; + +leftAsso + : binaryop newlines? baseExpr[precedence(_localctx.binaryop().getStart().getText()) + 1]; + +binaryop + : opId | varId + ; + +// primary + +primary + : (prefixExpress)? primaryNoFixPathable (newlines? pathPart)* (suffixExpress)? + | primaryNoFixNonPathable + ; + +prefixExpress + : {_input.LT(1).getType() != Token.EOF && isOpType(_input.LT(1).getText(), PREFIX)}? opId + ; + +suffixExpress + : {_input.LT(1).getType() != Token.EOF && isOpType(_input.LT(1).getText(), SUFFIX)}? opId + ; + +primaryNoFixPathable + : literal # constExpr + | '(' newlines? declType newlines? ')' primary # castExpr + | '(' newlines? expression newlines? ')' # groupExpr + | NEW varId ('.' varId)* typeArguments? '(' newlines? argumentList? newlines? ')' # newObjExpr + | NEW declTypeNoArr dimExprs # newEmptyArrExpr + | NEW declTypeNoArr dims arrayInitializer # newInitArrExpr + | varId # varIdExpr + | primitiveType # typeExpr + | '[' newlines? listItems? newlines? ']' # listExpr + | LBRACE newlines? mapEntries newlines? RBRACE # mapExpr + | LBRACE newlines? blockStatements? newlines? RBRACE # blockExpr + | SELECTOR_START SelectorVariable_VANME # contextSelectExpr + ; + +primaryNoFixNonPathable + : qlIf # ifExpr + | TRY LBRACE newlines? blockStatements? newlines? RBRACE tryCatches? (newlines? tryFinally)? # tryCatchExpr + | lambdaParameters ARROW newlines? ( LBRACE newlines? blockStatements? newlines? RBRACE | expression) # lambdaExpr + ; + +qlIf : IF '(' newlines? condition=expression newlines? ')' newlines? THEN? newlines? thenBody (newlines? ELSE newlines? elseBody)?; + +thenBody + : LBRACE newlines? blockStatements? newlines? RBRACE + | expression + | blockStatement + ; + +elseBody + : LBRACE newlines? blockStatements? newlines? RBRACE + // if ... else ... if ... + | qlIf + | expression + | blockStatement + ; + +listItems + : expression (newlines? ',' newlines? expression)* ','? + ; + +dimExprs + : ('[' newlines? expression newlines? ']')+ + ; + +tryCatches + : tryCatch (newlines? tryCatch)* + ; + +tryCatch + : 'catch' '(' catchParams ')' LBRACE newlines? blockStatements? newlines? RBRACE + ; + +catchParams + : (declType ('|' declType)*)? varId + ; + +tryFinally + : FINALLY LBRACE newlines? blockStatements? newlines? RBRACE + ; + +mapEntries + : ':' + | mapEntry (',' newlines? mapEntry)* ','? + ; + +mapEntry + : mapKey newlines? ':' newlines? mapValue + ; + +mapValue + : {_input.LT(-2).getText().equals("'@class'")}? QuoteStringLiteral # clsValue + | expression # eValue + ; + +mapKey + : idMapKey # idKey + | doubleQuoteStringLiteral # stringKey + | QuoteStringLiteral # quoteStringKey + ; + +idMapKey + : varId + | FOR + | IF + | ELSE + | WHILE + | BREAK + | CONTINUE + | RETURN + | FUNCTION + | MACRO + | IMPORT + | STATIC + | NEW + | BYTE + | SHORT + | INT + | LONG + | FLOAT + | DOUBLE + | CHAR + | BOOL + | NULL + | TRUE + | FALSE + | EXTENDS + | SUPER + | TRY + | CATCH + | FINALLY + | THROW + | CLASS + | THIS + ; + +pathPart + : '.' varId '(' newlines? argumentList? newlines? ')' # methodInvoke + | OPTIONAL_CHAINING varId '(' newlines? argumentList? newlines? ')' # optionalMethodInvoke + | SPREAD_CHAINING varId '(' newlines? argumentList? newlines? ')' # spreadMethodInvoke + | '.' fieldId # fieldAccess + | OPTIONAL_CHAINING fieldId # optionalFieldAccess + | SPREAD_CHAINING fieldId # spreadFieldAccess + | DCOLON varId # methodAccess + | '(' newlines? argumentList? newlines? ')' # callExpr + | '[' newlines? indexValueExpr? newlines? ']' # indexExpr + | {isOpType(_input.LT(1).getText(), MIDDLE) && precedence(_input.LT(1).getText()) == GROUP}? opId newlines? varId # customPath + ; + +fieldId + : varId + | CLASS + | QuoteStringLiteral + ; + +indexValueExpr + : expression # singleIndex + | start=expression? newlines? ':' newlines? end=expression? # sliceIndex + ; + +argumentList + : expression (newlines? ',' newlines? expression)* + ; + +literal + : IntegerLiteral + | FloatingPointLiteral + | IntegerOrFloatingLiteral + | boolenLiteral + | QuoteStringLiteral + | doubleQuoteStringLiteral + | NULL + ; + +doubleQuoteStringLiteral + : {getInterpolationMode() == DISABLE}? DOUBLE_QUOTE StaticStringCharacters? DOUBLE_QUOTE + | DOUBLE_QUOTE (DyStrText | stringExpression)* DOUBLE_QUOTE + ; + +stringExpression + : {getInterpolationMode() == SCRIPT}? DyStrExprStart newlines? expression newlines? RBRACE + | {getInterpolationMode() == VARIABLE}? DyStrExprStart SelectorVariable_VANME + ; + +boolenLiteral + : TRUE + | FALSE + ; + +lambdaParameters + : varId + | '(' formalOrInferredParameterList? ')' + ; + +formalOrInferredParameterList + : formalOrInferredParameter (newlines? ',' newlines? formalOrInferredParameter)* + ; + +formalOrInferredParameter + : declType? varId + ; + +// import (not support import static now) + +importDeclaration + // import xxx + : IMPORT varId ('.' varId)* ';' # importCls + // import .* + | IMPORT varId ('.' varId)* (DOT MUL | DOTMUL) ';' # importPack + ; + +// id + +assignOperator + : EQ + | RIGHSHIFT_ASSGIN + | URSHIFT_ASSGIN + | LSHIFT_ASSGIN + | ADD_ASSIGN + | SUB_ASSIGN + | AND_ASSIGN + | OR_ASSIGN + | MUL_ASSIGN + | MOD_ASSIGN + | DIV_ASSIGN + | XOR_ASSIGN + ; + +opId + : GT + | LT + | GE + | LE + | BANG + | TILDE + | ADD + | SUB + | MUL + | DIV + | INC + | DEC + | DOTMUL + | NOEQ + | RIGHSHIFT + | URSHIFT + | LEFTSHIFT + | BIT_AND + | BIT_OR + | MOD + | CARET + | assignOperator + | OPID + ; + +varId + : ID + | FUNCTION + ; \ No newline at end of file diff --git a/src/main/antlr4/QLexer.g4 b/src/main/antlr4/QLexer.g4 new file mode 100644 index 0000000..8b611a3 --- /dev/null +++ b/src/main/antlr4/QLexer.g4 @@ -0,0 +1,1196 @@ +lexer grammar QLexer; + +@header { + package com.alibaba.qlexpress4.aparser; + import static com.alibaba.qlexpress4.aparser.InterpolationMode.*; +} + +@members { + protected String getSelectorStart() { + return "${"; + } + + protected InterpolationMode getInterpolationMode() { + return SCRIPT; + } + + protected void consumeSelectorVariable() { } + + protected void throwScannerException(String lexeme, String reason) { } +} + +// lexer + +// Keywords + +FOR: 'for'; +IF: 'if'; +ELSE: 'else'; +WHILE: 'while'; +BREAK: 'break'; +CONTINUE: 'continue'; +RETURN: 'return'; +FUNCTION: 'function'; +MACRO: 'macro'; +IMPORT: 'import'; +STATIC: 'static'; +NEW: 'new'; +BYTE: 'byte'; +SHORT: 'short'; +INT: 'int'; +LONG: 'long'; +FLOAT: 'float'; +DOUBLE: 'double'; +CHAR: 'char'; +BOOL: 'boolean'; +NULL: 'null'; +TRUE: 'true'; +FALSE: 'false'; +EXTENDS: 'extends'; +SUPER: 'super'; +TRY: 'try'; +CATCH: 'catch'; +FINALLY: 'finally'; +THROW: 'throw'; +THEN: 'then'; + +// unuseful now, but reserve them for future +CLASS: 'class'; +THIS: 'this'; + +// String Literals + +QuoteStringLiteral + : '\'' QuoteStringCharacters? '\'' + ; + +fragment QuoteStringCharacters + : QuoteStringCharacter+ + ; + +fragment QuoteStringCharacter + : ~['\\] + | '\\' '\''? + ; + +fragment ZeroToThree + : [0-3] + ; + +fragment OctalDigit + : [0-7] + ; + +fragment HexDigit + : [0-9a-fA-F] + ; + +// Number Literals + +IntegerLiteral + : HexIntegerLiteral + | OctalIntegerLiteral + | BinaryIntegerLiteral + ; + +FloatingPointLiteral + : '.' Digits ExponentPart? FloatTypeSuffix? + | DecimalNumeral ExponentPart FloatTypeSuffix? + | DecimalNumeral FloatTypeSuffix + ; + +IntegerOrFloatingLiteral + // 1l 1. 1.2 1.2e3 1.2e3f + : DecimalNumeral IntegerOrFloating? + ; + +fragment IntegerOrFloating + : IntegerTypeSuffix + | { + !( + ( (_input.LA(2) >= 'a' && _input.LA(2) <= 'z') || (_input.LA(2) >= 'A' && _input.LA(2) <= 'Z') ) + && + ( (_input.LA(3) >= 'a' && _input.LA(3) <= 'z') || (_input.LA(3) >= 'A' && _input.LA(3) <= 'Z') ) + ) + }? '.' Digits? ExponentPart? FloatTypeSuffix? + ; + +fragment BinaryIntegerLiteral + : BinaryNumeral IntegerTypeSuffix? + ; + +fragment BinaryNumeral + : '0' [bB] BinaryDigits + ; + +fragment BinaryDigits + : BinaryDigit (BinaryDigitsAndUnderscores? BinaryDigit)? + ; + +fragment BinaryDigit + : [01] + ; + +fragment BinaryDigitsAndUnderscores + : BinaryDigitOrUnderscore+ + ; + +fragment BinaryDigitOrUnderscore + : BinaryDigit + | '_' + ; + +fragment OctalIntegerLiteral + : OctalNumeral IntegerTypeSuffix? + ; + +fragment OctalNumeral + : '0' Underscores? OctalDigits + ; + +fragment OctalDigits + : OctalDigit (OctalDigitsAndUnderscores? OctalDigit)? + ; + +fragment OctalDigitsAndUnderscores + : OctalDigitOrUnderscore+ + ; + +fragment OctalDigitOrUnderscore + : OctalDigit + | '_' + ; + +fragment HexIntegerLiteral + : HexNumeral IntegerTypeSuffix? + ; + +fragment HexNumeral + : '0' [xX] HexDigits + ; + +fragment HexDigits + : HexDigit (HexDigitsAndUnderscores? HexDigit)? + ; + +fragment HexDigitsAndUnderscores + : HexDigitOrUnderscore+ + ; + +fragment HexDigitOrUnderscore + : HexDigit + | '_' + ; + +fragment DecimalIntegerLiteral + : DecimalNumeral IntegerTypeSuffix? + ; + +fragment IntegerTypeSuffix + : [lL] + ; + +fragment DecimalNumeral + : '0' + | NonZeroDigit (Digits? | Underscores Digits) + ; + +fragment Underscores + : '_'+ + ; + + +fragment NonZeroDigit + : [1-9] + ; + +fragment Digits + : Digit (DigitsAndUnderscores? Digit)? + ; + +fragment Digit + : '0' + | NonZeroDigit + ; + +fragment DigitsAndUnderscores + : DigitOrUnderscore+ + ; + +fragment DigitOrUnderscore + : Digit + | '_' + ; + +fragment HexSignificand + : HexNumeral '.'? + | '0' [xX] HexDigits? '.' HexDigits + ; + +fragment BinaryExponent + : BinaryExponentIndicator SignedInteger + ; + +fragment BinaryExponentIndicator + : [pP] + ; + +fragment FloatTypeSuffix + : [fFdD] + ; + +fragment ExponentPart + : ExponentIndicator SignedInteger + ; + +fragment SignedInteger + : Sign? Digits + ; + +fragment Sign + : [+-] + ; + +fragment ExponentIndicator + : [eE] + ; + +// operator + +LPAREN : '('; +RPAREN : ')'; +LBRACE : '{'; +RBRACE : '}'; +LBRACK : '['; +RBRACK : ']'; + + +// system operator + +DOT : '.'; +ARROW : '->'; +SEMI : ';'; +COMMA : ','; +QUESTION : '?'; +COLON : ':'; +DCOLON: '::'; +GT : '>'; +LT : '<'; +EQ : '='; +NOEQ: '<>'; +RIGHSHIFT_ASSGIN: '>>='; +RIGHSHIFT: '>>'; +OPTIONAL_CHAINING: '?.'; +SPREAD_CHAINING: '*.'; +URSHIFT_ASSGIN: '>>>='; +URSHIFT: '>>>'; +LSHIFT_ASSGIN: '<<='; +LEFTSHIFT: '<<'; +GE: '>='; +LE: '<='; +DOTMUL: '.*'; +CARET: '^'; +ADD_ASSIGN: '+='; +SUB_ASSIGN: '-='; +AND_ASSIGN: '&='; +OR_ASSIGN: '|='; +MUL_ASSIGN: '*='; +MOD_ASSIGN: '%='; +DIV_ASSIGN: '/='; +XOR_ASSIGN: '^='; + +// prefix suffix operator +BANG : '!'; +TILDE : '~'; + +ADD: '+'; +SUB: '-'; +MUL: '*'; +DIV: '/'; +BIT_AND: '&'; +BIT_OR: '|'; +MOD: '%'; + +INC: '++'; +DEC: '--'; + +// Whitespace and comments + +NEWLINE : '\r' '\n'? | '\n'; + +WS : [ \t\u000C]+ -> skip + ; + +COMMENT + : '/*' .*? '*/' -> skip + ; + +LINE_COMMENT + : '//' ~[\r\n]* -> skip + ; + +// custom operator + +OPID: OpIdItemStart OpIdItem+; + +fragment OpIdItemStart + : CARET + | TILDE + | BIT_AND + | BIT_OR + | MUL + | MOD + | EQ + | BANG + | DIV + | ADD + | SUB + | QUESTION + | DOT + ; + +fragment OpIdItem + : CARET + | TILDE + | BIT_AND + | BIT_OR + | MUL + | MOD + | EQ + | BANG + | LT + | GT + | DIV + | COLON + | QUESTION + | DOT + ; + +// Selector + +SELECTOR_START: ('${' | '$[' | '#{' | '#[') { + if (getSelectorStart().equals(getText())) { + pushMode(SelectorVariable); + } else { + throwScannerException(getText(), "invalid selector start, expect '" + getSelectorStart() + "'"); + } +}; + +// Java Idetifiers specification +// https://docs.oracle.com/javase/specs/jls/se8/html/jls-3.html#jls-3.8 + +ID + : IdStart IdPart* + ; + +// unicode chart +// https://www.ssec.wisc.edu/~tomw/java/unicode.html +fragment IdStart + // # + : [\u0023] + // A-Z + | [\u0041-\u005A] + // _ + | [\u005F] + // @ + | [\u0040] + // $ + | [\u0024] + // a-z + | [\u0061-\u007A] + // ¢-¥ + | [\u00A2-\u00A5] + | [\u00AA] + | [\u00B5] + | [\u00BA] + | [\u00C0-\u00D6] + | [\u00D8-\u00F6] + | [\u00F8-\u02C1] + | [\u02C6-\u02D1] + | [\u02E0-\u02E4] + | [\u02EC] + | [\u02EE] + | [\u0370-\u0374] + | [\u0376-\u0377] + | [\u037A-\u037D] + | [\u037F] + | [\u0386] + | [\u0388-\u038A] + | [\u038C] + | [\u038E-\u03A1] + | [\u03A3-\u03F5] + | [\u03F7-\u0481] + | [\u048A-\u052F] + | [\u0531-\u0556] + | [\u0559] + | [\u0561-\u0587] + | [\u058F] + | [\u05D0-\u05EA] + | [\u05F0-\u05F2] + | [\u060B] + | [\u0620-\u064A] + | [\u066E-\u066F] + | [\u0671-\u06D3] + | [\u06D5] + | [\u06E5-\u06E6] + | [\u06EE-\u06EF] + | [\u06FA-\u06FC] + | [\u06FF] + | [\u0710] + | [\u0712-\u072F] + | [\u074D-\u07A5] + | [\u07B1] + | [\u07CA-\u07EA] + | [\u07F4-\u07F5] + | [\u07FA] + | [\u0800-\u0815] + | [\u081A] + | [\u0824] + | [\u0828] + | [\u0840-\u0858] + | [\u0860-\u086A] + | [\u08A0-\u08B4] + | [\u08B6-\u08BD] + | [\u0904-\u0939] + [\u093D] + | [\u0950] + | [\u0958-\u0961] + | [\u0971-\u0980] + | [\u0985-\u098C] + | [\u098F-\u0990] + | [\u0993-\u09A8] + | [\u09AA-\u09B0] + | [\u09B2] + | [\u09B6-\u09B9] + | [\u09BD] + | [\u09CE] + | [\u09DC-\u09DD] + | [\u09DF-\u09E1] + | [\u09F0-\u09F3] + | [\u09FB-\u09FC] + | [\u0A05-\u0A0A] + | [\u0A0F-\u0A10] + | [\u0A13-\u0A28] + | [\u0A2A-\u0A30] + | [\u0A32-\u0A33] + | [\u0A35-\u0A36] + | [\u0A38-\u0A39] + | [\u0A59-\u0A5C] + | [\u0A5E] + | [\u0A72-\u0A74] + | [\u0A85-\u0A8D] + | [\u0A8F-\u0A91] + | [\u0A93-\u0AA8] + | [\u0AAA-\u0AB0] + | [\u0AB2-\u0AB3] + | [\u0AB5-\u0AB9] + | [\u0ABD] + | [\u0AD0] + | [\u0AE0-\u0AE1] + | [\u0AF1] + | [\u0AF9] + | [\u0B05-\u0B0C] + | [\u0B0F-\u0B10] + | [\u0B13-\u0B28] + | [\u0B2A-\u0B30] + | [\u0B32-\u0B33] + | [\u0B35-\u0B39] + | [\u0B3D] + | [\u0B5C-\u0B5D] + | [\u0B5F-\u0B61] + | [\u0B71] + | [\u0B83] + | [\u0B85-\u0B8A] + | [\u0B8E-\u0B90] + | [\u0B92-\u0B95] + | [\u0B99-\u0B9A] + | [\u0B9C] + | [\u0B9E-\u0B9F] + | [\u0BA3-\u0BA4] + | [\u0BA8-\u0BAA] + | [\u0BAE-\u0BB9] + | [\u0BD0] + | [\u0BF9] + | [\u0C05-\u0C0C] + | [\u0C0E-\u0C10] + | [\u0C12-\u0C28] + | [\u0C2A-\u0C39] + | [\u0C3D] + | [\u0C58-\u0C5A] + | [\u0C60-\u0C61] + | [\u0C80] + | [\u0C85-\u0C8C] + | [\u0C8E-\u0C90] + | [\u0C92-\u0CA8] + | [\u0CAA-\u0CB3] + | [\u0CB5-\u0CB9] + | [\u0CBD] + | [\u0CDE] + | [\u0CE0-\u0CE1] + | [\u0CF1-\u0CF2] + | [\u0D05-\u0D0C] + | [\u0D0E-\u0D10] + | [\u0D12-\u0D3A] + | [\u0D3D] + | [\u0D4E] + | [\u0D54-\u0D56] + | [\u0D5F-\u0D61] + | [\u0D7A-\u0D7F] + | [\u0D85-\u0D96] + | [\u0D9A-\u0DB1] + | [\u0DB3-\u0DBB] + | [\u0DBD] + | [\u0DC0-\u0DC6] + | [\u0E01-\u0E30] + | [\u0E32-\u0E33] + | [\u0E3F-\u0E46] + | [\u0E81-\u0E82] + | [\u0E84] + | [\u0E87-\u0E88] + | [\u0E8A] + | [\u0E8D] + | [\u0E94-\u0E97] + | [\u0E99-\u0E9F] + | [\u0EA1-\u0EA3] + | [\u0EA5] + | [\u0EA7] + | [\u0EAA-\u0EAB] + | [\u0EAD-\u0EB0] + | [\u0EB2-\u0EB3] + | [\u0EBD] + | [\u0EC0-\u0EC4] + | [\u0EC6] + | [\u0EDC-\u0EDF] + | [\u0F00] + | [\u0F40-\u0F47] + | [\u0F49-\u0F6C] + | [\u0F88-\u0F8C] + | [\u1000-\u102A] + | [\u103F] + | [\u1050-\u1055] + | [\u105A-\u105D] + | [\u1061] + | [\u1065-\u1066] + | [\u106E-\u1070] + | [\u1075-\u1081] + | [\u108E] + | [\u10A0-\u10C5] + | [\u10C7] + | [\u10CD] + | [\u10D0-\u10FA] + | [\u10FC-\u1248] + | [\u124A-\u124D] + | [\u1250-\u1256] + | [\u1258] + | [\u125A-\u125D] + | [\u1260-\u1288] + | [\u128A-\u128D] + | [\u1290-\u12B0] + | [\u12B2-\u12B5] + | [\u12B8-\u12BE] + | [\u12C0] + | [\u12C2-\u12C5] + | [\u12C8-\u12D6] + | [\u12D8-\u1310] + | [\u1312-\u1315] + | [\u1318-\u135A] + | [\u1380-\u138F] + | [\u13A0-\u13F5] + | [\u13F8-\u13FD] + | [\u1401-\u166C] + | [\u166F-\u167F] + | [\u1681-\u169A] + | [\u16A0-\u16EA] + | [\u16EE-\u16F8] + | [\u1700-\u170C] + | [\u170E-\u1711] + | [\u1720-\u1731] + | [\u1740-\u1751] + | [\u1760-\u176C] + | [\u176E-\u1770] + | [\u1780-\u17B3] + | [\u17D7] + | [\u17DB-\u17DC] + | [\u1820-\u1877] + | [\u1880-\u1884] + | [\u1887-\u18A8] + | [\u18AA] + | [\u18B0-\u18F5] + | [\u1900-\u191E] + | [\u1950-\u196D] + | [\u1970-\u1974] + | [\u1980-\u19AB] + | [\u19B0-\u19C9] + | [\u1A00-\u1A16] + | [\u1A20-\u1A54] + | [\u1AA7] + | [\u1B05-\u1B33] + | [\u1B45-\u1B4B] + | [\u1B83-\u1BA0] + | [\u1BAE-\u1BAF] + | [\u1BBA-\u1BE5] + | [\u1C00-\u1C23] + | [\u1C4D-\u1C4F] + | [\u1C5A-\u1C7D] + | [\u1C80-\u1C88] + | [\u1CE9-\u1CEC] + | [\u1CEE-\u1CF1] + | [\u1CF5-\u1CF6] + | [\u1D00-\u1DBF] + | [\u1E00-\u1F15] + | [\u1F18-\u1F1D] + | [\u1F20-\u1F45] + | [\u1F48-\u1F4D] + | [\u1F50-\u1F57] + | [\u1F59] + | [\u1F5B] + | [\u1F5D] + | [\u1F5F-\u1F7D] + | [\u1F80-\u1FB4] + | [\u1FB6-\u1FBC] + | [\u1FBE] + | [\u1FC2-\u1FC4] + | [\u1FC6-\u1FCC] + | [\u1FD0-\u1FD3] + | [\u1FD6-\u1FDB] + | [\u1FE0-\u1FEC] + | [\u1FF2-\u1FF4] + | [\u1FF6-\u1FFC] + | [\u203F-\u2040] + | [\u2054] + | [\u2071] + | [\u207F] + | [\u2090-\u209C] + | [\u20A0-\u20BF] + | [\u2102] + | [\u2107] + | [\u210A-\u2113] + | [\u2115] + | [\u2119-\u211D] + | [\u2124] + | [\u2126] + | [\u2128] + | [\u212A-\u212D] + | [\u212F-\u2139] + | [\u213C-\u213F] + | [\u2145-\u2149] + | [\u214E] + | [\u2160-\u2188] + | [\u2C00-\u2C2E] + | [\u2C30-\u2C5E] + | [\u2C60-\u2CE4] + | [\u2CEB-\u2CEE] + | [\u2CF2-\u2CF3] + | [\u2D00-\u2D25] + | [\u2D27] + | [\u2D2D] + | [\u2D30-\u2D67] + | [\u2D6F] + | [\u2D80-\u2D96] + | [\u2DA0-\u2DA6] + | [\u2DA8-\u2DAE] + | [\u2DB0-\u2DB6] + | [\u2DB8-\u2DBE] + | [\u2DC0-\u2DC6] + | [\u2DC8-\u2DCE] + | [\u2DD0-\u2DD6] + | [\u2DD8-\u2DDE] + | [\u2E2F] + | [\u3005-\u3007] + | [\u3021-\u3029] + | [\u3031-\u3035] + | [\u3038-\u303C] + | [\u3041-\u3096] + | [\u309D-\u309F] + | [\u30A1-\u30FA] + | [\u30FC-\u30FF] + | [\u3105-\u312E] + | [\u3131-\u318E] + | [\u31A0-\u31BA] + | [\u31F0-\u31FF] + | [\u3400-\u4DB5] + // chinese + | [\u4E00-\u9FEA] + | [\uA000-\uA48C] + | [\uA4D0-\uA4FD] + | [\uA500-\uA60C] + | [\uA610-\uA61F] + | [\uA62A-\uA62B] + | [\uA640-\uA66E] + | [\uA67F-\uA69D] + | [\uA6A0-\uA6EF] + | [\uA717-\uA71F] + | [\uA722-\uA788] + | [\uA78B-\uA7AE] + | [\uA7B0-\uA7B7] + | [\uA7F7-\uA801] + | [\uA803-\uA805] + | [\uA807-\uA80A] + | [\uA80C-\uA822] + | [\uA838] + | [\uA840-\uA873] + | [\uA882-\uA8B3] + | [\uA8F2-\uA8F7] + | [\uA8FB] + | [\uA8FD] + | [\uA90A-\uA925] + | [\uA930-\uA946] + | [\uA960-\uA97C] + | [\uA984-\uA9B2] + | [\uA9CF] + | [\uA9E0-\uA9E4] + | [\uA9E6-\uA9EF] + | [\uA9FA-\uA9FE] + | [\uAA00-\uAA28] + | [\uAA40-\uAA42] + | [\uAA44-\uAA4B] + | [\uAA60-\uAA76] + | [\uAA7A] + | [\uAA7E-\uAAAF] + | [\uAAB1] + | [\uAAB5-\uAAB6] + | [\uAAB9-\uAABD] + | [\uAAC0] + | [\uAAC2] + | [\uAADB-\uAADD] + | [\uAAE0-\uAAEA] + | [\uAAF2-\uAAF4] + | [\uAB01-\uAB06] + | [\uAB09-\uAB0E] + | [\uAB11-\uAB16] + | [\uAB20-\uAB26] + | [\uAB28-\uAB2E] + | [\uAB30-\uAB5A] + | [\uAB5C-\uAB65] + | [\uAB70-\uABE2] + | [\uAC00-\uD7A3] + | [\uD7B0-\uD7C6] + | [\uD7CB-\uD7FB] + | [\uF900-\uFA6D] + | [\uFA70-\uFAD9] + | [\uFB00-\uFB06] + | [\uFB13-\uFB17] + | [\uFB1D] + | [\uFB1F-\uFB28] + | [\uFB2A-\uFB36] + | [\uFB38-\uFB3C] + | [\uFB3E] + | [\uFB40-\uFB41] + | [\uFB43-\uFB44] + | [\uFB46-\uFBB1] + | [\uFBD3-\uFD3D] + | [\uFD50-\uFD8F] + | [\uFD92-\uFDC7] + | [\uFDF0-\uFDFC] + | [\uFE33-\uFE34] + | [\uFE4D-\uFE4F] + | [\uFE69] + | [\uFE70-\uFE74] + | [\uFE76-\uFEFC] + | [\uFF04] + | [\uFF21-\uFF3A] + | [\uFF3F] + | [\uFF41-\uFF5A] + | [\uFF66-\uFFBE] + | [\uFFC2-\uFFC7] + | [\uFFCA-\uFFCF] + | [\uFFD2-\uFFD7] + | [\uFFDA-\uFFDC] + | [\uFFE0-\uFFE1] + | [\uFFE5-\uFFE6] + ; + +fragment IdPart + : IdStart + // 、 + | [\u3001] + // 0-9 + | [\u0030-\u0039] + | [\u3010\u3011] // 【】 + | [\u2260] // ≠ + | [\u007F-\u009F] + | [\u00AD] + | [\u0300-\u036F] + | [\u0483-\u0487] + | [\u0591-\u05BD] + | [\u05BF] + | [\u05C1-\u05C2] + | [\u05C4-\u05C5] + | [\u05C7] + | [\u0600-\u0605] + | [\u0610-\u061A] + | [\u061C] + | [\u064B-\u0669] + | [\u0670] + | [\u06D6-\u06DD] + | [\u06DF-\u06E4] + | [\u06E7-\u06E8] + | [\u06EA-\u06ED] + | [\u06F0-\u06F9] + | [\u070F] + | [\u0711] + | [\u0730-\u074A] + | [\u07A6-\u07B0] + | [\u07C0-\u07C9] + | [\u07EB-\u07F3] + | [\u0816-\u0819] + | [\u081B-\u0823] + | [\u0825-\u0827] + | [\u0829-\u082D] + | [\u0859-\u085B] + | [\u08D4-\u0903] + | [\u093A-\u093C] + | [\u093E-\u094F] + | [\u0951-\u0957] + | [\u0962-\u0963] + | [\u0966-\u096F] + | [\u0981-\u0983] + | [\u09BC] + | [\u09BE-\u09C4] + | [\u09C7-\u09C8] + | [\u09CB-\u09CD] + | [\u09D7] + | [\u09E2-\u09E3] + | [\u09E6-\u09EF] + | [\u0A01-\u0A03] + | [\u0A3C] + | [\u0A3E-\u0A42] + | [\u0A47-\u0A48] + | [\u0A4B-\u0A4D] + | [\u0A51] + | [\u0A66-\u0A71] + | [\u0A75] + | [\u0A81-\u0A83] + | [\u0ABC] + | [\u0ABE-\u0AC5] + | [\u0AC7-\u0AC9] + | [\u0ACB-\u0ACD] + | [\u0AE2-\u0AE3] + | [\u0AE6-\u0AEF] + | [\u0AFA-\u0AFF] + | [\u0B01-\u0B03] + | [\u0B3C] + | [\u0B3E-\u0B44] + | [\u0B47-\u0B48] + | [\u0B4B-\u0B4D] + | [\u0B56-\u0B57] + | [\u0B62-\u0B63] + | [\u0B66-\u0B6F] + | [\u0B82] + | [\u0BBE-\u0BC2] + | [\u0BC6-\u0BC8] + | [\u0BCA-\u0BCD] + | [\u0BD7] + | [\u0BE6-\u0BEF] + | [\u0C00-\u0C03] + | [\u0C3E-\u0C44] + | [\u0C46-\u0C48] + | [\u0C4A-\u0C4D] + | [\u0C55-\u0C56] + | [\u0C62-\u0C63] + | [\u0C66-\u0C6F] + | [\u0C81-\u0C83] + | [\u0CBC] + | [\u0CBE-\u0CC4] + | [\u0CC6-\u0CC8] + | [\u0CCA-\u0CCD] + | [\u0CD5-\u0CD6] + | [\u0CE2-\u0CE3] + | [\u0CE6-\u0CEF] + | [\u0D00-\u0D03] + | [\u0D3B-\u0D3C] + | [\u0D3E-\u0D44] + | [\u0D46-\u0D48] + | [\u0D4A-\u0D4D] + | [\u0D57] + | [\u0D62-\u0D63] + | [\u0D66-\u0D6F] + | [\u0D82-\u0D83] + | [\u0DCA] + | [\u0DCF-\u0DD4] + | [\u0DD6] + | [\u0DD8-\u0DDF] + | [\u0DE6-\u0DEF] + | [\u0DF2-\u0DF3] + | [\u0E31] + | [\u0E34-\u0E3A] + | [\u0E47-\u0E4E] + | [\u0E50-\u0E59] + | [\u0EB1] + | [\u0EB4-\u0EB9] + | [\u0EBB-\u0EBC] + | [\u0EC8-\u0ECD] + | [\u0ED0-\u0ED9] + | [\u0F18-\u0F19] + | [\u0F20-\u0F29] + | [\u0F35] + | [\u0F37] + | [\u0F39] + | [\u0F3E-\u0F3F] + | [\u0F71-\u0F84] + | [\u0F86-\u0F87] + | [\u0F8D-\u0F97] + | [\u0F99-\u0FBC] + | [\u0FC6] + | [\u102B-\u103E] + | [\u1040-\u1049] + | [\u1056-\u1059] + | [\u105E-\u1060] + | [\u1062-\u1064] + | [\u1067-\u106D] + | [\u1071-\u1074] + | [\u1082-\u108D] + | [\u108F-\u109D] + | [\u135D-\u135F] + | [\u1712-\u1714] + | [\u1732-\u1734] + | [\u1752-\u1753] + | [\u1772-\u1773] + | [\u17B4-\u17D3] + | [\u17DD] + | [\u17E0-\u17E9] + | [\u180B-\u180E] + | [\u1810-\u1819] + | [\u1885-\u1886] + | [\u18A9] + | [\u1920-\u192B] + | [\u1930-\u193B] + | [\u1946-\u194F] + | [\u19D0-\u19D9] + | [\u1A17-\u1A1B] + | [\u1A55-\u1A5E] + | [\u1A60-\u1A7C] + | [\u1A7F-\u1A89] + | [\u1A90-\u1A99] + | [\u1AB0-\u1ABD] + | [\u1B00-\u1B04] + | [\u1B34-\u1B44] + | [\u1B50-\u1B59] + | [\u1B6B-\u1B73] + | [\u1B80-\u1B82] + | [\u1BA1-\u1BAD] + | [\u1BB0-\u1BB9] + | [\u1BE6-\u1BF3] + | [\u1C24-\u1C37] + | [\u1C40-\u1C49] + | [\u1C50-\u1C59] + | [\u1CD0-\u1CD2] + | [\u1CD4-\u1CE8] + | [\u1CED] + | [\u1CF2-\u1CF4] + | [\u1CF7-\u1CF9] + | [\u1DC0-\u1DF9] + | [\u1DFB-\u1DFF] + | [\u200B-\u200F] + | [\u202A-\u202E] + | [\u2060-\u2064] + | [\u2066-\u206F] + | [\u20D0-\u20DC] + | [\u20E1] + | [\u20E5-\u20F0] + | [\u2CEF-\u2CF1] + | [\u2D7F] + | [\u2DE0-\u2DFF] + | [\u302A-\u302F] + | [\u3099-\u309A] + | [\uA620-\uA629] + | [\uA66F] + | [\uA674-\uA67D] + | [\uA69E-\uA69F] + | [\uA6F0-\uA6F1] + | [\uA802] + | [\uA806] + | [\uA80B] + | [\uA823-\uA827] + | [\uA880-\uA881] + | [\uA8B4-\uA8C5] + | [\uA8D0-\uA8D9] + | [\uA8E0-\uA8F1] + | [\uA900-\uA909] + | [\uA926-\uA92D] + | [\uA947-\uA953] + | [\uA980-\uA983] + | [\uA9B3-\uA9C0] + | [\uA9D0-\uA9D9] + | [\uA9E5] + | [\uA9F0-\uA9F9] + | [\uAA29-\uAA36] + | [\uAA43] + | [\uAA4C-\uAA4D] + | [\uAA50-\uAA59] + | [\uAA7B-\uAA7D] + | [\uAAB0] + | [\uAAB2-\uAAB4] + | [\uAAB7-\uAAB8] + | [\uAABE-\uAABF] + | [\uAAC1] + | [\uAAEB-\uAAEF] + | [\uAAF5-\uAAF6] + | [\uABE3-\uABEA] + | [\uABEC-\uABED] + | [\uABF0-\uABF9] + | [\uFB1E] + | [\uFE00-\uFE0F] + | [\uFE20-\uFE2F] + | [\uFEFF] + | [\uFF10-\uFF19] + | [\uFFF9-\uFFFB] + ; + +// string expression + +DOUBLE_QUOTE: '"' { + if (getInterpolationMode() == DISABLE) { + pushMode(StaticString); + } else { + pushMode(DynamicString); + } +}; + +CATCH_ALL: . ; + +mode StaticString; + +StaticStringCharacters: StaticStringCharacter+; + +fragment StaticStringCharacter + : ~["\\] + | '\\' '"'? + ; + +STATIC_STRING_CLOSE: DOUBLE_QUOTE -> popMode, type(DOUBLE_QUOTE); + +mode DynamicString; + +DyStrExprStart: '${' { + if (getInterpolationMode() == SCRIPT) { + pushMode(StringExpression); + } else if (getInterpolationMode() == VARIABLE) { + pushMode(SelectorVariable); + } +}; + +DyStrText: DyStringCharacter+; + +fragment DyStringCharacter + : ~["\\$] + | '\\' ('"' | '$')? + ; + +DYNAMIC_STRING_CLOSE: DOUBLE_QUOTE -> popMode, type(DOUBLE_QUOTE); + +mode SelectorVariable; + +SelectorVariable_VANME: { + consumeSelectorVariable(); +}; + +mode StringExpression; + +StrExpr_THEN: THEN -> type(THEN); +StrExpr_FOR: FOR -> type(FOR); +StrExpr_IF: IF -> type(IF); +StrExpr_ELSE: ELSE -> type(ELSE); +StrExpr_WHILE: WHILE -> type(WHILE); +StrExpr_BREAK: BREAK -> type(BREAK); +StrExpr_CONTINUE: CONTINUE -> type(CONTINUE); +StrExpr_RETURN: RETURN -> type(RETURN); +StrExpr_FUNCTION: FUNCTION -> type(FUNCTION); +StrExpr_MACRO: MACRO -> type(MACRO); +StrExpr_IMPORT: IMPORT -> type(IMPORT); +StrExpr_STATIC: STATIC -> type(STATIC); +StrExpr_NEW: NEW -> type(NEW); +StrExpr_BYTE: BYTE -> type(BYTE); +StrExpr_SHORT: SHORT -> type(SHORT); +StrExpr_INT: INT -> type(INT); +StrExpr_LONG: LONG -> type(LONG); +StrExpr_FLOAT: FLOAT -> type(FLOAT); +StrExpr_DOUBLE: DOUBLE -> type(DOUBLE); +StrExpr_CHAR: CHAR -> type(CHAR); +StrExpr_BOOL: BOOL -> type(BOOL); +StrExpr_NULL: NULL -> type(NULL); +StrExpr_TRUE: TRUE -> type(TRUE); +StrExpr_FALSE: FALSE -> type(FALSE); +StrExpr_EXTENDS: EXTENDS -> type(EXTENDS); +StrExpr_SUPER: SUPER -> type(SUPER); +StrExpr_TRY: TRY -> type(TRY); +StrExpr_CATCH: CATCH -> type(CATCH); +StrExpr_FINALLY: FINALLY -> type(FINALLY); +StrExpr_THROW: THROW -> type(THROW); + +StrExpr_CLASS: CLASS -> type(CLASS); +StrExpr_THIS: THIS -> type(THIS); + +StrExpr_QuoteStringLiteral: QuoteStringLiteral -> type(QuoteStringLiteral); + +StrExpr_IntegerLiteral: IntegerLiteral -> type(IntegerLiteral); +StrExpr_FloatingPointLiteral: FloatingPointLiteral -> type(FloatingPointLiteral); +StrExpr_IntegerOrFloatingLiteral: IntegerOrFloatingLiteral -> type(IntegerOrFloatingLiteral); + +StrExpr_LPAREN: LPAREN -> type(LPAREN); +StrExpr_RPAREN: RPAREN -> type(RPAREN); +StrExpr_LBRACE: LBRACE -> pushMode(StringExpression), type(LBRACE); +StrExpr_RBRACE: RBRACE -> popMode, type(RBRACE); +StrExpr_LBRACK: LBRACK -> type(LBRACK); +StrExpr_RBRACK: RBRACK -> type(RBRACK); + +StrExpr_DOT: DOT -> type(DOT); +StrExpr_ARROW: ARROW -> type(ARROW); +StrExpr_SEMI: SEMI -> type(SEMI); +StrExpr_COMMA: COMMA -> type(COMMA); +StrExpr_QUESTION: QUESTION -> type(QUESTION); +StrExpr_COLON: COLON -> type(COLON); +StrExpr_DCOLON: DCOLON -> type(DCOLON); +StrExpr_GT: GT -> type(GT); +StrExpr_LT: LT -> type(LT); +StrExpr_EQ: EQ -> type(EQ); +StrExpr_NOEQ: NOEQ -> type(NOEQ); +StrExpr_RIGHSHIFT_ASSGIN: RIGHSHIFT_ASSGIN -> type(RIGHSHIFT_ASSGIN); +StrExpr_RIGHSHIFT: RIGHSHIFT -> type(RIGHSHIFT); +StrExpr_OPTIONAL_CHAINING: OPTIONAL_CHAINING -> type(OPTIONAL_CHAINING); +StrExpr_SPREAD_CHAINING: SPREAD_CHAINING -> type(SPREAD_CHAINING); +StrExpr_URSHIFT_ASSGIN: URSHIFT_ASSGIN -> type(URSHIFT_ASSGIN); +StrExpr_URSHIFT: URSHIFT -> type(URSHIFT); +StrExpr_LSHIFT_ASSGIN: LSHIFT_ASSGIN -> type(LSHIFT_ASSGIN); +StrExpr_LEFTSHIFT: LEFTSHIFT -> type(LEFTSHIFT); +StrExpr_GE: GE -> type(GE); +StrExpr_LE: LE -> type(LE); +StrExpr_DOTMUL: DOTMUL -> type(DOTMUL); +StrExpr_CARET: CARET -> type(CARET); +StrExpr_ADD_ASSIGN: ADD_ASSIGN -> type(ADD_ASSIGN); +StrExpr_SUB_ASSIGN: SUB_ASSIGN -> type(SUB_ASSIGN); +StrExpr_AND_ASSIGN: AND_ASSIGN -> type(AND_ASSIGN); +StrExpr_OR_ASSIGN: OR_ASSIGN -> type(OR_ASSIGN); +StrExpr_MUL_ASSIGN: MUL_ASSIGN -> type(MUL_ASSIGN); +StrExpr_MOD_ASSIGN: MOD_ASSIGN -> type(MOD_ASSIGN); +StrExpr_DIV_ASSIGN: DIV_ASSIGN -> type(DIV_ASSIGN); +StrExpr_XOR_ASSIGN: XOR_ASSIGN -> type(XOR_ASSIGN); + +StrExpr_BANG: BANG -> type(BANG); +StrExpr_TILDE: TILDE -> type(TILDE); + +StrExpr_ADD: ADD -> type(ADD); +StrExpr_SUB: SUB -> type(SUB); +StrExpr_MUL: MUL -> type(MUL); +StrExpr_DIV: DIV -> type(DIV); +StrExpr_BIT_AND: BIT_AND -> type(BIT_AND); +StrExpr_BIT_OR: BIT_OR -> type(BIT_OR); +StrExpr_MOD: MOD -> type(MOD); + +StrExpr_INC: INC -> type(INC); +StrExpr_DEC: DEC -> type(DEC); + +StrExpr_NEWLINE: NEWLINE -> type(NEWLINE); + +StrExpr_WS: WS -> skip; + +StrExpr_COMMENT: COMMENT -> type(COMMENT); + +StrExpr_LINE_COMMENT: LINE_COMMENT -> type(LINE_COMMENT); + +StrExpr_OPID: OPID -> type(OPID); + +StrExpr_ID: ID -> type(ID); + +StrExpr_DOUBLE_QUOTE: DOUBLE_QUOTE -> type(DOUBLE_QUOTE), pushMode(DynamicString); +StrExpr_SELECTOR_START: SELECTOR_START -> type(SELECTOR_START); \ No newline at end of file diff --git a/src/main/antlr4/QLexer.tokens b/src/main/antlr4/QLexer.tokens new file mode 100644 index 0000000..22b8048 --- /dev/null +++ b/src/main/antlr4/QLexer.tokens @@ -0,0 +1,180 @@ +FOR=1 +IF=2 +ELSE=3 +WHILE=4 +BREAK=5 +CONTINUE=6 +RETURN=7 +FUNCTION=8 +MACRO=9 +IMPORT=10 +STATIC=11 +NEW=12 +BYTE=13 +SHORT=14 +INT=15 +LONG=16 +FLOAT=17 +DOUBLE=18 +CHAR=19 +BOOL=20 +NULL=21 +TRUE=22 +FALSE=23 +EXTENDS=24 +SUPER=25 +TRY=26 +CATCH=27 +FINALLY=28 +THROW=29 +THEN=30 +CLASS=31 +THIS=32 +QuoteStringLiteral=33 +IntegerLiteral=34 +FloatingPointLiteral=35 +IntegerOrFloatingLiteral=36 +LPAREN=37 +RPAREN=38 +LBRACE=39 +RBRACE=40 +LBRACK=41 +RBRACK=42 +DOT=43 +ARROW=44 +SEMI=45 +COMMA=46 +QUESTION=47 +COLON=48 +DCOLON=49 +GT=50 +LT=51 +EQ=52 +NOEQ=53 +RIGHSHIFT_ASSGIN=54 +RIGHSHIFT=55 +OPTIONAL_CHAINING=56 +SPREAD_CHAINING=57 +URSHIFT_ASSGIN=58 +URSHIFT=59 +LSHIFT_ASSGIN=60 +LEFTSHIFT=61 +GE=62 +LE=63 +DOTMUL=64 +CARET=65 +ADD_ASSIGN=66 +SUB_ASSIGN=67 +AND_ASSIGN=68 +OR_ASSIGN=69 +MUL_ASSIGN=70 +MOD_ASSIGN=71 +DIV_ASSIGN=72 +XOR_ASSIGN=73 +BANG=74 +TILDE=75 +ADD=76 +SUB=77 +MUL=78 +DIV=79 +BIT_AND=80 +BIT_OR=81 +MOD=82 +INC=83 +DEC=84 +NEWLINE=85 +WS=86 +COMMENT=87 +LINE_COMMENT=88 +OPID=89 +SELECTOR_START=90 +ID=91 +DOUBLE_QUOTE=92 +CATCH_ALL=93 +StaticStringCharacters=94 +DyStrExprStart=95 +DyStrText=96 +SelectorVariable_VANME=97 +StrExpr_WS=98 +'for'=1 +'if'=2 +'else'=3 +'while'=4 +'break'=5 +'continue'=6 +'return'=7 +'function'=8 +'macro'=9 +'import'=10 +'static'=11 +'new'=12 +'byte'=13 +'short'=14 +'int'=15 +'long'=16 +'float'=17 +'double'=18 +'char'=19 +'boolean'=20 +'null'=21 +'true'=22 +'false'=23 +'extends'=24 +'super'=25 +'try'=26 +'catch'=27 +'finally'=28 +'throw'=29 +'then'=30 +'class'=31 +'this'=32 +'('=37 +')'=38 +'{'=39 +'}'=40 +'['=41 +']'=42 +'.'=43 +'->'=44 +';'=45 +','=46 +'?'=47 +':'=48 +'::'=49 +'>'=50 +'<'=51 +'='=52 +'<>'=53 +'>>='=54 +'>>'=55 +'?.'=56 +'*.'=57 +'>>>='=58 +'>>>'=59 +'<<='=60 +'<<'=61 +'>='=62 +'<='=63 +'.*'=64 +'^'=65 +'+='=66 +'-='=67 +'&='=68 +'|='=69 +'*='=70 +'%='=71 +'/='=72 +'^='=73 +'!'=74 +'~'=75 +'+'=76 +'-'=77 +'*'=78 +'/'=79 +'&'=80 +'|'=81 +'%'=82 +'++'=83 +'--'=84 +'"'=92 +'${'=95 diff --git a/src/main/java/com/alibaba/qlexpress4/CheckOptions.java b/src/main/java/com/alibaba/qlexpress4/CheckOptions.java new file mode 100644 index 0000000..ea876d2 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/CheckOptions.java @@ -0,0 +1,69 @@ +package com.alibaba.qlexpress4; + +import com.alibaba.qlexpress4.operator.OperatorCheckStrategy; + +/** + * Script validation configuration class + * Used to configure restriction rules during script validation + * + * @author QLExpress Team + */ +public class CheckOptions { + + /** + * Operator check strategy for script validation + * default OperatorCheckStrategy.allowAll() + */ + private final OperatorCheckStrategy operatorCheckStrategy; + + /** + * Whether to disable function calls in the script + * default false + */ + private final boolean disableFunctionCalls; + + /** + * Default validation options + */ + public static final CheckOptions DEFAULT_OPTIONS = CheckOptions.builder().build(); + + private CheckOptions(OperatorCheckStrategy operatorCheckStrategy, boolean disableFunctionCalls) { + this.operatorCheckStrategy = operatorCheckStrategy; + this.disableFunctionCalls = disableFunctionCalls; + } + + public OperatorCheckStrategy getCheckStrategy() { + return operatorCheckStrategy; + } + + public boolean isDisableFunctionCalls() { + return disableFunctionCalls; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private OperatorCheckStrategy operatorCheckStrategy = OperatorCheckStrategy.allowAll(); + + private boolean disableFunctionCalls = false; + + private Builder() { + } + + public Builder operatorCheckStrategy(OperatorCheckStrategy operatorCheckStrategy) { + this.operatorCheckStrategy = operatorCheckStrategy; + return this; + } + + public Builder disableFunctionCalls(boolean disableFunctionCalls) { + this.disableFunctionCalls = disableFunctionCalls; + return this; + } + + public CheckOptions build() { + return new CheckOptions(operatorCheckStrategy, disableFunctionCalls); + } + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/ClassSupplier.java b/src/main/java/com/alibaba/qlexpress4/ClassSupplier.java new file mode 100644 index 0000000..02c1810 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/ClassSupplier.java @@ -0,0 +1,11 @@ +package com.alibaba.qlexpress4; + +/** + * Author: DQinYuan + */ +@FunctionalInterface +public interface ClassSupplier { + + Class loadCls(String clsQualifiedName); + +} \ No newline at end of file diff --git a/src/main/java/com/alibaba/qlexpress4/DefaultClassSupplier.java b/src/main/java/com/alibaba/qlexpress4/DefaultClassSupplier.java new file mode 100644 index 0000000..ce8b4aa --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/DefaultClassSupplier.java @@ -0,0 +1,39 @@ +package com.alibaba.qlexpress4; + +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Author: DQinYuan + */ +public class DefaultClassSupplier implements ClassSupplier { + + private static final DefaultClassSupplier INSTANCE = new DefaultClassSupplier(); + + public static DefaultClassSupplier getInstance() { + return INSTANCE; + } + + private final Map>> cache = new ConcurrentHashMap<>(); + + /** + * @param clsQualifiedName qualified name of class + * @return loaded class + */ + @Override + public Class loadCls(String clsQualifiedName) { + Optional> clsOp = cache.computeIfAbsent(clsQualifiedName, this::loadClsInner); + return clsOp.orElse(null); + } + + private Optional> loadClsInner(String clsQualifiedName) { + try { + Class aClass = Class.forName(clsQualifiedName); + return Optional.of(aClass); + } + catch (ClassNotFoundException | NoClassDefFoundError e) { + return Optional.empty(); + } + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/Express4Runner.java b/src/main/java/com/alibaba/qlexpress4/Express4Runner.java new file mode 100644 index 0000000..3c8839c --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/Express4Runner.java @@ -0,0 +1,751 @@ +package com.alibaba.qlexpress4; + +import com.alibaba.qlexpress4.aparser.CheckVisitor; +import com.alibaba.qlexpress4.aparser.GeneratorScope; +import com.alibaba.qlexpress4.aparser.ImportManager; +import com.alibaba.qlexpress4.aparser.MacroDefine; +import com.alibaba.qlexpress4.aparser.OutFunctionVisitor; +import com.alibaba.qlexpress4.aparser.OutVarAttrsVisitor; +import com.alibaba.qlexpress4.aparser.OutVarNamesVisitor; +import com.alibaba.qlexpress4.aparser.QCompileCache; +import com.alibaba.qlexpress4.aparser.QLParser; +import com.alibaba.qlexpress4.aparser.QvmInstructionVisitor; +import com.alibaba.qlexpress4.aparser.SyntaxTreeFactory; +import com.alibaba.qlexpress4.aparser.TraceExpressionVisitor; +import com.alibaba.qlexpress4.aparser.compiletimefunction.CompileTimeFunction; +import com.alibaba.qlexpress4.api.BatchAddFunctionResult; +import com.alibaba.qlexpress4.api.QLFunctionalVarargs; +import com.alibaba.qlexpress4.exception.PureErrReporter; +import com.alibaba.qlexpress4.exception.QLException; +import com.alibaba.qlexpress4.exception.QLSyntaxException; +import com.alibaba.qlexpress4.runtime.DelegateQContext; +import com.alibaba.qlexpress4.runtime.QLambda; +import com.alibaba.qlexpress4.runtime.QLambdaDefinitionInner; +import com.alibaba.qlexpress4.runtime.QLambdaTrace; +import com.alibaba.qlexpress4.runtime.QvmGlobalScope; +import com.alibaba.qlexpress4.runtime.QvmRuntime; +import com.alibaba.qlexpress4.runtime.ReflectLoader; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.context.ExpressContext; +import com.alibaba.qlexpress4.runtime.context.MapExpressContext; +import com.alibaba.qlexpress4.runtime.context.ObjectFieldExpressContext; +import com.alibaba.qlexpress4.runtime.context.QLAliasContext; +import com.alibaba.qlexpress4.runtime.function.CustomFunction; +import com.alibaba.qlexpress4.runtime.function.ExtensionFunction; +import com.alibaba.qlexpress4.runtime.function.QMethodFunction; +import com.alibaba.qlexpress4.runtime.instruction.QLInstruction; +import com.alibaba.qlexpress4.runtime.operator.CustomBinaryOperator; +import com.alibaba.qlexpress4.runtime.operator.OperatorManager; +import com.alibaba.qlexpress4.runtime.trace.ExpressionTrace; +import com.alibaba.qlexpress4.runtime.trace.QTraces; +import com.alibaba.qlexpress4.runtime.trace.TracePointTree; +import com.alibaba.qlexpress4.utils.BasicUtil; +import com.alibaba.qlexpress4.utils.QLFunctionUtil; +import org.antlr.v4.runtime.dfa.DFA; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +/** + * Author: DQinYuan + */ +public class Express4Runner { + private final OperatorManager operatorManager = new OperatorManager(); + + private final Map> compileCache = new ConcurrentHashMap<>(); + + private final Map userDefineFunction = new ConcurrentHashMap<>(); + + private final Map compileTimeFunctions = new ConcurrentHashMap<>(); + + private final GeneratorScope globalScope = new GeneratorScope(null, "global", new ConcurrentHashMap<>()); + + private final ReflectLoader reflectLoader; + + private final InitOptions initOptions; + + public Express4Runner(InitOptions initOptions) { + this.initOptions = initOptions; + this.reflectLoader = new ReflectLoader(initOptions.getSecurityStrategy(), initOptions.isAllowPrivateAccess()); + SyntaxTreeFactory.warmUp(); + } + + public CustomFunction getFunction(String functionName) { + return userDefineFunction.get(functionName); + } + + public CompileTimeFunction getCompileTimeFunction(String functionName) { + return compileTimeFunctions.get(functionName); + } + + /** + * Execute the script with variables set in the context; the map key corresponds to the + * variable name referenced in the script. + * + * @param script the script content to execute + * @param context variables for execution, keyed by variable name + * @param qlOptions execution options (e.g. interpolation, debug) + * @return result of script execution and related traces + * @throws QLException if a script or runtime error occurs + */ + public QLResult execute(String script, Map context, QLOptions qlOptions) + throws QLException { + return execute(script, new MapExpressContext(context), qlOptions); + } + + /** + * Execute a template string by wrapping it as a dynamic string literal. + * Template does not support newlines in this mode. + * + * @param template the template text to evaluate as a dynamic string + * @param context variables available to the template + * @param qlOptions execution options + * @return result of template evaluation + * @throws QLException if compilation or execution fails + */ + public QLResult executeTemplate(String template, Map context, QLOptions qlOptions) + throws QLException { + String script = wrapAsDynamicString(template); + return execute(script, context, qlOptions); + } + + private String wrapAsDynamicString(String template) { + if (template == null) { + return "\"\""; + } + String escaped = template.replace("\"", "\\\""); + return "\"" + escaped + "\""; + } + + /** + * Execute the script with variables resolved from the fields of the given context object. + * The variable name in the script corresponds to the field name on the object. + * + * @param script the script content to execute + * @param context the object whose public fields/properties are exposed as variables + * @param qlOptions execution options + * @return result of script execution + * @throws QLException if a script or runtime error occurs + */ + public QLResult execute(String script, Object context, QLOptions qlOptions) + throws QLException { + return execute(script, new ObjectFieldExpressContext(context, this), qlOptions); + } + + /** + * Execute the script using objects annotated with {@code @QLAlias}. + * The {@code QLAlias.value} serves as the variable name for each object. + * Objects without the annotation are ignored. + * + * @param script the script content to execute + * @param qlOptions execution options + * @param objects objects annotated with {@code @QLAlias} + * @return result of script execution + * @throws QLException if a script or runtime error occurs + */ + public QLResult executeWithAliasObjects(String script, QLOptions qlOptions, Object... objects) { + return execute(script, new QLAliasContext(objects), qlOptions); + } + + public QLResult execute(String script, ExpressContext context, QLOptions qlOptions) { + QLambdaTrace mainLambdaTrace; + if (initOptions.isDebug()) { + long start = System.currentTimeMillis(); + mainLambdaTrace = parseToLambda(script, context, qlOptions); + initOptions.getDebugInfoConsumer() + .accept("Compile consume time: " + (System.currentTimeMillis() - start) + " ms"); + } + else { + mainLambdaTrace = parseToLambda(script, context, qlOptions); + } + QLambda mainLambda = mainLambdaTrace.getqLambda(); + try { + Object result; + if (initOptions.isDebug()) { + long start = System.currentTimeMillis(); + result = mainLambda.call().getResult().get(); + initOptions.getDebugInfoConsumer() + .accept("Execute consume time: " + (System.currentTimeMillis() - start) + " ms"); + } + else { + result = mainLambda.call().getResult().get(); + } + + return new QLResult(result, mainLambdaTrace.getTraces().getExpressionTraces()); + } + catch (QLException e) { + throw e; + } + catch (Throwable nuKnown) { + // should not run here + throw new RuntimeException(nuKnown); + } + } + + private QTraces convertPoints2QTraces(List expressionTracePoints) { + Map traceMap = new HashMap<>(); + List expressionTraces = expressionTracePoints.stream() + .map(tracePoint -> convertPoint2Trace(tracePoint, traceMap)) + .collect(Collectors.toList()); + return new QTraces(expressionTraces, traceMap); + } + + private ExpressionTrace convertPoint2Trace(TracePointTree tree, Map traceMap) { + if (tree.getChildren().isEmpty()) { + ExpressionTrace result = new ExpressionTrace(tree.getType(), tree.getToken(), Collections.emptyList(), + tree.getLine(), tree.getCol(), tree.getPosition()); + traceMap.put(result.getPosition(), result); + return result; + } + List mergedChildren = + tree.getChildren().stream().map(child -> convertPoint2Trace(child, traceMap)).collect(Collectors.toList()); + ExpressionTrace result = new ExpressionTrace(tree.getType(), tree.getToken(), mergedChildren, tree.getLine(), + tree.getCol(), tree.getPosition()); + traceMap.put(result.getPosition(), result); + return result; + } + + /** + * Get external variables (those that must be provided via context) referenced by the script. + * + * @param script the script content + * @return names of external variables referenced in the script + */ + public Set getOutVarNames(String script) { + QLParser.ProgramContext programContext = parseToSyntaxTree(script); + OutVarNamesVisitor outVarNamesVisitor = new OutVarNamesVisitor(inheritDefaultImport()); + programContext.accept(outVarNamesVisitor); + return outVarNamesVisitor.getOutVars(); + } + + /** + * Get external variable attribute access paths referenced by the script. + * + * @param script the script content + * @return attribute chains accessed on external variables + */ + public Set> getOutVarAttrs(String script) { + QLParser.ProgramContext programContext = parseToSyntaxTree(script); + OutVarAttrsVisitor outVarAttrsVisitor = new OutVarAttrsVisitor(inheritDefaultImport()); + programContext.accept(outVarAttrsVisitor); + return outVarAttrsVisitor.getOutVarAttrs(); + } + + /** + * Get external functions (those that must be provided via context) referenced by the script. + * + * @param script the script content + * @return names of external functions referenced in the script + */ + public Set getOutFunctions(String script) { + QLParser.ProgramContext programContext = parseToSyntaxTree(script); + OutFunctionVisitor outFunctionVisitor = new OutFunctionVisitor(); + programContext.accept(outFunctionVisitor); + return outFunctionVisitor.getOutFunctions(); + } + + /** + * Get the expression trace trees for the script without executing it. + * + * @param script the script content + * @return trace trees for each expression + */ + public List getExpressionTracePoints(String script) { + QLParser.ProgramContext programContext = parseToSyntaxTree(script); + TraceExpressionVisitor traceExpressionVisitor = new TraceExpressionVisitor(); + programContext.accept(traceExpressionVisitor); + return traceExpressionVisitor.getExpressionTracePoints(); + } + + /** + * add user defined global macro to QLExpress engine + * @param name macro name + * @param macroScript script for macro + * @return true if add macro successfully. fail if macro name already exists. + */ + public boolean addMacro(String name, String macroScript) { + return globalScope.defineMacroIfAbsent(name, parseMacroDefine(name, macroScript)); + } + + /** + * add or replace user defined global macro to QLExpress engine + * @param name macro name + * @param macroScript script for macro + */ + public void addOrReplaceMacro(String name, String macroScript) { + globalScope.defineMacro(name, parseMacroDefine(name, macroScript)); + } + + private MacroDefine parseMacroDefine(String name, String macroScript) { + QLParser.ProgramContext macroProgram = parseToSyntaxTree(macroScript); + QvmInstructionVisitor macroVisitor = new QvmInstructionVisitor(macroScript, inheritDefaultImport(), + new GeneratorScope("MACRO_" + name, globalScope), operatorManager, QvmInstructionVisitor.Context.MACRO, + compileTimeFunctions, initOptions); + macroProgram.accept(macroVisitor); + List macroInstructions = macroVisitor.getInstructions(); + List blockStatementContexts = macroProgram.blockStatements().blockStatement(); + boolean lastStmtExpress = !blockStatementContexts.isEmpty() && blockStatementContexts + .get(blockStatementContexts.size() - 1) instanceof QLParser.ExpressionStatementContext; + return new MacroDefine(macroInstructions, lastStmtExpress); + } + + /** + * add user defined function to QLExpress engine + * @param name function name + * @param function function definition + * @return true if add function successfully. fail if function name already exists. + */ + public boolean addFunction(String name, CustomFunction function) { + CustomFunction preFunction = userDefineFunction.putIfAbsent(name, function); + return preFunction == null; + } + + public boolean addFunction(String name, Function function) { + return addFunction(name, (qContext, parameters) -> { + T t = parameters.size() > 0 ? (T)parameters.get(0).get() : null; + return function.apply(t); + }); + } + + public boolean addVarArgsFunction(String name, QLFunctionalVarargs functionalVarargs) { + return addFunction(name, (qContext, parameters) -> { + Object[] paramArr = new Object[parameters.size()]; + for (int i = 0; i < paramArr.length; i++) { + paramArr[i] = parameters.get(i).get(); + } + return functionalVarargs.call(paramArr); + }); + } + + public boolean addFunction(String name, Predicate predicate) { + return addFunction(name, (qContext, parameters) -> { + T t = parameters.size() > 0 ? (T)parameters.get(0).get() : null; + return predicate.test(t); + }); + } + + public boolean addFunction(String name, Runnable runnable) { + return addFunction(name, (qContext, parameters) -> { + runnable.run(); + return null; + }); + } + + public boolean addFunction(String name, Consumer consumer) { + return addFunction(name, (qContext, parameters) -> { + T t = parameters.size() > 0 ? (T)parameters.get(0).get() : null; + consumer.accept(t); + return null; + }); + } + + /** + * Add a user-defined function backed by a specific Java service instance method. + * + * @param name function name exposed in QLExpress scripts + * @param serviceObject target service instance, must not be {@code null} + * @param methodName Java method name on the service instance + * @param parameterClassTypes parameter type signature of the Java method; use an empty array for no-arg methods + * @return true if the function was added successfully; false if a function with the same name already exists + * @throws IllegalArgumentException if {@code serviceObject} or {@code methodName} is null, or if no matching + * public method is found on the service type + */ + public boolean addFunctionOfServiceMethod(String name, Object serviceObject, String methodName, + Class[] parameterClassTypes) { + if (serviceObject == null) { + throw new IllegalArgumentException("serviceObject must not be null"); + } + if (methodName == null) { + throw new IllegalArgumentException("methodName must not be null"); + } + + Class[] parameterTypes = parameterClassTypes == null ? new Class[0] : parameterClassTypes; + Method method; + try { + method = serviceObject.getClass().getMethod(methodName, parameterTypes); + } + catch (NoSuchMethodException e) { + throw new IllegalArgumentException("No such public method '" + methodName + "' with parameter types " + + java.util.Arrays.toString(parameterTypes) + " on service object class '" + + serviceObject.getClass().getName() + "'", e); + } + + return addFunction(name, new QMethodFunction(serviceObject, method)); + } + + /** + * execute `scriptWithFunctionDefine` and add functions defined in script + * @param scriptWithFunctionDefine script with function define + * @param context context when execute script + * @param qlOptions qlOptions when execute script + * @return succ and fail functions. fail if function name already exists + */ + public BatchAddFunctionResult addFunctionsDefinedInScript(String scriptWithFunctionDefine, ExpressContext context, + QLOptions qlOptions) { + BatchAddFunctionResult batchResult = new BatchAddFunctionResult(); + QLambdaTrace mainLambdaTrace = parseToLambda(scriptWithFunctionDefine, context, qlOptions); + try { + Map functionTableInScript = mainLambdaTrace.getqLambda().getFunctionDefined(); + for (Map.Entry 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 getFunctionTable() { + return qScope.getFunctionTable(); + } + + @Override + public void push(Value value) { + qScope.push(value); + } + + @Override + public Parameters pop(int number) { + return qScope.pop(number); + } + + @Override + public Value pop() { + return qScope.pop(); + } + + @Override + public Value peek() { + return qScope.peek(); + } + + @Override + public QScope getParent() { + return qScope.getParent(); + } + + @Override + public QScope getCurrentScope() { + return qScope; + } + + @Override + public QScope newScope() { + return qScope = qScope.newScope(); + } + + @Override + public void closeScope() { + qScope = qScope.getParent(); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/ExceptionTable.java b/src/main/java/com/alibaba/qlexpress4/runtime/ExceptionTable.java new file mode 100644 index 0000000..5124818 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/ExceptionTable.java @@ -0,0 +1,41 @@ +package com.alibaba.qlexpress4.runtime; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * Author: DQinYuan + */ +public class ExceptionTable { + + public static final ExceptionTable EMPTY = new ExceptionTable(Collections.emptyList(), null); + + private final List, Integer>> handlerPosMap; + + /** + * nullable + */ + private final Integer finalPos; + + public ExceptionTable(List, Integer>> handlerPosMap, Integer finalPos) { + this.handlerPosMap = handlerPosMap; + this.finalPos = finalPos; + } + + public Integer getRelativePos(Object throwObj) { + for (Map.Entry, Integer> classHandlerMap : handlerPosMap) { + if (throwObj == null) { + return classHandlerMap.getValue(); + } + else if (classHandlerMap.getKey().isAssignableFrom(throwObj.getClass())) { + return classHandlerMap.getValue(); + } + } + return null; + } + + public Integer getFinalPos() { + return finalPos; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/FixedSizeStack.java b/src/main/java/com/alibaba/qlexpress4/runtime/FixedSizeStack.java new file mode 100644 index 0000000..77cc1be --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/FixedSizeStack.java @@ -0,0 +1,61 @@ +package com.alibaba.qlexpress4.runtime; + +/** + * Author: DQinYuan + */ +public class FixedSizeStack { + + private final Value[] elements; + + /** + * next element to push + */ + private int cursor = 0; + + public FixedSizeStack(int size) { + this.elements = new Value[size]; + } + + public void push(Value ele) { + elements[cursor++] = ele; + } + + public Value pop() { + return elements[--cursor]; + } + + public Value peak() { + return elements[cursor - 1]; + } + + public Parameters pop(int n) { + cursor -= n; + return new StackSwapParameters(elements, cursor, n); + } + + private static class StackSwapParameters implements Parameters { + + private final Value[] elements; + + private final int start; + + private final int length; + + private StackSwapParameters(Value[] elements, int start, int length) { + this.elements = elements; + this.start = start; + this.length = length; + } + + @Override + public Value get(int i) { + return i >= length ? null : elements[start + i]; + } + + @Override + public int size() { + return length; + } + } + +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/IMethod.java b/src/main/java/com/alibaba/qlexpress4/runtime/IMethod.java new file mode 100644 index 0000000..b27effc --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/IMethod.java @@ -0,0 +1,24 @@ +package com.alibaba.qlexpress4.runtime; + +import java.lang.reflect.InvocationTargetException; + +/** + * Author: DQinYuan + */ +public interface IMethod { + + Class[] getParameterTypes(); + + boolean isVarArgs(); + + boolean isAccess(); + + void setAccessible(boolean flag); + + String getName(); + + Class getDeclaringClass(); + + Object invoke(Object obj, Object[] args) + throws InvocationTargetException, IllegalAccessException; +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/JvmIMethod.java b/src/main/java/com/alibaba/qlexpress4/runtime/JvmIMethod.java new file mode 100644 index 0000000..050f0e6 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/JvmIMethod.java @@ -0,0 +1,56 @@ +package com.alibaba.qlexpress4.runtime; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +/** + * Author: DQinYuan + */ +public class JvmIMethod implements IMethod { + private final Method method; + + public JvmIMethod(Method method) { + this.method = method; + } + + public Method getMethod() { + return method; + } + + @Override + public Class[] getParameterTypes() { + return method.getParameterTypes(); + } + + @Override + public boolean isVarArgs() { + return method.isVarArgs(); + } + + @Override + public boolean isAccess() { + return Modifier.isPublic(method.getDeclaringClass().getModifiers()) && Modifier.isPublic(method.getModifiers()); + } + + @Override + public void setAccessible(boolean flag) { + method.setAccessible(flag); + } + + @Override + public String getName() { + return method.getName(); + } + + @Override + public Class getDeclaringClass() { + return method.getDeclaringClass(); + } + + @Override + public Object invoke(Object obj, Object[] args) + throws InvocationTargetException, IllegalAccessException { + return method.invoke(obj, args); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/LeftValue.java b/src/main/java/com/alibaba/qlexpress4/runtime/LeftValue.java new file mode 100644 index 0000000..96e7f05 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/LeftValue.java @@ -0,0 +1,40 @@ +package com.alibaba.qlexpress4.runtime; + +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.exception.QLErrorCodes; +import com.alibaba.qlexpress4.runtime.data.convert.ObjTypeConvertor; + +/** + * assignable value + *

+ * Author: DQinYuan + */ +public interface LeftValue extends Value { + + Class getDefinedType(); + + @Override + default Class getType() { + Class definedType = getDefinedType(); + return definedType == null ? Value.super.getType() : definedType; + } + + default void set(Object newValue, ErrorReporter errorReporter) { + Class defineType = getDefinedType(); + ObjTypeConvertor.QConverted result = ObjTypeConvertor.cast(newValue, defineType); + if (!result.isConvertible()) { + throw errorReporter.reportFormat(QLErrorCodes.INCOMPATIBLE_ASSIGNMENT_TYPE.name(), + QLErrorCodes.INCOMPATIBLE_ASSIGNMENT_TYPE.getErrorMsg(), + newValue == null ? "null" : newValue.getClass().getName(), + defineType.getName()); + } + setInner(result.getConverted()); + } + + void setInner(Object newValue); + + /** + * @return Nullable + */ + String getSymbolName(); +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/MemberResolver.java b/src/main/java/com/alibaba/qlexpress4/runtime/MemberResolver.java new file mode 100644 index 0000000..d89d684 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/MemberResolver.java @@ -0,0 +1,242 @@ +package com.alibaba.qlexpress4.runtime; + +import com.alibaba.qlexpress4.annotation.QLAlias; +import com.alibaba.qlexpress4.utils.BasicUtil; +import com.alibaba.qlexpress4.utils.CacheUtil; +import com.alibaba.qlexpress4.utils.QLAliasUtils; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Author: DQinYuan + */ +public class MemberResolver { + + public enum MatchPriority { + MISMATCH(-1), + // e.g. Integer -> Number + EXTEND(0), + // e.g. BigDecimal -> int + NUMBER_DEMOTION(9), + // e.g. int -> long + // 1 -> 8 + NUMBER_PROMOTION(8), + // e.g. Integer -> int + UNBOX(9), + // e.g. QLambda -> Function, Runnable, ... + LAMBDA(10), + // e.g. Integer -> Integer + EQUAL(11); + + public final int priority; + + MatchPriority(int priority) { + this.priority = priority; + } + } + + public static Constructor resolveConstructor(Class cls, Class[] argTypes) { + Constructor[] constructors = cls.getConstructors(); + + // simple match + Class[][] candidates = new Class[constructors.length][]; + for (int i = 0; i < constructors.length; i++) { + candidates[i] = constructors[i].getParameterTypes(); + } + Integer bestIndex = resolveBestMatch(candidates, argTypes); + if (bestIndex != null) { + return constructors[bestIndex]; + } + + // var args match + List[]> varArgsCandidates = new ArrayList<>(constructors.length); + List varArgsConstructorI = new ArrayList<>(constructors.length); + for (int i = 0; i < constructors.length; i++) { + Constructor constructor = constructors[i]; + if (!constructor.isVarArgs()) { + continue; + } + varArgsCandidates.add(adapt2VarArgTypes(constructor.getParameterTypes(), argTypes.length)); + varArgsConstructorI.add(i); + } + Integer varArgBestIndex = resolveBestMatch(varArgsCandidates.toArray(new Class[0][]), argTypes); + if (varArgBestIndex == null) { + return null; + } + return constructors[varArgsConstructorI.get(varArgBestIndex)]; + } + + public static IMethod resolveMethod(Class cls, String methodName, Class[] argTypes, boolean isStatic, + boolean allowPrivate) { + Class curCls = cls; + List> inters = new ArrayList<>(); + while (curCls != null) { + IMethod method = resolveDeclaredMethod(curCls, methodName, argTypes, isStatic, allowPrivate); + if (method != null) { + return method; + } + // collect interfaces implemented by current class to search default methods later + Class[] curInters = curCls.getInterfaces(); + inters.addAll(Arrays.asList(curInters)); + + curCls = curCls.getSuperclass(); + } + // interface method + return resolveIntersMethod(inters.toArray(new Class[0]), methodName, argTypes, isStatic); + } + + private static IMethod resolveIntersMethod(Class[] inters, String methodName, Class[] argTypes, + boolean isStatic) { + for (Class inter : inters) { + IMethod method = resolveInterMethod(inter, methodName, argTypes, isStatic); + if (method != null) { + return method; + } + } + return null; + } + + private static IMethod resolveInterMethod(Class inter, String methodName, Class[] argTypes, + boolean isStatic) { + // no private method in interface, so pass false to 'allowPrivate' + IMethod method = resolveDeclaredMethod(inter, methodName, argTypes, isStatic, false); + if (method != null) { + return method; + } + return resolveIntersMethod(inter.getInterfaces(), methodName, argTypes, isStatic); + } + + public static int resolvePriority(Class[] paramTypes, Class[] argTypes) { + if (paramTypes.length != argTypes.length) { + return MatchPriority.MISMATCH.priority; + } + int methodPriority = MatchPriority.EQUAL.priority; + for (int i = 0; i < paramTypes.length; i++) { + Class paramType = paramTypes[i]; + Class argType = argTypes[i]; + int paramPriority = resolveArgPriority(paramType, argType); + if (paramPriority == MatchPriority.MISMATCH.priority) { + return paramPriority; + } + if (paramPriority < methodPriority) { + // methodPriority is min(paramPriority) + methodPriority = paramPriority; + } + } + return methodPriority; + } + + private static IMethod resolveDeclaredMethod(Class cls, String methodName, Class[] argTypes, boolean isStatic, + boolean allowPrivate) { + return resolveMethod(getDeclaredMethod(cls, methodName, isStatic, allowPrivate), argTypes); + } + + public static IMethod resolveMethod(List methods, Class[] argTypes) { + // simple match + Class[][] candidates = new Class[methods.size()][]; + for (int i = 0; i < methods.size(); i++) { + IMethod declaredMethod = methods.get(i); + candidates[i] = declaredMethod.getParameterTypes(); + } + Integer bestIndex = resolveBestMatch(candidates, argTypes); + if (bestIndex != null) { + return methods.get(bestIndex); + } + + // var args match + List[]> varArgsCandidates = new ArrayList<>(methods.size()); + List varArgsMethodI = new ArrayList<>(methods.size()); + for (int i = 0; i < methods.size(); i++) { + IMethod declaredMethod = methods.get(i); + if (!declaredMethod.isVarArgs()) { + continue; + } + varArgsCandidates.add(adapt2VarArgTypes(declaredMethod.getParameterTypes(), argTypes.length)); + varArgsMethodI.add(i); + } + Integer varArgBestIndex = resolveBestMatch(varArgsCandidates.toArray(new Class[0][]), argTypes); + if (varArgBestIndex == null) { + return null; + } + return methods.get(varArgsMethodI.get(varArgBestIndex)); + } + + private static List getDeclaredMethod(Class cls, String methodName, boolean isStatic, + boolean allowPrivate) { + Method[] declaredMethods = cls.getDeclaredMethods(); + List result = new ArrayList<>(declaredMethods.length); + for (Method declaredMethod : declaredMethods) { + if (!methodName.equals(declaredMethod.getName()) + && !QLAliasUtils.matchQLAlias(methodName, declaredMethod.getAnnotationsByType(QLAlias.class))) { + continue; + } + if ((!isStatic || BasicUtil.isStatic(declaredMethod)) + && (allowPrivate || BasicUtil.isPublic(declaredMethod))) { + result.add(new JvmIMethod(declaredMethod)); + } + } + return result; + } + + private static Class[] adapt2VarArgTypes(Class[] parameterTypes, int argLength) { + Class varItemType = parameterTypes[parameterTypes.length - 1].getComponentType(); + + Class[] varParamTypes = new Class[argLength]; + System.arraycopy(parameterTypes, 0, varParamTypes, 0, parameterTypes.length - 1); + for (int i = parameterTypes.length - 1; i < argLength; i++) { + varParamTypes[i] = varItemType; + } + return varParamTypes; + } + + public static Integer resolveBestMatch(Class[][] candidates, Class[] argTypes) { + Integer bestMatchIndex = null; + int bestPriority = MatchPriority.MISMATCH.priority; + for (int i = 0; i < candidates.length; i++) { + Class[] candidate = candidates[i]; + int priority = resolvePriority(candidate, argTypes); + if (priority > bestPriority) { + bestPriority = priority; + bestMatchIndex = i; + } + } + return bestMatchIndex; + } + + private static int resolveArgPriority(Class paramType, Class argType) { + if (paramType == argType) { + return MatchPriority.EQUAL.priority; + } + if (CacheUtil.isFunctionInterface(paramType) && QLambda.class.isAssignableFrom(argType)) { + return MatchPriority.LAMBDA.priority; + } + + Class primitiveArgCls = argType.isPrimitive() ? argType : BasicUtil.transToPrimitive(argType); + Class primitiveParamCls = paramType.isPrimitive() ? paramType : BasicUtil.transToPrimitive(paramType); + if (primitiveArgCls != null && primitiveArgCls == primitiveParamCls) { + return MatchPriority.UNBOX.priority; + } + + Integer paramNumLevel = BasicUtil.numberPromoteLevel(paramType); + Integer argNumLevel = BasicUtil.numberPromoteLevel(argType); + if (paramNumLevel != null && argNumLevel != null) { + return paramNumLevel >= argNumLevel ? MatchPriority.NUMBER_PROMOTION.priority + argNumLevel - paramNumLevel + : MatchPriority.NUMBER_DEMOTION.priority; + } + + // Handle primitive to Object boxing conversion + if (argType.isPrimitive() && paramType == Object.class) { + return MatchPriority.EXTEND.priority; + } + + if (argType == Nothing.class || paramType.isAssignableFrom(argType)) { + return MatchPriority.EXTEND.priority; + } + return MatchPriority.MISMATCH.priority; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/MetaClass.java b/src/main/java/com/alibaba/qlexpress4/runtime/MetaClass.java new file mode 100644 index 0000000..9adab77 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/MetaClass.java @@ -0,0 +1,34 @@ +package com.alibaba.qlexpress4.runtime; + +import java.util.Objects; + +/** + * Author: DQinYuan + */ +public class MetaClass { + + private final Class clz; + + public MetaClass(Class clz) { + this.clz = clz; + } + + public Class getClz() { + return clz; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + MetaClass metaClass = (MetaClass)o; + return clz.equals(metaClass.clz); + } + + @Override + public int hashCode() { + return Objects.hash(clz); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/Nothing.java b/src/main/java/com/alibaba/qlexpress4/runtime/Nothing.java new file mode 100644 index 0000000..dd7e6cf --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/Nothing.java @@ -0,0 +1,12 @@ +package com.alibaba.qlexpress4.runtime; + +/** + * subclass of any object + */ +public class Nothing { + + private Nothing() { + + } + +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/Parameters.java b/src/main/java/com/alibaba/qlexpress4/runtime/Parameters.java new file mode 100644 index 0000000..a52ff0b --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/Parameters.java @@ -0,0 +1,26 @@ +package com.alibaba.qlexpress4.runtime; + +/** + * Author: DQinYuan + */ +public interface Parameters { + + default Object getValue(int i) { + Value boxedValue = get(i); + return boxedValue == null ? null : boxedValue.get(); + } + + /** + * get parameters in i position + * + * @param i index + * @return value in index, null if exceed parameters' length + */ + Value get(int i); + + /** + * parameters size + * @return size + */ + int size(); +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/QContext.java b/src/main/java/com/alibaba/qlexpress4/runtime/QContext.java new file mode 100644 index 0000000..3091659 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/QContext.java @@ -0,0 +1,13 @@ +package com.alibaba.qlexpress4.runtime; + +import com.alibaba.qlexpress4.runtime.scope.QScope; + +/** + * Author: DQinYuan + */ +public interface QContext extends QScope, QRuntime { + + QScope getCurrentScope(); + + void closeScope(); +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/QLambda.java b/src/main/java/com/alibaba/qlexpress4/runtime/QLambda.java new file mode 100644 index 0000000..f410a83 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/QLambda.java @@ -0,0 +1,84 @@ +package com.alibaba.qlexpress4.runtime; + +import com.alibaba.qlexpress4.runtime.function.CustomFunction; + +import java.util.Collections; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; + +public interface QLambda + extends Runnable, Supplier, Consumer, Predicate, Function { + + /** + * @param params params of lambda + * @return result of lambda + * @throws Throwable {@link com.alibaba.qlexpress4.exception.UserDefineException} for custom error message + */ + QResult call(Object... params) + throws Throwable; + + /** + * Get functions defined inside this lambda when invoked with parameters. + * + * @param params parameters passed to the lambda + * @return function table defined by the lambda (empty by default) + * @throws Throwable if user code throws an exception while collecting functions + */ + default Map getFunctionDefined(Object... params) + throws Throwable { + return Collections.emptyMap(); + } + + @Override + default Object get() { + try { + return call().getResult().get(); + } + catch (Throwable t) { + throw t instanceof RuntimeException ? (RuntimeException)t : new RuntimeException(t); + } + } + + @Override + default void accept(Object o) { + try { + call(o); + } + catch (Throwable t) { + throw t instanceof RuntimeException ? (RuntimeException)t : new RuntimeException(t); + } + } + + @Override + default void run() { + try { + call(); + } + catch (Throwable t) { + throw t instanceof RuntimeException ? (RuntimeException)t : new RuntimeException(t); + } + } + + @Override + default boolean test(Object o) { + try { + return (boolean)call(o).getResult().get(); + } + catch (Throwable t) { + throw t instanceof RuntimeException ? (RuntimeException)t : new RuntimeException(t); + } + } + + @Override + default Object apply(Object o) { + try { + return call(o).getResult().get(); + } + catch (Throwable t) { + throw t instanceof RuntimeException ? (RuntimeException)t : new RuntimeException(t); + } + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/QLambdaDefinition.java b/src/main/java/com/alibaba/qlexpress4/runtime/QLambdaDefinition.java new file mode 100644 index 0000000..cded31b --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/QLambdaDefinition.java @@ -0,0 +1,17 @@ +package com.alibaba.qlexpress4.runtime; + +import com.alibaba.qlexpress4.QLOptions; + +import java.util.function.Consumer; + +/** + * Author: DQinYuan + */ +public interface QLambdaDefinition { + + QLambda toLambda(QContext qContext, QLOptions qlOptions, boolean newEnv); + + void println(int depth, Consumer debug); + + String getName(); +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/QLambdaDefinitionEmpty.java b/src/main/java/com/alibaba/qlexpress4/runtime/QLambdaDefinitionEmpty.java new file mode 100644 index 0000000..9c985c9 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/QLambdaDefinitionEmpty.java @@ -0,0 +1,29 @@ +package com.alibaba.qlexpress4.runtime; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.utils.PrintlnUtils; + +import java.util.function.Consumer; + +/** + * Author: DQinYuan + */ +public class QLambdaDefinitionEmpty implements QLambdaDefinition { + + public static QLambdaDefinition INSTANCE = new QLambdaDefinitionEmpty(); + + @Override + public QLambda toLambda(QContext qContext, QLOptions qlOptions, boolean newEnv) { + return QLambdaEmpty.INSTANCE; + } + + @Override + public void println(int depth, Consumer debug) { + PrintlnUtils.printlnByCurDepth(depth, getName(), debug); + } + + @Override + public String getName() { + return "EmptyLambdaDefinition"; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/QLambdaDefinitionInner.java b/src/main/java/com/alibaba/qlexpress4/runtime/QLambdaDefinitionInner.java new file mode 100644 index 0000000..425beba --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/QLambdaDefinitionInner.java @@ -0,0 +1,89 @@ +package com.alibaba.qlexpress4.runtime; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.runtime.instruction.CloseScopeInstruction; +import com.alibaba.qlexpress4.runtime.instruction.NewScopeInstruction; +import com.alibaba.qlexpress4.runtime.instruction.QLInstruction; + +import java.util.List; +import java.util.function.Consumer; + +/** + * Author: DQinYuan + */ +public class QLambdaDefinitionInner implements QLambdaDefinition { + + /** + * function name + */ + private final String name; + + private final QLInstruction[] instructions; + + private final List paramsType; + + private final int maxStackSize; + + public QLambdaDefinitionInner(String name, List instructions, List paramsType, + int maxStackSize) { + this.name = name; + this.instructions = instructions.toArray(new QLInstruction[0]); + this.paramsType = paramsType; + this.maxStackSize = maxStackSize; + } + + public QLambdaDefinitionInner(String name, QLInstruction[] instructions, List paramsType, int maxStackSize) { + this.name = name; + this.instructions = instructions; + this.paramsType = paramsType; + this.maxStackSize = maxStackSize; + } + + @Override + public String getName() { + return name; + } + + public QLInstruction[] getInstructions() { + return instructions; + } + + public List getParamsType() { + return paramsType; + } + + public int getMaxStackSize() { + return maxStackSize; + } + + @Override + public QLambda toLambda(QContext qContext, QLOptions qlOptions, boolean newEnv) { + return new QLambdaInner(this, new DelegateQContext(qContext, qContext.getCurrentScope()), qlOptions, newEnv); + } + + @Override + public void println(int depth, Consumer debug) { + for (int i = 0; i < instructions.length; i++) { + instructions[i].println(i, depth, debug); + } + } + + public static class Param { + private final String name; + + private final Class clazz; + + public Param(String name, Class clazz) { + this.name = name; + this.clazz = clazz; + } + + public String getName() { + return name; + } + + public Class getClazz() { + return clazz; + } + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/QLambdaEmpty.java b/src/main/java/com/alibaba/qlexpress4/runtime/QLambdaEmpty.java new file mode 100644 index 0000000..0af9291 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/QLambdaEmpty.java @@ -0,0 +1,21 @@ +package com.alibaba.qlexpress4.runtime; + +import com.alibaba.qlexpress4.runtime.function.CustomFunction; + +import java.util.Collections; +import java.util.Map; + +/** + * Author: DQinYuan + */ +public class QLambdaEmpty implements QLambda { + + public static QLambda INSTANCE = new QLambdaEmpty(); + + @Override + public QResult call(Object... params) + throws Throwable { + return new QResult(Value.NULL_VALUE, QResult.ResultType.RETURN); + } + +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/QLambdaInner.java b/src/main/java/com/alibaba/qlexpress4/runtime/QLambdaInner.java new file mode 100644 index 0000000..b2caabd --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/QLambdaInner.java @@ -0,0 +1,99 @@ +package com.alibaba.qlexpress4.runtime; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.UserDefineException; +import com.alibaba.qlexpress4.runtime.data.AssignableDataValue; +import com.alibaba.qlexpress4.runtime.data.convert.ObjTypeConvertor; +import com.alibaba.qlexpress4.runtime.function.CustomFunction; +import com.alibaba.qlexpress4.runtime.instruction.QLInstruction; +import com.alibaba.qlexpress4.runtime.scope.QvmBlockScope; + +import java.text.MessageFormat; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Author: DQinYuan + */ +public class QLambdaInner implements QLambda { + + private final QLambdaDefinitionInner lambdaDefinition; + + private final QContext qContext; + + private final QLOptions qlOptions; + + private final boolean newEnv; + + public QLambdaInner(QLambdaDefinitionInner lambdaDefinition, QContext qContext, QLOptions qlOptions, + boolean newEnv) { + this.lambdaDefinition = lambdaDefinition; + this.qContext = qContext; + this.qlOptions = qlOptions; + this.newEnv = newEnv; + } + + public QResult call(Object... params) + throws Throwable { + return callInner(newEnv ? inheritScope(params) : qContext); + } + + @Override + public Map getFunctionDefined(Object... params) + throws Throwable { + QContext newRuntime = newEnv ? inheritScope(params) : qContext; + callInner(newRuntime); + return newRuntime.getFunctionTable(); + } + + private QResult callInner(QContext runtime) { + QLInstruction[] instructions = lambdaDefinition.getInstructions(); + for (int i = 0; i < instructions.length; i++) { + QResult qResult = instructions[i].execute(runtime, qlOptions); + switch (qResult.getResultType()) { + case JUMP: + i += (int)qResult.getResult().get(); + continue; + case RETURN: + case BREAK: + case CONTINUE: + return qResult; + } + } + + return QResult.NEXT_INSTRUCTION; + } + + private QContext inheritScope(Object[] params) + throws UserDefineException { + Map initSymbolTable = new HashMap<>(params.length); + List paramsDefinition = lambdaDefinition.getParamsType(); + for (int i = 0; i < Math.min(params.length, paramsDefinition.size()); i++) { + QLambdaDefinitionInner.Param paramDefinition = paramsDefinition.get(i); + Object originParamI = params[i]; + Class targetCls = paramDefinition.getClazz(); + ObjTypeConvertor.QConverted qlConvertResult = ObjTypeConvertor.cast(originParamI, targetCls); + if (!qlConvertResult.isConvertible()) { + throw new UserDefineException(UserDefineException.ExceptionType.INVALID_ARGUMENT, + MessageFormat.format( + "invalid argument at index {0} (start from 0), required type {1}, but {2} provided", + i, + targetCls.getName(), + originParamI == null ? "null" : originParamI.getClass().getName())); + } + initSymbolTable.put(paramDefinition.getName(), + new AssignableDataValue(paramDefinition.getName(), qlConvertResult.getConverted(), targetCls)); + } + // null for rest params + for (int i = params.length; i < paramsDefinition.size(); i++) { + QLambdaDefinitionInner.Param paramDefinition = paramsDefinition.get(i); + initSymbolTable.put(paramDefinition.getName(), + new AssignableDataValue(paramDefinition.getName(), null, paramDefinition.getClazz())); + } + QvmBlockScope newScope = + new QvmBlockScope(qContext, initSymbolTable, lambdaDefinition.getMaxStackSize(), ExceptionTable.EMPTY); + return new DelegateQContext(qContext, newScope); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/QLambdaTrace.java b/src/main/java/com/alibaba/qlexpress4/runtime/QLambdaTrace.java new file mode 100644 index 0000000..75339e2 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/QLambdaTrace.java @@ -0,0 +1,23 @@ +package com.alibaba.qlexpress4.runtime; + +import com.alibaba.qlexpress4.runtime.trace.QTraces; + +public class QLambdaTrace { + + private final QLambda qLambda; + + private final QTraces traces; + + public QLambdaTrace(QLambda qLambda, QTraces traces) { + this.qLambda = qLambda; + this.traces = traces; + } + + public QLambda getqLambda() { + return qLambda; + } + + public QTraces getTraces() { + return traces; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/QResult.java b/src/main/java/com/alibaba/qlexpress4/runtime/QResult.java new file mode 100644 index 0000000..8e1c453 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/QResult.java @@ -0,0 +1,44 @@ +package com.alibaba.qlexpress4.runtime; + +/** + * Author: DQinYuan + */ +public class QResult { + + public static final QResult LOOP_BREAK_RESULT = new QResult(Value.NULL_VALUE, ResultType.BREAK); + + public static final QResult LOOP_CONTINUE_RESULT = new QResult(Value.NULL_VALUE, ResultType.CONTINUE); + + public static final QResult NEXT_INSTRUCTION = new QResult(Value.NULL_VALUE, ResultType.NEXT_INSTRUCTION); + + public enum ResultType { + // break + BREAK, + // without return, different with return null + CONTINUE, + // jump to other instruction position. + // in this case, result is Value of int, which is the position to be jumped + JUMP, + // return from function/lambda/script + RETURN, + // execute next instruction + NEXT_INSTRUCTION + } + + public QResult(Value result, ResultType rType) { + this.result = result; + this.resultType = rType; + } + + private final Value result; + + private final ResultType resultType; + + public Value getResult() { + return result; + } + + public ResultType getResultType() { + return resultType; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/QRuntime.java b/src/main/java/com/alibaba/qlexpress4/runtime/QRuntime.java new file mode 100644 index 0000000..c5a304b --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/QRuntime.java @@ -0,0 +1,23 @@ +package com.alibaba.qlexpress4.runtime; + +import com.alibaba.qlexpress4.runtime.trace.QTraces; + +import java.util.Map; + +/** + * Author: DQinYuan + */ +public interface QRuntime { + + /** + * get script start time + * @return start time + */ + long scriptStartTimeStamp(); + + Map attachment(); + + ReflectLoader getReflectLoader(); + + QTraces getTraces(); +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/QvmGlobalScope.java b/src/main/java/com/alibaba/qlexpress4/runtime/QvmGlobalScope.java new file mode 100644 index 0000000..ec1caef --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/QvmGlobalScope.java @@ -0,0 +1,105 @@ +package com.alibaba.qlexpress4.runtime; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.runtime.context.ExpressContext; +import com.alibaba.qlexpress4.runtime.data.AssignableDataValue; +import com.alibaba.qlexpress4.runtime.function.CustomFunction; +import com.alibaba.qlexpress4.runtime.scope.QScope; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * global scope + * + * Author: DQinYuan + */ +public class QvmGlobalScope implements QScope { + + private final ExpressContext externalVariable; + + private final Map newVariables; + + private final Map externalFunction; + + private final QLOptions qlOptions; + + public QvmGlobalScope(ExpressContext externalVariable, Map externalFunction, + QLOptions qlOptions) { + this.externalVariable = externalVariable; + this.newVariables = new HashMap<>(); + this.externalFunction = externalFunction; + this.qlOptions = qlOptions; + } + + @Override + public Value getSymbol(String varName) { + LeftValue newVariable = newVariables.get(varName); + if (newVariable != null) { + return newVariable; + } + Value externalValue = externalVariable.get(qlOptions.getAttachments(), varName); + if (externalValue != null && qlOptions.isPolluteUserContext()) { + return externalValue; + } + newVariable = new AssignableDataValue(varName, externalValue == null ? null : externalValue.get()); + newVariables.put(varName, newVariable); + return newVariable; + } + + @Override + public Object getSymbolValue(String varName) { + return QScope.super.getSymbolValue(varName); + } + + @Override + public void defineLocalSymbol(String varName, Class varClz, Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public void defineFunction(String functionName, CustomFunction function) { + throw new UnsupportedOperationException(); + } + + @Override + public CustomFunction getFunction(String functionName) { + return externalFunction.get(functionName); + } + + @Override + public Map getFunctionTable() { + return externalFunction; + } + + @Override + public void push(Value value) { + throw new UnsupportedOperationException(); + } + + @Override + public Parameters pop(int number) { + throw new UnsupportedOperationException(); + } + + @Override + public Value pop() { + throw new UnsupportedOperationException(); + } + + @Override + public Value peek() { + throw new UnsupportedOperationException(); + } + + @Override + public QScope getParent() { + throw new UnsupportedOperationException(); + } + + @Override + public QScope newScope() { + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/QvmRuntime.java b/src/main/java/com/alibaba/qlexpress4/runtime/QvmRuntime.java new file mode 100644 index 0000000..65cbfc1 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/QvmRuntime.java @@ -0,0 +1,46 @@ +package com.alibaba.qlexpress4.runtime; + +import com.alibaba.qlexpress4.runtime.trace.QTraces; + +import java.util.Map; + +/** + * root runtime with external variable and function + * Author: DQinYuan + */ +public class QvmRuntime implements QRuntime { + + private final QTraces traces; + + private final Map attachments; + + private final ReflectLoader reflectLoader; + + private final long startTime; + + public QvmRuntime(QTraces traces, Map attachments, ReflectLoader reflectLoader, long startTime) { + this.traces = traces; + this.attachments = attachments; + this.reflectLoader = reflectLoader; + this.startTime = startTime; + } + + @Override + public long scriptStartTimeStamp() { + return startTime; + } + + @Override + public Map attachment() { + return attachments; + } + + public ReflectLoader getReflectLoader() { + return reflectLoader; + } + + @Override + public QTraces getTraces() { + return traces; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/ReflectLoader.java b/src/main/java/com/alibaba/qlexpress4/runtime/ReflectLoader.java new file mode 100644 index 0000000..d32012b --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/ReflectLoader.java @@ -0,0 +1,461 @@ +package com.alibaba.qlexpress4.runtime; + +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.exception.QLErrorCodes; +import com.alibaba.qlexpress4.exception.QLRuntimeException; +import com.alibaba.qlexpress4.member.FieldHandler; +import com.alibaba.qlexpress4.member.MethodHandler; +import com.alibaba.qlexpress4.runtime.data.DataValue; +import com.alibaba.qlexpress4.runtime.data.FieldValue; +import com.alibaba.qlexpress4.runtime.data.MapItemValue; +import com.alibaba.qlexpress4.runtime.function.ExtensionFunction; +import com.alibaba.qlexpress4.runtime.function.FilterExtensionFunction; +import com.alibaba.qlexpress4.runtime.function.MapExtensionFunction; +import com.alibaba.qlexpress4.security.QLSecurityStrategy; +import com.alibaba.qlexpress4.security.StrategyIsolation; +import com.alibaba.qlexpress4.utils.BasicUtil; + +import java.io.Serializable; +import java.lang.reflect.*; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +/** + * java reflect util with cache + * Author: DQinYuan + */ +public class ReflectLoader { + + private final QLSecurityStrategy securityStrategy; + + private final boolean allowPrivateAccess; + + private final Map>, Constructor> constructorCache = new ConcurrentHashMap<>(); + + private final Map, FieldReflectCache> fieldCache = new ConcurrentHashMap<>(); + + private final Map staticMethodCache = new ConcurrentHashMap<>(); + + private final Map memberMethodCache = new ConcurrentHashMap<>(); + + /** + * default extension functions + */ + private final List extensionFunctions = + new CopyOnWriteArrayList<>(Arrays.asList(FilterExtensionFunction.INSTANCE, MapExtensionFunction.INSTANCE)); + + public ReflectLoader(QLSecurityStrategy securityStrategy, boolean allowPrivateAccess) { + this.securityStrategy = securityStrategy; + this.allowPrivateAccess = allowPrivateAccess; + } + + public void addExtendFunction(ExtensionFunction extensionFunction) { + extensionFunctions.add(extensionFunction); + } + + public Constructor loadConstructor(Class cls, Class[] paramTypes) { + if (securityStrategy instanceof StrategyIsolation) { + return null; + } + + List> cacheKey = new ArrayList<>(paramTypes.length + 1); + cacheKey.add(cls); + cacheKey.addAll(Arrays.asList(paramTypes)); + + Constructor cachedConstructor = constructorCache.get(cacheKey); + if (cachedConstructor != null) { + return cachedConstructor; + } + + Constructor constructor = securityFilter(MemberResolver.resolveConstructor(cls, paramTypes)); + if (constructor == null) { + return null; + } + constructorCache.put(cacheKey, constructor); + return constructor; + } + + public Value loadField(Object bean, String fieldName, boolean skipSecurity, ErrorReporter errorReporter) { + if (bean.getClass().isArray() && BasicUtil.LENGTH.equals(fieldName)) { + return new DataValue(((Object[])bean).length); + } + else if (bean instanceof List && BasicUtil.LENGTH.equals(fieldName)) { + return new DataValue(((List)bean).size()); + } + else if (bean instanceof Map) { + return new MapItemValue((Map)bean, fieldName); + } + else if (!skipSecurity && securityStrategy instanceof StrategyIsolation) { + return null; + } + else if (bean instanceof MetaClass) { + MetaClass metaClass = (MetaClass)bean; + if (BasicUtil.CLASS.equals(fieldName)) { + return new DataValue(metaClass.getClz()); + } + return loadJavaField(metaClass.getClz(), null, fieldName, skipSecurity, errorReporter); + } + else { + return loadJavaField(bean.getClass(), bean, fieldName, skipSecurity, errorReporter); + } + } + + public IMethod loadMethod(Object bean, String methodName, Class[] argTypes) { + boolean isStaticMethod = bean instanceof MetaClass; + Class clz = isStaticMethod ? ((MetaClass)bean).getClz() : bean.getClass(); + // only support member extension method + if (!isStaticMethod) { + IMethod extendFunction = loadExtendFunction(clz, methodName, argTypes); + if (extendFunction != null) { + return extendFunction; + } + } + + if (securityStrategy instanceof StrategyIsolation) { + return null; + } + + MethodCacheKey cacheKey = new MethodCacheKey(clz, methodName, argTypes); + Map methodCache = isStaticMethod ? staticMethodCache : memberMethodCache; + IMethod cachedMethod = methodCache.get(cacheKey); + if (cachedMethod != null) { + return cachedMethod; + } + + IMethod method = securityFilterIMethod( + MemberResolver.resolveMethod(clz, methodName, argTypes, isStaticMethod, allowPrivateAccess)); + if (method == null) { + return null; + } + + methodCache.put(cacheKey, method); + return method; + } + + private IMethod securityFilterIMethod(IMethod iMethod) { + if (iMethod instanceof JvmIMethod) { + Method filterResult = securityFilter(((JvmIMethod)iMethod).getMethod()); + return filterResult == null ? null : iMethod; + } + else { + return iMethod; + } + } + + private IMethod loadExtendFunction(Class clz, String methodName, Class[] argTypes) { + List assignableExtensionFunctions = extensionFunctions.stream() + .filter(extensionFunction -> extensionFunction.getDeclaringClass().isAssignableFrom(clz) + && methodName.equals(extensionFunction.getName())) + .collect(Collectors.toList()); + if (assignableExtensionFunctions.isEmpty()) { + return null; + } + return MemberResolver.resolveMethod(assignableExtensionFunctions, argTypes); + } + + private Value loadJavaField(Class cls, Object bean, String fieldName, boolean skipSecurity, + ErrorReporter errorReporter) { + FieldReflectCache fieldReflectCache = loadFieldReflectCache(cls, fieldName, skipSecurity); + if (fieldReflectCache == null) { + return null; + } + Supplier getterOp = fieldReflectCache.getterSupplier.apply(errorReporter, bean); + if (fieldReflectCache.setterSupplier == null) { + return new DataValue(getterOp.get()); + } + Consumer setterOp = fieldReflectCache.setterSupplier.apply(errorReporter, bean); + return new FieldValue(getterOp, setterOp, fieldReflectCache.defType); + } + + private FieldReflectCache loadFieldReflectCache(Class cls, String fieldName, boolean skipSecurity) { + List cacheKey = Arrays.asList(cls, fieldName); + FieldReflectCache cachedField = fieldCache.get(cacheKey); + if (cachedField != null) { + return cachedField; + } + + FieldReflectCache fieldReflect = loadJavaFieldInner(cls, fieldName, skipSecurity); + if (fieldReflect != null) { + fieldCache.put(cacheKey, fieldReflect); + } + return fieldReflect; + } + + private FieldReflectCache loadJavaFieldInner(Class cls, String fieldName, boolean skipSecurity) { + String preHandledName = FieldHandler.Preferred.preHandleAlias(cls, fieldName); + Method getMethod = skipSecurity ? MethodHandler.getGetter(cls, preHandledName) + : securityFilter(MethodHandler.getGetter(cls, preHandledName)); + Field field = skipSecurity ? FieldHandler.Preferred.gatherFieldRecursive(cls, preHandledName) + : securityFilter(FieldHandler.Preferred.gatherFieldRecursive(cls, preHandledName)); + BiFunction> getterSupplier = fieldGetter(getMethod, field); + if (getterSupplier == null) { + return null; + } + Method setMethod = securityFilter(MethodHandler.getSetter(cls, preHandledName)); + BiFunction> setterSupplier = fieldSetter(setMethod, field); + return new FieldReflectCache(getterSupplier, setterSupplier, fieldDefCls(setMethod, field)); + } + + private T securityFilter(T member) { + return member == null ? null : (securityStrategy.check(member) ? member : null); + } + + private Class fieldDefCls(Method setMethod, Field field) { + return setMethod != null ? setMethod.getParameterTypes()[0] : field != null ? field.getType() : Object.class; + } + + private BiFunction> fieldSetter(Method setMethod, Field field) { + if (setMethod != null) { + if (BasicUtil.isPublic(setMethod)) { + return setMethodAccessible(setMethod); + } + if (allowPrivateAccess) { + return setMethodUnAccessible(setMethod); + } + } + if (field != null) { + if (BasicUtil.isPublic(field)) { + return setFieldAccessible(field); + } + if (allowPrivateAccess) { + return setFieldUnAccessible(field); + } + } + return null; + } + + private BiFunction> fieldGetter(Method getMethod, Field field) { + if (getMethod != null) { + if (BasicUtil.isPublic(getMethod)) { + return getMethodAccessible(getMethod); + } + if (allowPrivateAccess) { + return getMethodUnAccessible(getMethod); + } + } + if (field != null) { + if (BasicUtil.isPublic(field)) { + return getFieldAccessible(field); + } + if (allowPrivateAccess) { + return getFieldUnAccessible(field); + } + } + return null; + } + + private BiFunction> setMethodAccessible(Method setMethod) { + return (errorReporter, bean) -> newValue -> { + try { + setMethod.invoke(bean, newValue); + } + catch (Exception e) { + throw unwrapMethodInvokeEx(errorReporter, setMethod.getName(), e); + } + }; + } + + private BiFunction> setMethodUnAccessible(Method setMethod) { + return (errorReporter, bean) -> newValue -> { + try { + setMethod.setAccessible(true); + setMethod.invoke(bean, newValue); + } + catch (Exception e) { + throw unwrapMethodInvokeEx(errorReporter, setMethod.getName(), e); + } + }; + } + + private BiFunction> setFieldAccessible(Field field) { + return (errorReporter, bean) -> newValue -> { + try { + field.set(bean, newValue); + } + catch (Exception e) { + throw errorReporter.report(e, + QLErrorCodes.SET_FIELD_UNKNOWN_ERROR.name(), + String.format(QLErrorCodes.SET_FIELD_UNKNOWN_ERROR.getErrorMsg(), field.getName())); + } + }; + } + + private BiFunction> setFieldUnAccessible(Field field) { + return (errorReporter, bean) -> newValue -> { + try { + field.setAccessible(true); + field.set(bean, newValue); + } + catch (Exception e) { + throw errorReporter.report(e, + QLErrorCodes.SET_FIELD_UNKNOWN_ERROR.name(), + String.format(QLErrorCodes.SET_FIELD_UNKNOWN_ERROR.getErrorMsg(), field.getName())); + } + }; + } + + private BiFunction> getMethodAccessible(Method getMethod) { + return (errorReporter, bean) -> () -> { + try { + return getMethod.invoke(bean); + } + catch (Exception e) { + throw unwrapMethodInvokeEx(errorReporter, getMethod.getName(), e); + } + }; + } + + private BiFunction> getMethodUnAccessible(Method getMethod) { + return (errorReporter, bean) -> () -> { + try { + getMethod.setAccessible(true); + return getMethod.invoke(bean); + } + catch (Exception e) { + throw unwrapMethodInvokeEx(errorReporter, getMethod.getName(), e); + } + }; + } + + private BiFunction> getFieldAccessible(Field field) { + return (errorReporter, bean) -> () -> { + try { + return field.get(bean); + } + catch (Exception e) { + throw errorReporter.report(e, + QLErrorCodes.GET_FIELD_UNKNOWN_ERROR.name(), + String.format(QLErrorCodes.GET_FIELD_UNKNOWN_ERROR.getErrorMsg(), field.getName())); + } + }; + } + + private BiFunction> getFieldUnAccessible(Field field) { + return (errorReporter, bean) -> () -> { + try { + field.setAccessible(true); + return field.get(bean); + } + catch (Exception e) { + throw errorReporter.report(e, + QLErrorCodes.GET_FIELD_UNKNOWN_ERROR.name(), + String.format(QLErrorCodes.GET_FIELD_UNKNOWN_ERROR.getErrorMsg(), field.getName())); + } + }; + } + + public static QLRuntimeException unwrapMethodInvokeEx(ErrorReporter errorReporter, String methodName, + Exception ex) { + if (ex instanceof IllegalArgumentException) { + return errorReporter.reportFormat(QLErrorCodes.INVOKE_METHOD_WITH_WRONG_ARGUMENTS.name(), + String.format(QLErrorCodes.INVOKE_METHOD_WITH_WRONG_ARGUMENTS.getErrorMsg(), methodName)); + } + else if (ex instanceof InvocationTargetException) { + return errorReporter.report(((InvocationTargetException)ex).getTargetException(), + QLErrorCodes.INVOKE_METHOD_INNER_ERROR.name(), + String.format(QLErrorCodes.INVOKE_METHOD_INNER_ERROR.getErrorMsg(), methodName)); + } + else { + return errorReporter.report(ex, + QLErrorCodes.INVOKE_METHOD_UNKNOWN_ERROR.name(), + String.format(QLErrorCodes.INVOKE_METHOD_UNKNOWN_ERROR.getErrorMsg(), methodName)); + } + } + + private static class FieldReflectCache { + private final BiFunction> getterSupplier; + + private final BiFunction> setterSupplier; + + private final Class defType; + + private FieldReflectCache(BiFunction> getterSupplier, + BiFunction> setterSupplier, Class defType) { + this.getterSupplier = getterSupplier; + this.setterSupplier = setterSupplier; + this.defType = defType; + } + } + + private static class ExtensionMapKey { + private final Class cls; + + private final String methodName; + + public ExtensionMapKey(Class cls, String methodName) { + this.cls = cls; + this.methodName = methodName; + } + + public Class getCls() { + return cls; + } + + public String getMethodName() { + return methodName; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + ExtensionMapKey that = (ExtensionMapKey)o; + return Objects.equals(cls, that.cls) && Objects.equals(methodName, that.methodName); + } + + @Override + public int hashCode() { + return Objects.hash(cls, methodName); + } + } + + private static class MethodCacheKey { + private final Class cls; + + private final String methodName; + + private final Class[] argTypes; + + public MethodCacheKey(Class cls, String methodName, Class[] argTypes) { + this.cls = cls; + this.methodName = methodName; + this.argTypes = argTypes; + } + + public Class getCls() { + return cls; + } + + public String getMethodName() { + return methodName; + } + + public Class[] getArgTypes() { + return argTypes; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + MethodCacheKey that = (MethodCacheKey)o; + return cls.equals(that.cls) && methodName.equals(that.methodName) && Arrays.equals(argTypes, that.argTypes); + } + + @Override + public int hashCode() { + int result = Objects.hash(cls, methodName); + result = 31 * result + Arrays.hashCode(argTypes); + return result; + } + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/Value.java b/src/main/java/com/alibaba/qlexpress4/runtime/Value.java new file mode 100644 index 0000000..1b299f8 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/Value.java @@ -0,0 +1,29 @@ +package com.alibaba.qlexpress4.runtime; + +import com.alibaba.qlexpress4.runtime.data.DataValue; + +/** + * Author: DQinYuan + */ +public interface Value { + Value NULL_VALUE = new DataValue((Object)null); + + Object get(); + + default Class getType() { + Object value = get(); + if (value == null) { + return Nothing.class; + } + + return value.getClass(); + } + + default String getTypeName() { + Class type = getType(); + if (type == null) { + return null; + } + return type.getName(); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/context/DynamicVariableContext.java b/src/main/java/com/alibaba/qlexpress4/runtime/context/DynamicVariableContext.java new file mode 100644 index 0000000..999caae --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/context/DynamicVariableContext.java @@ -0,0 +1,51 @@ +package com.alibaba.qlexpress4.runtime.context; + +import com.alibaba.qlexpress4.Express4Runner; +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.QLResult; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.data.DataValue; +import com.alibaba.qlexpress4.runtime.data.MapItemValue; + +import java.util.HashMap; +import java.util.Map; + +public class DynamicVariableContext implements ExpressContext { + + private final Express4Runner runner; + + private final Map staticContext; + + private final QLOptions qlOptions; + + private final Map dynamicContext; + + public DynamicVariableContext(Express4Runner runner, Map staticContext, QLOptions qlOptions, + Map dynamicContext) { + this.runner = runner; + this.staticContext = staticContext; + this.qlOptions = qlOptions; + this.dynamicContext = dynamicContext; + } + + public DynamicVariableContext(Express4Runner runner, Map staticContext, QLOptions qlOptions) { + this.runner = runner; + this.staticContext = staticContext; + this.qlOptions = qlOptions; + this.dynamicContext = new HashMap<>(); + } + + public void put(String name, String valueExpression) { + dynamicContext.put(name, valueExpression); + } + + @Override + public Value get(Map attachments, String variableName) { + String dynamicScript = dynamicContext.get(variableName); + if (dynamicScript != null) { + QLResult result = runner.execute(dynamicScript, this, qlOptions); + return new DataValue(result.getResult()); + } + return new MapItemValue(staticContext, variableName); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/context/EmptyContext.java b/src/main/java/com/alibaba/qlexpress4/runtime/context/EmptyContext.java new file mode 100644 index 0000000..08cc24c --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/context/EmptyContext.java @@ -0,0 +1,12 @@ +package com.alibaba.qlexpress4.runtime.context; + +import com.alibaba.qlexpress4.runtime.Value; + +import java.util.Map; + +public class EmptyContext implements ExpressContext { + @Override + public Value get(Map attachments, String variableName) { + return Value.NULL_VALUE; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/context/ExpressContext.java b/src/main/java/com/alibaba/qlexpress4/runtime/context/ExpressContext.java new file mode 100644 index 0000000..af77f2d --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/context/ExpressContext.java @@ -0,0 +1,16 @@ +package com.alibaba.qlexpress4.runtime.context; + +import com.alibaba.qlexpress4.runtime.Value; + +import java.util.Map; + +/** + * Author: DQinYuan + */ +public interface ExpressContext { + + ExpressContext EMPTY_CONTEXT = new EmptyContext(); + + Value get(Map attachments, String variableName); + +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/context/MapExpressContext.java b/src/main/java/com/alibaba/qlexpress4/runtime/context/MapExpressContext.java new file mode 100644 index 0000000..2a6b022 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/context/MapExpressContext.java @@ -0,0 +1,23 @@ +package com.alibaba.qlexpress4.runtime.context; + +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.data.MapItemValue; + +import java.util.Map; + +/** + * Author: DQinYuan + */ +public class MapExpressContext implements ExpressContext { + + private final Map source; + + public MapExpressContext(Map source) { + this.source = source; + } + + @Override + public Value get(Map attachments, String variableName) { + return new MapItemValue(source, variableName); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/context/ObjectFieldExpressContext.java b/src/main/java/com/alibaba/qlexpress4/runtime/context/ObjectFieldExpressContext.java new file mode 100644 index 0000000..e9d131c --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/context/ObjectFieldExpressContext.java @@ -0,0 +1,25 @@ +package com.alibaba.qlexpress4.runtime.context; + +import com.alibaba.qlexpress4.Express4Runner; +import com.alibaba.qlexpress4.exception.PureErrReporter; +import com.alibaba.qlexpress4.runtime.ReflectLoader; +import com.alibaba.qlexpress4.runtime.Value; + +import java.util.Map; + +public class ObjectFieldExpressContext implements ExpressContext { + + private final Object object; + + private final Express4Runner express4Runner; + + public ObjectFieldExpressContext(Object object, Express4Runner express4Runner) { + this.object = object; + this.express4Runner = express4Runner; + } + + @Override + public Value get(Map attachments, String variableName) { + return express4Runner.loadField(object, variableName); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/context/QLAliasContext.java b/src/main/java/com/alibaba/qlexpress4/runtime/context/QLAliasContext.java new file mode 100644 index 0000000..fc61a28 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/context/QLAliasContext.java @@ -0,0 +1,32 @@ +package com.alibaba.qlexpress4.runtime.context; + +import com.alibaba.qlexpress4.annotation.QLAlias; +import com.alibaba.qlexpress4.exception.QLException; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.data.MapItemValue; + +import java.util.HashMap; +import java.util.Map; + +public class QLAliasContext implements ExpressContext { + + private final Map context; + + public QLAliasContext(Object... os) { + Map context = new HashMap<>(); + for (Object o : os) { + QLAlias[] qlAliases = o.getClass().getAnnotationsByType(QLAlias.class); + for (QLAlias qlAlias : qlAliases) { + for (String alias : qlAlias.value()) { + context.put(alias, o); + } + } + } + this.context = context; + } + + @Override + public Value get(Map attachments, String variableName) { + return new MapItemValue(context, variableName); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/data/ArrayItemValue.java b/src/main/java/com/alibaba/qlexpress4/runtime/data/ArrayItemValue.java new file mode 100644 index 0000000..9a6b636 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/data/ArrayItemValue.java @@ -0,0 +1,40 @@ +package com.alibaba.qlexpress4.runtime.data; + +import com.alibaba.qlexpress4.runtime.LeftValue; + +import java.lang.reflect.Array; + +/** + * Author: DQinYuan + */ +public class ArrayItemValue implements LeftValue { + + private final Object array; + + private final int index; + + public ArrayItemValue(Object array, int index) { + this.array = array; + this.index = index; + } + + @Override + public void setInner(Object newValue) { + Array.set(array, index, newValue); + } + + @Override + public String getSymbolName() { + return null; + } + + @Override + public Object get() { + return Array.get(array, index); + } + + @Override + public Class getDefinedType() { + return array.getClass().getComponentType(); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/data/AssignableDataValue.java b/src/main/java/com/alibaba/qlexpress4/runtime/data/AssignableDataValue.java new file mode 100644 index 0000000..e9e0cd7 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/data/AssignableDataValue.java @@ -0,0 +1,47 @@ +package com.alibaba.qlexpress4.runtime.data; + +import com.alibaba.qlexpress4.runtime.LeftValue; + +/** + * Author: DQinYuan + */ +public class AssignableDataValue implements LeftValue { + + private String symbolName; + + private Object value; + + private final Class defineType; + + public AssignableDataValue(String symbolName, Object value) { + this.symbolName = symbolName; + this.value = value; + this.defineType = null; + } + + public AssignableDataValue(String symbolName, Object value, Class defineType) { + this.symbolName = symbolName; + this.value = value; + this.defineType = defineType; + } + + @Override + public void setInner(Object newValue) { + this.value = newValue; + } + + @Override + public Object get() { + return value; + } + + @Override + public Class getDefinedType() { + return defineType; + } + + @Override + public String getSymbolName() { + return symbolName; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/data/DataValue.java b/src/main/java/com/alibaba/qlexpress4/runtime/data/DataValue.java new file mode 100644 index 0000000..60c8a1d --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/data/DataValue.java @@ -0,0 +1,24 @@ +package com.alibaba.qlexpress4.runtime.data; + +import com.alibaba.qlexpress4.runtime.Value; + +/** + * Author: TaoKan + */ +public class DataValue implements Value { + + private final Object value; + + public DataValue(Object value) { + this.value = value; + } + + public DataValue(Value value) { + this.value = value.get(); + } + + @Override + public Object get() { + return this.value; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/data/FieldValue.java b/src/main/java/com/alibaba/qlexpress4/runtime/data/FieldValue.java new file mode 100644 index 0000000..8f623da --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/data/FieldValue.java @@ -0,0 +1,43 @@ +package com.alibaba.qlexpress4.runtime.data; + +import com.alibaba.qlexpress4.runtime.LeftValue; + +import java.util.function.Consumer; +import java.util.function.Supplier; + +/** + * Author: TaoKan + */ +public class FieldValue implements LeftValue { + private final Supplier getOp; + + private final Consumer setOp; + + private final Class defineType; + + public FieldValue(Supplier getOp, Consumer setOp, Class defineType) { + this.getOp = getOp; + this.setOp = setOp; + this.defineType = defineType; + } + + @Override + public void setInner(Object newValue) { + setOp.accept(newValue); + } + + @Override + public String getSymbolName() { + return null; + } + + @Override + public Object get() { + return getOp.get(); + } + + @Override + public Class getDefinedType() { + return this.defineType; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/data/ListItemValue.java b/src/main/java/com/alibaba/qlexpress4/runtime/data/ListItemValue.java new file mode 100644 index 0000000..6780f94 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/data/ListItemValue.java @@ -0,0 +1,40 @@ +package com.alibaba.qlexpress4.runtime.data; + +import com.alibaba.qlexpress4.runtime.LeftValue; + +import java.util.List; + +/** + * Author: DQinYuan + */ +public class ListItemValue implements LeftValue { + + private final List list; + + private final int index; + + public ListItemValue(List list, int index) { + this.list = list; + this.index = index; + } + + @Override + public void setInner(Object newValue) { + list.set(index, newValue); + } + + @Override + public String getSymbolName() { + return null; + } + + @Override + public Object get() { + return list.get(index); + } + + @Override + public Class getDefinedType() { + return Object.class; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/data/MapItemValue.java b/src/main/java/com/alibaba/qlexpress4/runtime/data/MapItemValue.java new file mode 100644 index 0000000..946316b --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/data/MapItemValue.java @@ -0,0 +1,50 @@ +package com.alibaba.qlexpress4.runtime.data; + +import com.alibaba.qlexpress4.runtime.LeftValue; + +import java.util.Map; + +/** + * Author: DQinYuan + */ +@SuppressWarnings("all") +public class MapItemValue implements LeftValue { + + private final String symbolName; + + private final Map map; + + private final Object key; + + public MapItemValue(Map map, Object key) { + this.symbolName = null; + this.map = map; + this.key = key; + } + + public MapItemValue(String symbolName, Map map, Object key) { + this.symbolName = symbolName; + this.map = map; + this.key = key; + } + + @Override + public void setInner(Object newValue) { + map.put(key, newValue); + } + + @Override + public String getSymbolName() { + return symbolName; + } + + @Override + public Object get() { + return map.get(key); + } + + @Override + public Class getDefinedType() { + return null; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/data/convert/ObjTypeConvertor.java b/src/main/java/com/alibaba/qlexpress4/runtime/data/convert/ObjTypeConvertor.java new file mode 100644 index 0000000..eecb83c --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/data/convert/ObjTypeConvertor.java @@ -0,0 +1,163 @@ +package com.alibaba.qlexpress4.runtime.data.convert; + +import com.alibaba.qlexpress4.proxy.QLambdaInvocationHandler; +import com.alibaba.qlexpress4.runtime.Nothing; +import com.alibaba.qlexpress4.runtime.QLambda; +import com.alibaba.qlexpress4.runtime.operator.number.NumberMath; +import com.alibaba.qlexpress4.utils.CacheUtil; + +import java.lang.reflect.Proxy; +import java.math.BigDecimal; +import java.math.BigInteger; + +/** + * Author: TaoKan + */ +public class ObjTypeConvertor { + + public static QConverted cast(Object value, Class type) { + // no need to convert + if (noNeedConvert(value, type)) { + return converted(value); + } + if (type == Character.class || type == char.class) { + return castChar(value); + } + if (CacheUtil.isFunctionInterface(type)) { + return castFunctionInter(value, type); + } + // unboxed boxed + if (type == boolean.class || type == Boolean.class) { + if (value instanceof Boolean) { + return converted(value); + } + return unConvertible(); + } + if (type == byte.class || type == Byte.class) { + if (value instanceof Byte) { + return converted(value); + } + if (value instanceof Number) { + return converted(((Number)value).byteValue()); + } + return unConvertible(); + } + if (type == short.class || type == Short.class) { + if (value instanceof Short) { + return converted(value); + } + if (value instanceof Number) { + return converted(((Number)value).shortValue()); + } + return unConvertible(); + } + if (type == int.class || type == Integer.class) { + if (value instanceof Integer) { + return converted(value); + } + if (value instanceof Number) { + return converted(((Number)value).intValue()); + } + return unConvertible(); + } + if (type == long.class || type == Long.class) { + if (value instanceof Long) { + return converted(value); + } + if (value instanceof Number) { + return converted(((Number)value).longValue()); + } + return unConvertible(); + } + if (type == float.class || type == Float.class) { + if (value instanceof Float) { + return converted(value); + } + if (value instanceof Number) { + return converted(((Number)value).floatValue()); + } + return unConvertible(); + } + if (type == double.class || type == Double.class) { + if (value instanceof Double) { + return converted(value); + } + if (value instanceof Number) { + return converted(((Number)value).doubleValue()); + } + return unConvertible(); + } + if (type == BigInteger.class) { + if (value instanceof Number) { + return converted(NumberMath.toBigInteger((Number)value)); + } + return unConvertible(); + } + if (type == BigDecimal.class) { + if (value instanceof Number) { + return converted(NumberMath.toBigDecimal((Number)value)); + } + return unConvertible(); + } + return unConvertible(); + } + + private static QConverted castFunctionInter(Object value, Class functionInter) { + if (value instanceof QLambda) { + return converted(Proxy.newProxyInstance(functionInter.getClassLoader(), + new Class[] {functionInter}, + new QLambdaInvocationHandler((QLambda)value))); + } + return unConvertible(); + } + + private static QConverted castChar(Object value) { + if (value instanceof Character) { + return converted(value); + } + if (value instanceof Number) { + return converted((char)((Number)value).intValue()); + } + if (value instanceof String) { + String strValue = (String)value; + if (strValue.length() == 1) { + return converted(strValue.charAt(0)); + } + return unConvertible(); + } + return unConvertible(); + } + + private static boolean noNeedConvert(Object value, Class type) { + return type == null || value == null || type.isInstance(value); + } + + private static QConverted converted(Object converted) { + return new QConverted(true, converted); + } + + private static QConverted unConvertible() { + return new QConverted(false, null); + } + + public static class QConverted { + + private final boolean convertible; + + private final Object converted; + + public QConverted(boolean convertible, Object converted) { + this.convertible = convertible; + this.converted = converted; + } + + public boolean isConvertible() { + return convertible; + } + + public Object getConverted() { + return converted; + } + } + +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/data/convert/ParametersTypeConvertor.java b/src/main/java/com/alibaba/qlexpress4/runtime/data/convert/ParametersTypeConvertor.java new file mode 100644 index 0000000..7b153ca --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/data/convert/ParametersTypeConvertor.java @@ -0,0 +1,35 @@ +package com.alibaba.qlexpress4.runtime.data.convert; + +import java.lang.reflect.Array; + +/** + * Author: TaoKan + */ +public class ParametersTypeConvertor { + public static Object[] cast(Object[] arguments, Class[] paramTypes, boolean isVarArg) { + if (!isVarArg) { + Object[] result = new Object[arguments.length]; + for (int i = 0; i < arguments.length; i++) { + Object argument = arguments[i]; + result[i] = ObjTypeConvertor.cast(argument, paramTypes[i]).getConverted(); + } + return result; + } + + Class itemType = paramTypes[paramTypes.length - 1].getComponentType(); + Object varArgs = Array.newInstance(itemType, arguments.length - paramTypes.length + 1); + int varArgStart = paramTypes.length - 1; + for (int i = varArgStart; i < arguments.length; i++) { + Object argument = arguments[i]; + Object castValue = ObjTypeConvertor.cast(argument, itemType).getConverted(); + Array.set(varArgs, i - varArgStart, castValue); + } + + Object[] result = new Object[paramTypes.length]; + for (int i = 0; i < paramTypes.length - 1; i++) { + result[i] = ObjTypeConvertor.cast(arguments[i], paramTypes[i]).getConverted(); + } + result[result.length - 1] = varArgs; + return result; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/data/lambda/QLambdaMethod.java b/src/main/java/com/alibaba/qlexpress4/runtime/data/lambda/QLambdaMethod.java new file mode 100644 index 0000000..4f228e2 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/data/lambda/QLambdaMethod.java @@ -0,0 +1,89 @@ +package com.alibaba.qlexpress4.runtime.data.lambda; + +import com.alibaba.qlexpress4.exception.UserDefineException; +import com.alibaba.qlexpress4.member.MethodHandler; +import com.alibaba.qlexpress4.runtime.IMethod; +import com.alibaba.qlexpress4.runtime.MetaClass; +import com.alibaba.qlexpress4.runtime.QLambda; +import com.alibaba.qlexpress4.runtime.QResult; +import com.alibaba.qlexpress4.runtime.ReflectLoader; +import com.alibaba.qlexpress4.runtime.data.DataValue; +import com.alibaba.qlexpress4.runtime.data.convert.ParametersTypeConvertor; +import com.alibaba.qlexpress4.runtime.function.CustomFunction; +import com.alibaba.qlexpress4.utils.BasicUtil; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; + +/** + * Author: TaoKan + */ +public class QLambdaMethod implements QLambda { + + private final String methodName; + + private final ReflectLoader reflectLoader; + + private final Object bean; + + public QLambdaMethod(String methodName, ReflectLoader reflectLoader, Object obj) { + this.methodName = methodName; + this.reflectLoader = reflectLoader; + this.bean = obj; + } + + @Override + public QResult call(Object... params) + throws Exception { + if (bean instanceof MetaClass) { + Class[] type = BasicUtil.getTypeOfObject(params); + IMethod method = reflectLoader.loadMethod(bean, methodName, type); + if (method != null) { + // static method + Object[] convertResult = + ParametersTypeConvertor.cast(params, method.getParameterTypes(), method.isVarArgs()); + Object value = MethodHandler.Access.accessMethodValue(method, bean, convertResult); + return new QResult(new DataValue(value), QResult.ResultType.RETURN); + } + if (params.length < 1) { + throw createMethodNotFoundException(type); + } + if (((MetaClass)bean).getClz() != params[0].getClass()) { + throw createMethodNotFoundException(type); + } + Class[] restParamsType = Arrays.copyOfRange(type, 1, type.length); + method = reflectLoader.loadMethod(params[0], methodName, restParamsType); + if (method == null) { + throw createMethodNotFoundException(restParamsType); + } + Object paramBean = params[0]; + Object[] restParams = Arrays.copyOfRange(params, 1, params.length); + Object[] convertResult = + ParametersTypeConvertor.cast(restParams, method.getParameterTypes(), method.isVarArgs()); + Object value = MethodHandler.Access.accessMethodValue(method, paramBean, convertResult); + return new QResult(new DataValue(value), QResult.ResultType.RETURN); + } + else { + Class[] type = BasicUtil.getTypeOfObject(params); + IMethod method = reflectLoader.loadMethod(bean, methodName, type); + if (method == null) { + throw createMethodNotFoundException(type); + } + Object[] convertResult = + ParametersTypeConvertor.cast(params, method.getParameterTypes(), method.isVarArgs()); + Object value = MethodHandler.Access.accessMethodValue(method, bean, convertResult); + return new QResult(new DataValue(value), QResult.ResultType.RETURN); + } + } + + /** + * Create method not found exception + * @param type parameter type array + * @return UserDefineException exception object + */ + private UserDefineException createMethodNotFoundException(Class[] type) { + return new UserDefineException(UserDefineException.ExceptionType.INVALID_ARGUMENT, + "method reference '" + methodName + "' not found for argument types " + Arrays.toString(type)); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/function/CustomFunction.java b/src/main/java/com/alibaba/qlexpress4/runtime/function/CustomFunction.java new file mode 100644 index 0000000..0f854d3 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/function/CustomFunction.java @@ -0,0 +1,21 @@ +package com.alibaba.qlexpress4.runtime.function; + +import com.alibaba.qlexpress4.runtime.Parameters; +import com.alibaba.qlexpress4.runtime.QContext; + +/** + * Author: DQinYuan + */ +public interface CustomFunction { + + /** + * @param qContext context of current script run + * @param parameters parameters + * @return result of function + * @throws Throwable + * {@link com.alibaba.qlexpress4.exception.UserDefineException} for custom error message + */ + Object call(QContext qContext, Parameters parameters) + throws Throwable; + +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/function/ExtensionFunction.java b/src/main/java/com/alibaba/qlexpress4/runtime/function/ExtensionFunction.java new file mode 100644 index 0000000..5a4f143 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/function/ExtensionFunction.java @@ -0,0 +1,27 @@ +package com.alibaba.qlexpress4.runtime.function; + +import com.alibaba.qlexpress4.runtime.IMethod; + +import java.lang.reflect.InvocationTargetException; + +/** + * Author: DQinYuan + */ +public abstract class ExtensionFunction implements IMethod { + + @Override + public boolean isVarArgs() { + return false; + } + + @Override + public boolean isAccess() { + return true; + } + + @Override + public void setAccessible(boolean flag) { + + } + +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/function/FilterExtensionFunction.java b/src/main/java/com/alibaba/qlexpress4/runtime/function/FilterExtensionFunction.java new file mode 100644 index 0000000..2694064 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/function/FilterExtensionFunction.java @@ -0,0 +1,41 @@ +package com.alibaba.qlexpress4.runtime.function; + +import java.lang.reflect.InvocationTargetException; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +/** + * Author: DQinYuan + */ +public class FilterExtensionFunction extends ExtensionFunction { + + public static FilterExtensionFunction INSTANCE = new FilterExtensionFunction(); + + private FilterExtensionFunction() { + } + + @Override + public Class[] getParameterTypes() { + return new Class[] {Predicate.class}; + } + + @Override + public String getName() { + return "filter"; + } + + @Override + public Class getDeclaringClass() { + return List.class; + } + + @Override + public Object invoke(Object obj, Object[] args) + throws InvocationTargetException, IllegalAccessException { + if (!(obj instanceof List)) { + return null; + } + return ((List)obj).stream().filter((Predicate)args[0]).collect(Collectors.toList()); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/function/MapExtensionFunction.java b/src/main/java/com/alibaba/qlexpress4/runtime/function/MapExtensionFunction.java new file mode 100644 index 0000000..83a621e --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/function/MapExtensionFunction.java @@ -0,0 +1,41 @@ +package com.alibaba.qlexpress4.runtime.function; + +import java.lang.reflect.InvocationTargetException; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * Author: DQinYuan + */ +public class MapExtensionFunction extends ExtensionFunction { + + public static final MapExtensionFunction INSTANCE = new MapExtensionFunction(); + + private MapExtensionFunction() { + } + + @Override + public Class[] getParameterTypes() { + return new Class[] {Function.class}; + } + + @Override + public String getName() { + return "map"; + } + + @Override + public Class getDeclaringClass() { + return List.class; + } + + @Override + public Object invoke(Object obj, Object[] args) + throws InvocationTargetException, IllegalAccessException { + if (!(obj instanceof List)) { + return null; + } + return ((List)obj).stream().map((Function)args[0]).collect(Collectors.toList()); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/function/QLambdaFunction.java b/src/main/java/com/alibaba/qlexpress4/runtime/function/QLambdaFunction.java new file mode 100644 index 0000000..7c93b01 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/function/QLambdaFunction.java @@ -0,0 +1,28 @@ +package com.alibaba.qlexpress4.runtime.function; + +import com.alibaba.qlexpress4.runtime.Parameters; +import com.alibaba.qlexpress4.runtime.QContext; +import com.alibaba.qlexpress4.runtime.QLambda; +import com.alibaba.qlexpress4.runtime.QRuntime; + +/** + * Author: DQinYuan + */ +public class QLambdaFunction implements CustomFunction { + + private final QLambda qLambda; + + public QLambdaFunction(QLambda qLambda) { + this.qLambda = qLambda; + } + + @Override + public Object call(QContext qContext, Parameters parameters) + throws Throwable { + Object[] paramsArr = new Object[parameters.size()]; + for (int i = 0; i < paramsArr.length; i++) { + paramsArr[i] = parameters.get(i).get(); + } + return qLambda.call(paramsArr).getResult().get(); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/function/QMethodFunction.java b/src/main/java/com/alibaba/qlexpress4/runtime/function/QMethodFunction.java new file mode 100644 index 0000000..28ddb39 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/function/QMethodFunction.java @@ -0,0 +1,51 @@ +package com.alibaba.qlexpress4.runtime.function; + +import com.alibaba.qlexpress4.exception.UserDefineException; +import com.alibaba.qlexpress4.member.MethodHandler; +import com.alibaba.qlexpress4.runtime.IMethod; +import com.alibaba.qlexpress4.runtime.JvmIMethod; +import com.alibaba.qlexpress4.runtime.MemberResolver; +import com.alibaba.qlexpress4.runtime.Parameters; +import com.alibaba.qlexpress4.runtime.QContext; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.data.convert.ParametersTypeConvertor; +import com.alibaba.qlexpress4.utils.BasicUtil; + +import java.lang.reflect.Method; +import java.util.Arrays; + +/** + * Author: DQinYuan + */ +public class QMethodFunction implements CustomFunction { + + private final Object object; + + private final IMethod method; + + public QMethodFunction(Object object, Method method) { + this.object = object; + this.method = new JvmIMethod(method); + } + + @Override + public Object call(QContext qContext, Parameters parameters) + throws Throwable { + Class[] type = new Class[parameters.size()]; + Object[] params = new Object[parameters.size()]; + for (int i = 0; i < params.length; i++) { + Value v = parameters.get(i); + params[i] = v.get(); + type[i] = v.getType(); + } + + int priority = MemberResolver.resolvePriority(method.getParameterTypes(), type); + if (priority == MemberResolver.MatchPriority.MISMATCH.priority) { + throw new UserDefineException(UserDefineException.ExceptionType.INVALID_ARGUMENT, + "invalid argument types " + Arrays.toString(type) + " for java method '" + method.getName() + "'" + + " in declaring java class '" + method.getDeclaringClass().getName() + "'"); + } + Object[] convertResult = ParametersTypeConvertor.cast(params, method.getParameterTypes(), method.isVarArgs()); + return MethodHandler.Access.accessMethodValue(method, object, convertResult); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/instruction/BreakContinueInstruction.java b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/BreakContinueInstruction.java new file mode 100644 index 0000000..7fdd77a --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/BreakContinueInstruction.java @@ -0,0 +1,47 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.QContext; +import com.alibaba.qlexpress4.runtime.QResult; +import com.alibaba.qlexpress4.utils.PrintlnUtils; + +import java.util.function.Consumer; + +/** + * Operation: return break object and exit lambda + * Input: 0 + * Output: 0 + *

+ * Author: DQinYuan + */ +public class BreakContinueInstruction extends QLInstruction { + + private final QResult result; + + public BreakContinueInstruction(ErrorReporter errorReporter, QResult result) { + super(errorReporter); + this.result = result; + } + + @Override + public QResult execute(QContext qContext, QLOptions qlOptions) { + return result; + } + + @Override + public int stackInput() { + return 0; + } + + @Override + public int stackOutput() { + return 0; + } + + @Override + public void println(int index, int depth, Consumer debug) { + String breakContinue = result == QResult.LOOP_BREAK_RESULT ? "Break" : "Continue"; + PrintlnUtils.printlnByCurDepth(depth, index + ": " + breakContinue, debug); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/instruction/CallConstInstruction.java b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/CallConstInstruction.java new file mode 100644 index 0000000..dc23ba8 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/CallConstInstruction.java @@ -0,0 +1,74 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.exception.QLErrorCodes; +import com.alibaba.qlexpress4.exception.UserDefineException; +import com.alibaba.qlexpress4.runtime.*; +import com.alibaba.qlexpress4.runtime.util.ThrowUtils; +import com.alibaba.qlexpress4.runtime.util.ValueUtils; +import com.alibaba.qlexpress4.utils.PrintlnUtils; + +import java.util.function.Consumer; + +/** + * Operation: call const lambda + * Input: ${argNum} + * Output: 1 const lambda result + *

+ * Author: DQinYuan + */ +public class CallConstInstruction extends QLInstruction { + + private final QLambda constLambda; + + private final int argNum; + + private final String lambdaName; + + public CallConstInstruction(ErrorReporter errorReporter, QLambda constLambda, int argNum, String lambdaName) { + super(errorReporter); + this.constLambda = constLambda; + this.argNum = argNum; + this.lambdaName = lambdaName; + } + + @Override + public QResult execute(QContext qContext, QLOptions qlOptions) { + Parameters args = qContext.pop(argNum); + Object[] argArr = new Object[argNum]; + for (int i = 0; i < argNum; i++) { + argArr[i] = args.getValue(i); + } + + try { + QResult result = constLambda.call(argArr); + qContext.push(ValueUtils.toImmutable(result.getResult())); + return QResult.NEXT_INSTRUCTION; + } + catch (UserDefineException e) { + throw ThrowUtils.reportUserDefinedException(errorReporter, e); + } + catch (Throwable t) { + throw ThrowUtils.wrapThrowable(t, + errorReporter, + QLErrorCodes.EXECUTE_BLOCK_ERROR.name(), + QLErrorCodes.EXECUTE_BLOCK_ERROR.getErrorMsg()); + } + } + + @Override + public int stackInput() { + return argNum; + } + + @Override + public int stackOutput() { + return 1; + } + + @Override + public void println(int index, int depth, Consumer debug) { + PrintlnUtils.printlnByCurDepth(depth, index + ": CallConstLambda " + lambdaName, debug); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/instruction/CallFunctionInstruction.java b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/CallFunctionInstruction.java new file mode 100644 index 0000000..efd1d80 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/CallFunctionInstruction.java @@ -0,0 +1,121 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.exception.QLErrorCodes; +import com.alibaba.qlexpress4.exception.UserDefineException; +import com.alibaba.qlexpress4.runtime.*; +import com.alibaba.qlexpress4.runtime.data.DataValue; +import com.alibaba.qlexpress4.runtime.function.CustomFunction; +import com.alibaba.qlexpress4.runtime.trace.ExpressionTrace; +import com.alibaba.qlexpress4.runtime.util.ThrowUtils; +import com.alibaba.qlexpress4.runtime.util.ValueUtils; +import com.alibaba.qlexpress4.utils.PrintlnUtils; + +import java.util.function.Consumer; + +/** + * Operation: call ql function from function table + * Input: ${argNum} + * Output: 1 function result + * Author: DQinYuan + */ +public class CallFunctionInstruction extends QLInstruction { + + private final String functionName; + + private final int argNum; + + private final Integer traceKey; + + public CallFunctionInstruction(ErrorReporter errorReporter, String functionName, int argNum, Integer traceKey) { + super(errorReporter); + this.functionName = functionName; + this.argNum = argNum; + this.traceKey = traceKey; + } + + @Override + public QResult execute(QContext qContext, QLOptions qlOptions) { + CustomFunction function = qContext.getFunction(functionName); + if (function == null) { + callLambda(qContext, qlOptions); + return QResult.NEXT_INSTRUCTION; + } + Parameters parameters = qContext.pop(argNum); + try { + Object functionResultObj = function.call(qContext, parameters); + qContext.push(new DataValue(functionResultObj)); + + // trace + ExpressionTrace expressionTrace = qContext.getTraces().getExpressionTraceByKey(traceKey); + if (expressionTrace != null) { + expressionTrace.valueEvaluated(functionResultObj); + } + + return QResult.NEXT_INSTRUCTION; + } + catch (UserDefineException e) { + throw ThrowUtils.reportUserDefinedException(errorReporter, e); + } + catch (Throwable t) { + throw ThrowUtils.wrapThrowable(t, + errorReporter, + QLErrorCodes.INVOKE_FUNCTION_INNER_ERROR.name(), + String.format(QLErrorCodes.INVOKE_FUNCTION_INNER_ERROR.getErrorMsg(), functionName, t.getMessage())); + } + } + + private void callLambda(QContext qContext, QLOptions qlOptions) { + Object lambdaSymbol = qContext.getSymbolValue(functionName); + if (lambdaSymbol == null) { + if (qlOptions.isAvoidNullPointer()) { + qContext.pop(argNum); + qContext.push(DataValue.NULL_VALUE); + } + else { + throw errorReporter.report(new NullPointerException(), + QLErrorCodes.FUNCTION_NOT_FOUND.name(), + String.format(QLErrorCodes.FUNCTION_NOT_FOUND.getErrorMsg(), functionName)); + } + return; + } + if (!(lambdaSymbol instanceof QLambda)) { + throw errorReporter.report(QLErrorCodes.FUNCTION_TYPE_MISMATCH.name(), + String.format(QLErrorCodes.FUNCTION_TYPE_MISMATCH.getErrorMsg(), functionName)); + } + Parameters parameters = qContext.pop(argNum); + Object[] parametersArr = new Object[parameters.size()]; + for (int i = 0; i < parametersArr.length; i++) { + parametersArr[i] = parameters.get(i).get(); + } + try { + Value resultValue = ((QLambda)lambdaSymbol).call(parametersArr).getResult(); + qContext.push(ValueUtils.toImmutable(resultValue)); + } + catch (UserDefineException e) { + throw ThrowUtils.reportUserDefinedException(errorReporter, e); + } + catch (Throwable t) { + throw ThrowUtils.wrapThrowable(t, + errorReporter, + QLErrorCodes.INVOKE_LAMBDA_ERROR.name(), + QLErrorCodes.INVOKE_LAMBDA_ERROR.getErrorMsg()); + } + } + + @Override + public int stackInput() { + return argNum; + } + + @Override + public int stackOutput() { + return 1; + } + + @Override + public void println(int index, int depth, Consumer debug) { + PrintlnUtils.printlnByCurDepth(depth, index + ": CallFunction " + functionName + " " + argNum, debug); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/instruction/CallInstruction.java b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/CallInstruction.java new file mode 100644 index 0000000..23e8b58 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/CallInstruction.java @@ -0,0 +1,84 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.exception.QLErrorCodes; +import com.alibaba.qlexpress4.exception.UserDefineException; +import com.alibaba.qlexpress4.runtime.*; +import com.alibaba.qlexpress4.runtime.data.DataValue; +import com.alibaba.qlexpress4.runtime.util.ThrowUtils; +import com.alibaba.qlexpress4.runtime.util.ValueUtils; +import com.alibaba.qlexpress4.utils.PrintlnUtils; + +import java.util.function.Consumer; + +/** + * Operation: call a lambda with fixed number of arguments + * Input: ${argNum} + 1 + * Output: 1, lambda return result + *

+ * Author: DQinYuan + */ +public class CallInstruction extends QLInstruction { + + private final int argNum; + + public CallInstruction(ErrorReporter errorReporter, int argNum) { + super(errorReporter); + this.argNum = argNum; + } + + @Override + public QResult execute(QContext qContext, QLOptions qlOptions) { + Parameters parameters = qContext.pop(this.argNum + 1); + Object bean = parameters.get(0).get(); + if (bean == null) { + if (qlOptions.isAvoidNullPointer()) { + qContext.push(DataValue.NULL_VALUE); + return QResult.NEXT_INSTRUCTION; + } + else { + throw this.errorReporter.report(new NullPointerException(), + QLErrorCodes.NULL_CALL.name(), + QLErrorCodes.NULL_CALL.getErrorMsg()); + } + } + if (!(bean instanceof QLambda)) { + throw this.errorReporter.report(QLErrorCodes.OBJECT_NOT_CALLABLE.name(), + String.format(QLErrorCodes.OBJECT_NOT_CALLABLE.getErrorMsg(), bean.getClass().getName())); + } + Object[] params = new Object[this.argNum]; + for (int i = 0; i < this.argNum; i++) { + params[i] = parameters.get(i + 1).get(); + } + try { + QLambda qLambda = (QLambda)bean; + qContext.push(ValueUtils.toImmutable(qLambda.call(params).getResult())); + return QResult.NEXT_INSTRUCTION; + } + catch (UserDefineException e) { + throw ThrowUtils.reportUserDefinedException(errorReporter, e); + } + catch (Throwable t) { + throw ThrowUtils.wrapThrowable(t, + errorReporter, + QLErrorCodes.INVOKE_LAMBDA_ERROR.name(), + QLErrorCodes.INVOKE_LAMBDA_ERROR.getErrorMsg()); + } + } + + @Override + public int stackInput() { + return argNum + 1; + } + + @Override + public int stackOutput() { + return 1; + } + + @Override + public void println(int index, int depth, Consumer debug) { + PrintlnUtils.printlnByCurDepth(depth, index + ": Call with argNum " + argNum, debug); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/instruction/CastInstruction.java b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/CastInstruction.java new file mode 100644 index 0000000..fcdf723 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/CastInstruction.java @@ -0,0 +1,75 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.exception.QLErrorCodes; +import com.alibaba.qlexpress4.runtime.MetaClass; +import com.alibaba.qlexpress4.runtime.QContext; +import com.alibaba.qlexpress4.runtime.QResult; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.data.DataValue; +import com.alibaba.qlexpress4.runtime.data.convert.ObjTypeConvertor; +import com.alibaba.qlexpress4.utils.PrintlnUtils; + +import java.util.function.Consumer; + +/** + * Operation: force cast value to specified type + * Input: 2 targetCls and value + * Output: 1 casted value + *

+ * Author: DQinYuan + */ +public class CastInstruction extends QLInstruction { + + public CastInstruction(ErrorReporter errorReporter) { + super(errorReporter); + } + + @Override + public QResult execute(QContext qContext, QLOptions qlOptions) { + Value value = qContext.pop(); + Class targetClz = popTargetClz(qContext.pop().get()); + if (value == null) { + qContext.push(Value.NULL_VALUE); + return QResult.NEXT_INSTRUCTION; + } + ObjTypeConvertor.QConverted result = ObjTypeConvertor.cast(value.get(), targetClz); + if (!result.isConvertible()) { + throw errorReporter.reportFormat(QLErrorCodes.INCOMPATIBLE_TYPE_CAST.name(), + QLErrorCodes.INCOMPATIBLE_TYPE_CAST.getErrorMsg(), + value.getTypeName(), + targetClz.getName()); + } + Value dataCast = new DataValue(result.getConverted()); + qContext.push(dataCast); + return QResult.NEXT_INSTRUCTION; + } + + private Class popTargetClz(Object target) { + if (target instanceof MetaClass) { + return ((MetaClass)target).getClz(); + } + else if (target instanceof Class) { + return (Class)target; + } + throw errorReporter.reportFormat(QLErrorCodes.INVALID_CAST_TARGET.name(), + QLErrorCodes.INVALID_CAST_TARGET.getErrorMsg(), + target == null ? "null" : target.getClass().getName()); + } + + @Override + public int stackInput() { + return 2; + } + + @Override + public int stackOutput() { + return 1; + } + + @Override + public void println(int index, int depth, Consumer debug) { + PrintlnUtils.printlnByCurDepth(depth, index + ": Cast", debug); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/instruction/CheckTimeOutInstruction.java b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/CheckTimeOutInstruction.java new file mode 100644 index 0000000..94ff7d1 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/CheckTimeOutInstruction.java @@ -0,0 +1,52 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.exception.QLErrorCodes; +import com.alibaba.qlexpress4.runtime.QContext; +import com.alibaba.qlexpress4.runtime.QResult; +import com.alibaba.qlexpress4.utils.PrintlnUtils; + +import java.util.function.Consumer; + +/** + * Operation: check if program timeout + * Input: 0 + * Output: 0 + *

+ * Author: DQinYuan + */ +public class CheckTimeOutInstruction extends QLInstruction { + public CheckTimeOutInstruction(ErrorReporter errorReporter) { + super(errorReporter); + } + + @Override + public QResult execute(QContext qContext, QLOptions qlOptions) { + if (qlOptions.getTimeoutMillis() <= 0) { + return QResult.NEXT_INSTRUCTION; + } + if (System.currentTimeMillis() - qContext.scriptStartTimeStamp() > qlOptions.getTimeoutMillis()) { + // timeout + throw errorReporter.reportFormat(QLErrorCodes.SCRIPT_TIME_OUT.name(), + QLErrorCodes.SCRIPT_TIME_OUT.getErrorMsg(), + qlOptions.getTimeoutMillis()); + } + return QResult.NEXT_INSTRUCTION; + } + + @Override + public int stackInput() { + return 0; + } + + @Override + public int stackOutput() { + return 0; + } + + @Override + public void println(int index, int depth, Consumer debug) { + PrintlnUtils.printlnByCurDepth(depth, index + ": CheckTimeout", debug); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/instruction/CloseScopeInstruction.java b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/CloseScopeInstruction.java new file mode 100644 index 0000000..1097f5e --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/CloseScopeInstruction.java @@ -0,0 +1,47 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.QContext; +import com.alibaba.qlexpress4.runtime.QResult; +import com.alibaba.qlexpress4.utils.PrintlnUtils; + +import java.util.function.Consumer; + +/** + * Operation: close scope + * Input: 0 + * Output: 0 + * + * Author: DQinYuan + */ +public class CloseScopeInstruction extends QLInstruction { + + private final String scopeName; + + public CloseScopeInstruction(ErrorReporter errorReporter, String scopeName) { + super(errorReporter); + this.scopeName = scopeName; + } + + @Override + public QResult execute(QContext qContext, QLOptions qlOptions) { + qContext.closeScope(); + return QResult.NEXT_INSTRUCTION; + } + + @Override + public int stackInput() { + return 0; + } + + @Override + public int stackOutput() { + return 0; + } + + @Override + public void println(int index, int depth, Consumer debug) { + PrintlnUtils.printlnByCurDepth(depth, index + ": CloseScope " + scopeName, debug); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/instruction/ConstInstruction.java b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/ConstInstruction.java new file mode 100644 index 0000000..f895d4f --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/ConstInstruction.java @@ -0,0 +1,65 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.QContext; +import com.alibaba.qlexpress4.runtime.QResult; +import com.alibaba.qlexpress4.runtime.data.DataValue; +import com.alibaba.qlexpress4.runtime.trace.ExpressionTrace; +import com.alibaba.qlexpress4.utils.PrintlnUtils; + +import java.util.function.Consumer; + +/** + * Operation: push constObj to stack + * Input: 0 + * Output: 1 + *

+ * Author: DQinYuan + */ +public class ConstInstruction extends QLInstruction { + + private final Object constObj; + + private final Integer traceKey; + + public ConstInstruction(ErrorReporter errorReporter, Object constObj, Integer traceKey) { + super(errorReporter); + this.constObj = constObj; + this.traceKey = traceKey; + } + + @Override + public QResult execute(QContext qContext, QLOptions qlOptions) { + qContext.push(new DataValue(constObj)); + + // trace + ExpressionTrace expressionTrace = qContext.getTraces().getExpressionTraceByKey(traceKey); + if (expressionTrace != null) { + expressionTrace.valueEvaluated(constObj); + } + + return QResult.NEXT_INSTRUCTION; + } + + @Override + public int stackInput() { + return 0; + } + + @Override + public int stackOutput() { + return 1; + } + + public Object getConstObj() { + return constObj; + } + + @Override + public void println(int index, int depth, Consumer debug) { + PrintlnUtils.printlnByCurDepth(depth, + index + ": LoadConst " + (constObj == null ? "null" : constObj.toString()), + debug); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/instruction/DefineFunctionInstruction.java b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/DefineFunctionInstruction.java new file mode 100644 index 0000000..cf7be6f --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/DefineFunctionInstruction.java @@ -0,0 +1,52 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.*; +import com.alibaba.qlexpress4.runtime.function.QLambdaFunction; +import com.alibaba.qlexpress4.utils.PrintlnUtils; + +import java.util.function.Consumer; + +/** + * Operation: define function + * Input: 0 + * Output: 0 + *

+ * Author: DQinYuan + */ +public class DefineFunctionInstruction extends QLInstruction { + + private final String name; + + private final QLambdaDefinition lambdaDefinition; + + public DefineFunctionInstruction(ErrorReporter errorReporter, String name, QLambdaDefinition lambdaDefinition) { + super(errorReporter); + this.name = name; + this.lambdaDefinition = lambdaDefinition; + } + + @Override + public QResult execute(QContext qContext, QLOptions qlOptions) { + QLambda lambda = lambdaDefinition.toLambda(qContext, qlOptions, true); + qContext.defineFunction(name, new QLambdaFunction(lambda)); + return QResult.NEXT_INSTRUCTION; + } + + @Override + public int stackInput() { + return 0; + } + + @Override + public int stackOutput() { + return 0; + } + + @Override + public void println(int index, int depth, Consumer debug) { + PrintlnUtils.printlnByCurDepth(depth, index + ": DefineFunction " + name, debug); + lambdaDefinition.println(depth + 1, debug); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/instruction/DefineLocalInstruction.java b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/DefineLocalInstruction.java new file mode 100644 index 0000000..b012b49 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/DefineLocalInstruction.java @@ -0,0 +1,60 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.exception.QLErrorCodes; +import com.alibaba.qlexpress4.runtime.QContext; +import com.alibaba.qlexpress4.runtime.QResult; +import com.alibaba.qlexpress4.runtime.data.convert.ObjTypeConvertor; +import com.alibaba.qlexpress4.utils.PrintlnUtils; + +import java.util.function.Consumer; + +/** + * Operation: define a symbol in local scope + * Input: 1 symbol init value + * Output: 0 + * + * Author: DQinYuan + */ +public class DefineLocalInstruction extends QLInstruction { + + private final String variableName; + + private final Class defineClz; + + public DefineLocalInstruction(ErrorReporter errorReporter, String variableName, Class defineClz) { + super(errorReporter); + this.variableName = variableName; + this.defineClz = defineClz; + } + + @Override + public QResult execute(QContext qContext, QLOptions qlOptions) { + Object initValue = qContext.pop().get(); + ObjTypeConvertor.QConverted qlConvertResult = ObjTypeConvertor.cast(initValue, defineClz); + if (!qlConvertResult.isConvertible()) { + throw errorReporter.reportFormat(QLErrorCodes.INCOMPATIBLE_ASSIGNMENT_TYPE.name(), + QLErrorCodes.INCOMPATIBLE_ASSIGNMENT_TYPE.getErrorMsg(), + defineClz.getName(), + initValue == null ? "null" : initValue.getClass().getName()); + } + qContext.defineLocalSymbol(variableName, defineClz, qlConvertResult.getConverted()); + return QResult.NEXT_INSTRUCTION; + } + + @Override + public int stackInput() { + return 1; + } + + @Override + public int stackOutput() { + return 0; + } + + @Override + public void println(int index, int depth, Consumer debug) { + PrintlnUtils.printlnByCurDepth(depth, index + ": DefineLocal " + variableName, debug); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/instruction/ForEachInstruction.java b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/ForEachInstruction.java new file mode 100644 index 0000000..d7c842e --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/ForEachInstruction.java @@ -0,0 +1,124 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.exception.QLErrorCodes; +import com.alibaba.qlexpress4.exception.QLRuntimeException; +import com.alibaba.qlexpress4.exception.UserDefineException; +import com.alibaba.qlexpress4.runtime.*; +import com.alibaba.qlexpress4.utils.PrintlnUtils; + +import java.lang.reflect.Array; +import java.text.MessageFormat; +import java.util.Iterator; +import java.util.function.Consumer; + +/** + * Operation: process each element in iterable object on top of stack, + * Input: 1 + * Output: 0 + * + * Author: DQinYuan + */ +public class ForEachInstruction extends QLInstruction { + + private final QLambdaDefinition body; + + private final ErrorReporter targetErrorReporter; + + private final Class itCls; + + public ForEachInstruction(ErrorReporter errorReporter, QLambdaDefinition body, Class itCls, + ErrorReporter targetErrorReporter) { + super(errorReporter); + this.body = body; + this.itCls = itCls; + this.targetErrorReporter = targetErrorReporter; + } + + @Override + public QResult execute(QContext qContext, QLOptions qlOptions) { + Object mayBeIterable = qContext.pop().get(); + if (mayBeIterable != null && mayBeIterable.getClass().isArray()) { + mayBeIterable = new ReflectArrayIterable(mayBeIterable); + } + else if (!(mayBeIterable instanceof Iterable)) { + throw targetErrorReporter.report(QLErrorCodes.FOR_EACH_ITERABLE_REQUIRED.name(), + QLErrorCodes.FOR_EACH_ITERABLE_REQUIRED.getErrorMsg()); + } + Iterable iterable = (Iterable)mayBeIterable; + QLambda bodyLambda = body.toLambda(qContext, qlOptions, true); + forEachBody: for (Object item : iterable) { + try { + QResult bodyResult = bodyLambda.call(item); + switch (bodyResult.getResultType()) { + case RETURN: + return bodyResult; + case BREAK: + break forEachBody; + } + } + catch (UserDefineException e) { + throw errorReporter.reportFormat(QLErrorCodes.FOR_EACH_TYPE_MISMATCH.name(), + QLErrorCodes.FOR_EACH_TYPE_MISMATCH.getErrorMsg(), + itCls.getName(), + item == null ? "null" : item.getClass().getName()); + } + catch (Throwable t) { + if (t instanceof QLRuntimeException) { + throw (QLRuntimeException)t; + } + // should not run there + throw errorReporter.report(QLErrorCodes.FOR_EACH_UNKNOWN_ERROR.name(), + QLErrorCodes.FOR_EACH_UNKNOWN_ERROR.getErrorMsg()); + } + } + return QResult.NEXT_INSTRUCTION; + } + + @Override + public int stackInput() { + return 1; + } + + @Override + public int stackOutput() { + return 0; + } + + @Override + public void println(int index, int depth, Consumer debug) { + PrintlnUtils.printlnByCurDepth(depth, index + ": ForEach", debug); + body.println(depth + 1, debug); + } + + private static class ReflectArrayIterable implements Iterable { + + private final Object arrObj; + + private ReflectArrayIterable(Object arrObj) { + this.arrObj = arrObj; + } + + @Override + public Iterator iterator() { + return new ReflectArrayIterator(); + } + + private class ReflectArrayIterator implements Iterator { + + private int cursor; + + @Override + public boolean hasNext() { + return cursor < Array.getLength(arrObj); + } + + @Override + public Object next() { + return Array.get(arrObj, cursor++); + } + } + } + +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/instruction/ForInstruction.java b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/ForInstruction.java new file mode 100644 index 0000000..46618ed --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/ForInstruction.java @@ -0,0 +1,163 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.exception.QLErrorCodes; +import com.alibaba.qlexpress4.runtime.*; +import com.alibaba.qlexpress4.runtime.scope.QvmBlockScope; +import com.alibaba.qlexpress4.runtime.util.ThrowUtils; +import com.alibaba.qlexpress4.utils.PrintlnUtils; + +import java.util.HashMap; +import java.util.function.Consumer; + +/** + * Operation: traditional for loop + * Input: 0 + * Output: 0 + * Author: DQinYuan + */ +public class ForInstruction extends QLInstruction { + + /** + * nullable + */ + private final QLambdaDefinition forInit; + + /** + * nullable + */ + private final QLambdaDefinition condition; + + private final ErrorReporter conditionErrorReporter; + + /** + * nullable + */ + private final QLambdaDefinition forUpdate; + + private final int forScopeMaxStackSize; + + private final QLambdaDefinition forBody; + + public ForInstruction(ErrorReporter errorReporter, QLambdaDefinition forInit, QLambdaDefinition condition, + ErrorReporter conditionErrorReporter, QLambdaDefinition forUpdate, int forScopeMaxStackSize, + QLambdaDefinition forBody) { + super(errorReporter); + this.forInit = forInit; + this.condition = condition; + this.conditionErrorReporter = conditionErrorReporter; + this.forUpdate = forUpdate; + this.forScopeMaxStackSize = forScopeMaxStackSize; + this.forBody = forBody; + } + + @Override + public QResult execute(QContext qContext, QLOptions qlOptions) { + QContext forScopeContext = + needForScope() + ? new DelegateQContext(qContext, + new QvmBlockScope(qContext, new HashMap<>(1), forScopeMaxStackSize, ExceptionTable.EMPTY)) + : qContext; + if (forInit != null) { + QLambda initLambda = forInit.toLambda(forScopeContext, qlOptions, false); + try { + initLambda.call(); + } + catch (Throwable t) { + throw ThrowUtils.wrapThrowable(t, + errorReporter, + QLErrorCodes.FOR_INIT_ERROR.name(), + QLErrorCodes.FOR_INIT_ERROR.getErrorMsg()); + } + } + + QLambda conditionLambda = condition != null ? condition.toLambda(forScopeContext, qlOptions, false) : null; + QLambda updateLambda = forUpdate != null ? forUpdate.toLambda(forScopeContext, qlOptions, false) : null; + QLambda bodyLambda = forBody.toLambda(forScopeContext, qlOptions, true); + + forBody: while (conditionLambda == null || evalCondition(conditionLambda)) { + try { + QResult bodyResult = bodyLambda.call(); + switch (bodyResult.getResultType()) { + case RETURN: + return bodyResult; + case BREAK: + break forBody; + } + } + catch (Throwable t) { + throw ThrowUtils.wrapThrowable(t, + errorReporter, + QLErrorCodes.FOR_BODY_ERROR.name(), + QLErrorCodes.FOR_BODY_ERROR.getErrorMsg()); + } + if (updateLambda != null) { + runUpdate(updateLambda); + } + } + return QResult.NEXT_INSTRUCTION; + } + + private boolean needForScope() { + return forInit != null || condition != null || forUpdate != null; + } + + private void runUpdate(QLambda updateLambda) { + try { + updateLambda.call(); + } + catch (Throwable t) { + throw ThrowUtils.wrapThrowable(t, + errorReporter, + QLErrorCodes.FOR_UPDATE_ERROR.name(), + QLErrorCodes.FOR_UPDATE_ERROR.getErrorMsg()); + } + } + + private boolean evalCondition(QLambda conditionLambda) { + try { + Object conditionResult = conditionLambda.call().getResult().get(); + if (!(conditionResult instanceof Boolean)) { + throw conditionErrorReporter.report(QLErrorCodes.FOR_CONDITION_BOOL_REQUIRED.name(), + QLErrorCodes.FOR_CONDITION_BOOL_REQUIRED.getErrorMsg()); + } + return (boolean)conditionResult; + } + catch (Throwable t) { + throw ThrowUtils.wrapThrowable(t, + conditionErrorReporter, + QLErrorCodes.FOR_CONDITION_ERROR.name(), + QLErrorCodes.FOR_CONDITION_ERROR.getErrorMsg()); + } + } + + @Override + public int stackInput() { + return 0; + } + + @Override + public int stackOutput() { + return 0; + } + + @Override + public void println(int index, int depth, Consumer debug) { + PrintlnUtils.printlnByCurDepth(depth, index + ": For", debug); + PrintlnUtils.printlnByCurDepth(depth + 1, "Init", debug); + if (forInit != null) { + forInit.println(depth + 2, debug); + } + PrintlnUtils.printlnByCurDepth(depth + 1, "Condition", debug); + if (condition != null) { + condition.println(depth + 2, debug); + } + PrintlnUtils.printlnByCurDepth(depth + 1, "Update", debug); + if (forUpdate != null) { + forUpdate.println(depth + 2, debug); + } + PrintlnUtils.printlnByCurDepth(depth + 1, "Body", debug); + forBody.println(depth + 2, debug); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/instruction/GetFieldInstruction.java b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/GetFieldInstruction.java new file mode 100644 index 0000000..acaee78 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/GetFieldInstruction.java @@ -0,0 +1,72 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.exception.QLErrorCodes; +import com.alibaba.qlexpress4.runtime.*; +import com.alibaba.qlexpress4.runtime.data.DataValue; +import com.alibaba.qlexpress4.utils.PrintlnUtils; + +import java.util.function.Consumer; + +/** + * Operation: get specified field of object on the top of stack + * Input: 1 + * Output: 1 + *

+ * Author: DQinYuan + */ +public class GetFieldInstruction extends QLInstruction { + + private final String fieldName; + + private final boolean optional; + + public GetFieldInstruction(ErrorReporter errorReporter, String fieldName, boolean optional) { + super(errorReporter); + this.fieldName = fieldName; + this.optional = optional; + } + + @Override + public QResult execute(QContext qContext, QLOptions qlOptions) { + Object bean = qContext.pop().get(); + if (bean == null) { + if (qlOptions.isAvoidNullPointer() || optional) { + qContext.push(DataValue.NULL_VALUE); + return QResult.NEXT_INSTRUCTION; + } + throw errorReporter.report(new NullPointerException(), + QLErrorCodes.NULL_FIELD_ACCESS.name(), + QLErrorCodes.NULL_FIELD_ACCESS.getErrorMsg()); + } + Value fieldValue = qContext.getReflectLoader().loadField(bean, fieldName, false, errorReporter); + if (fieldValue == null) { + throw errorReporter.reportFormat(QLErrorCodes.FIELD_NOT_FOUND.name(), + QLErrorCodes.FIELD_NOT_FOUND.getErrorMsg(), + fieldName); + } + qContext.push(fieldValue); + return QResult.NEXT_INSTRUCTION; + } + + @Override + public int stackInput() { + return 1; + } + + @Override + public int stackOutput() { + return 1; + } + + @Override + public void println(int index, int depth, Consumer debug) { + PrintlnUtils.printlnByCurDepth(depth, index + ": GetField " + fieldName, debug); + } + + public String getFieldName() { + return fieldName; + } + +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/instruction/GetMethodInstruction.java b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/GetMethodInstruction.java new file mode 100644 index 0000000..05c1dfe --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/GetMethodInstruction.java @@ -0,0 +1,61 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.exception.QLErrorCodes; +import com.alibaba.qlexpress4.runtime.*; +import com.alibaba.qlexpress4.runtime.data.DataValue; +import com.alibaba.qlexpress4.runtime.data.lambda.QLambdaMethod; +import com.alibaba.qlexpress4.utils.PrintlnUtils; + +import java.util.function.Consumer; + +/** + * Operation: get specified method of object on the top of stack + * Input: 1 + * Output: 1 + *

+ * Author: DQinYuan + */ +public class GetMethodInstruction extends QLInstruction { + + private final String methodName; + + public GetMethodInstruction(ErrorReporter errorReporter, String methodName) { + super(errorReporter); + this.methodName = methodName; + } + + @Override + public QResult execute(QContext qContext, QLOptions qlOptions) { + Object bean = qContext.pop().get(); + if (bean == null) { + if (qlOptions.isAvoidNullPointer()) { + qContext.push(DataValue.NULL_VALUE); + return QResult.NEXT_INSTRUCTION; + } + throw this.errorReporter.report(new NullPointerException(), + QLErrorCodes.NULL_METHOD_ACCESS.name(), + QLErrorCodes.NULL_METHOD_ACCESS.getErrorMsg()); + } + ReflectLoader reflectLoader = qContext.getReflectLoader(); + qContext.push(new DataValue(new QLambdaMethod(methodName, reflectLoader, bean))); + return QResult.NEXT_INSTRUCTION; + } + + @Override + public int stackInput() { + return 1; + } + + @Override + public int stackOutput() { + return 1; + } + + @Override + public void println(int index, int depth, Consumer debug) { + PrintlnUtils.printlnByCurDepth(depth, index + ": GetMethod " + methodName, debug); + } + +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/instruction/IndexInstruction.java b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/IndexInstruction.java new file mode 100644 index 0000000..d03a4c1 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/IndexInstruction.java @@ -0,0 +1,94 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.exception.QLErrorCodes; +import com.alibaba.qlexpress4.runtime.QContext; +import com.alibaba.qlexpress4.runtime.QResult; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.data.ArrayItemValue; +import com.alibaba.qlexpress4.runtime.data.ListItemValue; +import com.alibaba.qlexpress4.runtime.data.MapItemValue; +import com.alibaba.qlexpress4.runtime.util.ValueUtils; +import com.alibaba.qlexpress4.utils.PrintlnUtils; + +import java.lang.reflect.Array; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +/** + * Operation: extract value with index, like a[0], m['a'] + * Input: 2, indexable object and index + * Output: 1 + *

+ * Author: DQinYuan + */ +public class IndexInstruction extends QLInstruction { + + public IndexInstruction(ErrorReporter errorReporter) { + super(errorReporter); + } + + @SuppressWarnings("unchecked") + @Override + public QResult execute(QContext qContext, QLOptions qlOptions) { + Object index = qContext.pop().get(); + Object indexAble = qContext.pop().get(); + if (indexAble instanceof List) { + Number indexNumber = ValueUtils.assertType(index, + Number.class, + QLErrorCodes.INVALID_INDEX.name(), + QLErrorCodes.INVALID_INDEX.getErrorMsg(), + errorReporter); + List list = (List)indexAble; + int intIndex = ValueUtils.javaIndex(list.size(), indexNumber.intValue()); + if (intIndex < 0 || intIndex >= list.size()) { + throw errorReporter.report(QLErrorCodes.INDEX_OUT_BOUND.name(), + QLErrorCodes.INDEX_OUT_BOUND.getErrorMsg()); + } + qContext.push(new ListItemValue(list, intIndex)); + } + else if (indexAble instanceof Map) { + qContext.push(new MapItemValue((Map)indexAble, index)); + } + else if (indexAble != null && indexAble.getClass().isArray()) { + Number indexNumber = ValueUtils.assertType(index, + Number.class, + QLErrorCodes.INVALID_INDEX.name(), + QLErrorCodes.INVALID_INDEX.getErrorMsg(), + errorReporter); + int arrLen = Array.getLength(indexAble); + int intIndex = ValueUtils.javaIndex(arrLen, indexNumber.intValue()); + if (intIndex < 0 || intIndex >= arrLen) { + throw errorReporter.report(QLErrorCodes.INDEX_OUT_BOUND.name(), + QLErrorCodes.INDEX_OUT_BOUND.getErrorMsg()); + } + qContext.push(new ArrayItemValue(indexAble, intIndex)); + } + else if (indexAble == null && qlOptions.isAvoidNullPointer()) { + qContext.push(Value.NULL_VALUE); + } + else { + throw errorReporter.reportFormat(QLErrorCodes.NONINDEXABLE_OBJECT.name(), + QLErrorCodes.NONINDEXABLE_OBJECT.getErrorMsg(), + indexAble == null ? "null" : indexAble.getClass().getName()); + } + return QResult.NEXT_INSTRUCTION; + } + + @Override + public int stackInput() { + return 2; + } + + @Override + public int stackOutput() { + return 1; + } + + @Override + public void println(int index, int depth, Consumer debug) { + PrintlnUtils.printlnByCurDepth(depth, index + ": Index", debug); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/instruction/JumpIfInstruction.java b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/JumpIfInstruction.java new file mode 100644 index 0000000..9f1ad83 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/JumpIfInstruction.java @@ -0,0 +1,83 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.exception.QLErrorCodes; +import com.alibaba.qlexpress4.runtime.QContext; +import com.alibaba.qlexpress4.runtime.QResult; +import com.alibaba.qlexpress4.runtime.data.DataValue; +import com.alibaba.qlexpress4.runtime.trace.ExpressionTrace; +import com.alibaba.qlexpress4.utils.PrintlnUtils; + +import java.util.function.Consumer; + +/** + * Operation: if the element is ${expect}, jump to position, else execute next instruction as normal. not jump if null + * Input: 0 + * Output: 0 + * + * Author: DQinYuan + */ +public class JumpIfInstruction extends QLInstruction { + + private final boolean expect; + + private int position; + + private final Integer traceKey; + + public JumpIfInstruction(ErrorReporter errorReporter, boolean expect, int position, Integer traceKey) { + super(errorReporter); + this.expect = expect; + this.position = position; + this.traceKey = traceKey; + } + + @Override + public QResult execute(QContext qContext, QLOptions qlOptions) { + boolean conditionBool = conditionToBool(qContext.peek().get()); + if (conditionBool == expect && !qlOptions.isShortCircuitDisable()) { + // short circuit + // trace + ExpressionTrace expressionTrace = qContext.getTraces().getExpressionTraceByKey(traceKey); + if (expressionTrace != null) { + expressionTrace.valueEvaluated(conditionBool); + expressionTrace.getChildren().get(0).valueEvaluated(conditionBool); + } + return new QResult(new DataValue(position), QResult.ResultType.JUMP); + } + else { + return QResult.NEXT_INSTRUCTION; + } + } + + private boolean conditionToBool(Object condition) { + if (condition == null) { + return !expect; + } + if (!(condition instanceof Boolean)) { + throw errorReporter.report(QLErrorCodes.CONDITION_BOOL_REQUIRED.name(), + QLErrorCodes.CONDITION_BOOL_REQUIRED.getErrorMsg()); + } + return (boolean)condition; + } + + @Override + public int stackInput() { + return 0; + } + + @Override + public int stackOutput() { + return 0; + } + + public void setPosition(int position) { + this.position = position; + } + + @Override + public void println(int index, int depth, Consumer debug) { + PrintlnUtils.printlnByCurDepth(depth, index + ": JumpIf " + expect + " " + position, debug); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/instruction/JumpIfPopInstruction.java b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/JumpIfPopInstruction.java new file mode 100644 index 0000000..b162e0d --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/JumpIfPopInstruction.java @@ -0,0 +1,68 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.exception.QLErrorCodes; +import com.alibaba.qlexpress4.runtime.QContext; +import com.alibaba.qlexpress4.runtime.QResult; +import com.alibaba.qlexpress4.runtime.data.DataValue; +import com.alibaba.qlexpress4.utils.PrintlnUtils; + +import java.util.function.Consumer; + +/** + * Operation: pop the top of stack, if the element is ${expect}, jump to position, else execute next instruction as normal. jump if null + * Input: 1 + * Output: 0 + * + * Author: DQinYuan + */ +public class JumpIfPopInstruction extends QLInstruction { + + private final boolean expect; + + private int position; + + public JumpIfPopInstruction(ErrorReporter errorReporter, boolean expect, int position) { + super(errorReporter); + this.expect = expect; + this.position = position; + } + + @Override + public QResult execute(QContext qContext, QLOptions qlOptions) { + boolean conditionBool = conditionToBool(qContext.pop().get()); + return conditionBool == expect ? new QResult(new DataValue(position), QResult.ResultType.JUMP) + : QResult.NEXT_INSTRUCTION; + } + + private boolean conditionToBool(Object condition) { + if (condition == null) { + return expect; + } + if (!(condition instanceof Boolean)) { + throw errorReporter.report(QLErrorCodes.CONDITION_BOOL_REQUIRED.name(), + QLErrorCodes.CONDITION_BOOL_REQUIRED.getErrorMsg()); + } + return (boolean)condition; + } + + @Override + public int stackInput() { + return 1; + } + + @Override + public int stackOutput() { + return 0; + } + + public void setPosition(int position) { + this.position = position; + } + + @Override + public void println(int index, int depth, Consumer debug) { + PrintlnUtils.printlnByCurDepth(depth, index + ": JumpIfPop " + expect + " " + position, debug); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/instruction/JumpInstruction.java b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/JumpInstruction.java new file mode 100644 index 0000000..a9597c0 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/JumpInstruction.java @@ -0,0 +1,53 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.QContext; +import com.alibaba.qlexpress4.runtime.QResult; +import com.alibaba.qlexpress4.runtime.data.DataValue; +import com.alibaba.qlexpress4.utils.PrintlnUtils; + +import java.util.function.Consumer; + +/** + * Operation: jump to a position + * Input: 0 + * Output: 0 + * + * Author: DQinYuan + */ +public class JumpInstruction extends QLInstruction { + + public static final JumpInstruction INSTANCE = new JumpInstruction(null, -1); + + private int position; + + public JumpInstruction(ErrorReporter errorReporter, int position) { + super(errorReporter); + this.position = position; + } + + @Override + public QResult execute(QContext qContext, QLOptions qlOptions) { + return new QResult(new DataValue(position), QResult.ResultType.JUMP); + } + + @Override + public int stackInput() { + return 0; + } + + @Override + public int stackOutput() { + return 0; + } + + public void setPosition(int position) { + this.position = position; + } + + @Override + public void println(int index, int depth, Consumer debug) { + PrintlnUtils.printlnByCurDepth(depth, index + ": Jump " + position, debug); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/instruction/LoadInstruction.java b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/LoadInstruction.java new file mode 100644 index 0000000..60b942a --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/LoadInstruction.java @@ -0,0 +1,62 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.*; +import com.alibaba.qlexpress4.runtime.trace.ExpressionTrace; +import com.alibaba.qlexpress4.utils.PrintlnUtils; + +import java.util.function.Consumer; + +/** + * Operation: load variable from local to global scope, create when not exist + * Input: 0 + * Output: 1 left value of local variable + * + * Author: DQinYuan + */ +public class LoadInstruction extends QLInstruction { + + private final String name; + + private final Integer traceKey; + + public LoadInstruction(ErrorReporter errorReporter, String name, Integer traceKey) { + super(errorReporter); + this.name = name; + this.traceKey = traceKey; + } + + @Override + public QResult execute(QContext qContext, QLOptions qlOptions) { + Value symbolValue = qContext.getSymbol(name); + qContext.push(symbolValue); + + // trace + ExpressionTrace expressionTrace = qContext.getTraces().getExpressionTraceByKey(traceKey); + if (expressionTrace != null) { + expressionTrace.valueEvaluated(symbolValue.get()); + } + + return QResult.NEXT_INSTRUCTION; + } + + @Override + public int stackInput() { + return 0; + } + + @Override + public int stackOutput() { + return 1; + } + + public String getName() { + return name; + } + + @Override + public void println(int index, int depth, Consumer debug) { + PrintlnUtils.printlnByCurDepth(depth, index + ": Load " + name, debug); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/instruction/LoadLambdaInstruction.java b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/LoadLambdaInstruction.java new file mode 100644 index 0000000..671c687 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/LoadLambdaInstruction.java @@ -0,0 +1,53 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.*; +import com.alibaba.qlexpress4.runtime.data.DataValue; +import com.alibaba.qlexpress4.utils.PrintlnUtils; + +import java.util.function.Consumer; + +/** + * Operation: instantiate lambda definition on stack + * Input: 0 + * Output: 1 lambda instance + * + * Author: DQinYuan + */ +public class LoadLambdaInstruction extends QLInstruction { + + private final QLambdaDefinition lambdaDefinition; + + public LoadLambdaInstruction(ErrorReporter errorReporter, QLambdaDefinition lambdaDefinition) { + super(errorReporter); + this.lambdaDefinition = lambdaDefinition; + } + + @Override + public QResult execute(QContext qContext, QLOptions qlOptions) { + QLambda lambdaInstance = lambdaDefinition.toLambda(qContext, qlOptions, true); + qContext.push(new DataValue(lambdaInstance)); + return QResult.NEXT_INSTRUCTION; + } + + @Override + public int stackInput() { + return 0; + } + + @Override + public int stackOutput() { + return 1; + } + + @Override + public void println(int index, int depth, Consumer debug) { + PrintlnUtils.printlnByCurDepth(depth, index + ": LoadLambda", debug); + lambdaDefinition.println(depth + 1, debug); + } + + public QLambdaDefinition getLambdaDefinition() { + return lambdaDefinition; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/instruction/MethodInvokeInstruction.java b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/MethodInvokeInstruction.java new file mode 100644 index 0000000..eec1f6b --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/MethodInvokeInstruction.java @@ -0,0 +1,82 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.exception.QLErrorCodes; +import com.alibaba.qlexpress4.runtime.*; +import com.alibaba.qlexpress4.runtime.data.DataValue; +import com.alibaba.qlexpress4.runtime.util.MethodInvokeUtils; +import com.alibaba.qlexpress4.utils.PrintlnUtils; + +import java.util.function.Consumer; + +/** + * Operation: invoke specified method of object on the top of stack + * Input: ${argNum} + 1 + * Output: 1, method return value, null for void method + *

+ * equivalent to GetMethodInstruction + CallInstruction + *

+ * Author: DQinYuan + */ +public class MethodInvokeInstruction extends QLInstruction { + + private final String methodName; + + private final int argNum; + + private final boolean optional; + + public MethodInvokeInstruction(ErrorReporter errorReporter, String methodName, int argNum, boolean optional) { + super(errorReporter); + this.methodName = methodName; + this.argNum = argNum; + this.optional = optional; + } + + @Override + public QResult execute(QContext qContext, QLOptions qlOptions) { + Parameters parameters = qContext.pop(this.argNum + 1); + Object bean = parameters.get(0).get(); + Class[] type = new Class[this.argNum]; + Object[] params = new Object[this.argNum]; + for (int i = 0; i < this.argNum; i++) { + Value v = parameters.get(i + 1); + params[i] = v.get(); + type[i] = v.getType(); + } + if (bean == null) { + if (qlOptions.isAvoidNullPointer() || optional) { + qContext.push(DataValue.NULL_VALUE); + return QResult.NEXT_INSTRUCTION; + } + throw errorReporter.report(new NullPointerException(), + QLErrorCodes.NULL_METHOD_ACCESS.name(), + QLErrorCodes.NULL_METHOD_ACCESS.getErrorMsg()); + } + Value invokeRes = MethodInvokeUtils + .findMethodAndInvoke(bean, methodName, params, type, qContext.getReflectLoader(), errorReporter); + qContext.push(invokeRes); + return QResult.NEXT_INSTRUCTION; + } + + @Override + public int stackInput() { + return argNum + 1; + } + + @Override + public int stackOutput() { + return 1; + } + + @Override + public void println(int index, int depth, Consumer debug) { + PrintlnUtils.printlnByCurDepth(depth, index + ": MethodInvoke " + methodName + " with argNum " + argNum, debug); + } + + public String getMethodName() { + return methodName; + } + +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/instruction/MultiNewArrayInstruction.java b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/MultiNewArrayInstruction.java new file mode 100644 index 0000000..9bd226a --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/MultiNewArrayInstruction.java @@ -0,0 +1,71 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.exception.QLErrorCodes; +import com.alibaba.qlexpress4.runtime.Parameters; +import com.alibaba.qlexpress4.runtime.QContext; +import com.alibaba.qlexpress4.runtime.QResult; +import com.alibaba.qlexpress4.runtime.data.DataValue; +import com.alibaba.qlexpress4.utils.PrintlnUtils; + +import java.lang.reflect.Array; +import java.util.function.Consumer; + +/** + * new int[1][2][][] + * Operation: new array with multi dims + * Input: ${dims} + * Output: 1 + * Author: DQinYuan + */ +public class MultiNewArrayInstruction extends QLInstruction { + + private final Class clz; + + private final int dims; + + public MultiNewArrayInstruction(ErrorReporter errorReporter, Class clz, int dims) { + super(errorReporter); + this.clz = clz; + this.dims = dims; + } + + @Override + public QResult execute(QContext qContext, QLOptions qlOptions) { + Parameters dimValues = qContext.pop(dims); + int[] dimArray = new int[dims]; + for (int i = 0; i < dims; i++) { + Object dimValue = dimValues.get(i).get(); + if (!(dimValue instanceof Number)) { + throw errorReporter.reportFormat(QLErrorCodes.ARRAY_SIZE_NUM_REQUIRED.name(), + QLErrorCodes.ARRAY_SIZE_NUM_REQUIRED.getErrorMsg()); + } + int dimLen = ((Number)dimValue).intValue(); + if (!qlOptions.checkArrLen(dimLen)) { + throw errorReporter.reportFormat(QLErrorCodes.EXCEED_MAX_ARR_LENGTH.name(), + QLErrorCodes.EXCEED_MAX_ARR_LENGTH.getErrorMsg(), + dimLen, + qlOptions.getMaxArrLength()); + } + dimArray[i] = dimLen; + } + qContext.push(new DataValue(Array.newInstance(clz, dimArray))); + return QResult.NEXT_INSTRUCTION; + } + + @Override + public int stackInput() { + return dims; + } + + @Override + public int stackOutput() { + return 1; + } + + @Override + public void println(int index, int depth, Consumer debug) { + PrintlnUtils.printlnByCurDepth(depth, index + ": MultiNewArray with dims " + dims, debug); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/instruction/NewArrayInstruction.java b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/NewArrayInstruction.java new file mode 100644 index 0000000..17dd7ea --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/NewArrayInstruction.java @@ -0,0 +1,79 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.exception.QLErrorCodes; +import com.alibaba.qlexpress4.runtime.Parameters; +import com.alibaba.qlexpress4.runtime.QContext; +import com.alibaba.qlexpress4.runtime.QResult; +import com.alibaba.qlexpress4.runtime.data.DataValue; +import com.alibaba.qlexpress4.runtime.data.convert.ObjTypeConvertor; +import com.alibaba.qlexpress4.utils.PrintlnUtils; + +import java.lang.reflect.Array; +import java.util.function.Consumer; + +/** + * new int[] {1,2,3} + * Operation: new array with init items + * Input: ${length} + * Output: 1 + * Author: DQinYuan + */ +public class NewArrayInstruction extends QLInstruction { + + private final Class clz; + + private final int length; + + public NewArrayInstruction(ErrorReporter errorReporter, Class clz, int length) { + super(errorReporter); + this.clz = clz; + this.length = length; + } + + @Override + public QResult execute(QContext qContext, QLOptions qlOptions) { + if (!qlOptions.checkArrLen(length)) { + throw errorReporter.reportFormat(QLErrorCodes.EXCEED_MAX_ARR_LENGTH.name(), + QLErrorCodes.EXCEED_MAX_ARR_LENGTH.getErrorMsg(), + length, + qlOptions.getMaxArrLength()); + } + Object array = Array.newInstance(clz, length); + Parameters initItems = qContext.pop(length); + for (int i = 0; i < initItems.size(); i++) { + Object initItemObj = initItems.get(i).get(); + ObjTypeConvertor.QConverted qlConvertResult = ObjTypeConvertor.cast(initItemObj, clz); + if (!qlConvertResult.isConvertible()) { + throw errorReporter.reportFormat(QLErrorCodes.INCOMPATIBLE_ARRAY_ITEM_TYPE.name(), + QLErrorCodes.INCOMPATIBLE_ARRAY_ITEM_TYPE.getErrorMsg(), + i, + initItemObj == null ? "null" : initItemObj.getClass().getName(), + clz.getName()); + } + Array.set(array, i, qlConvertResult.getConverted()); + } + qContext.push(new DataValue(array)); + return QResult.NEXT_INSTRUCTION; + } + + public Class getClz() { + return clz; + } + + @Override + public int stackInput() { + return length; + } + + @Override + public int stackOutput() { + return 1; + } + + @Override + public void println(int index, int depth, Consumer debug) { + PrintlnUtils.printlnByCurDepth(depth, index + ": NewArray with length " + length, debug); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/instruction/NewFilledInstanceInstruction.java b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/NewFilledInstanceInstruction.java new file mode 100644 index 0000000..7ac6602 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/NewFilledInstanceInstruction.java @@ -0,0 +1,93 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.exception.QLErrorCodes; +import com.alibaba.qlexpress4.runtime.LeftValue; +import com.alibaba.qlexpress4.runtime.Parameters; +import com.alibaba.qlexpress4.runtime.QContext; +import com.alibaba.qlexpress4.runtime.QResult; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.data.DataValue; +import com.alibaba.qlexpress4.utils.PrintlnUtils; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.List; +import java.util.function.Consumer; + +/** + * Operation: new a Map with top ${keys.length} stack element + * Input: ${keys.length} + * Output: 1 + *

+ * Author: DQinYuan + */ +public class NewFilledInstanceInstruction extends QLInstruction { + + private final Class newCls; + + private final List keys; + + public NewFilledInstanceInstruction(ErrorReporter errorReporter, Class newCls, List keys) { + super(errorReporter); + this.newCls = newCls; + this.keys = keys; + } + + @Override + public QResult execute(QContext qContext, QLOptions qlOptions) { + Object instance = newInstance(qContext); + Parameters initItems = qContext.pop(keys.size()); + for (int i = 0; i < keys.size(); i++) { + Object initValue = initItems.get(i).get(); + String fieldName = keys.get(i); + Value fieldValue = qContext.getReflectLoader().loadField(instance, fieldName, false, errorReporter); + if (fieldValue == null) { + // ignore field that don't exist + continue; + } + if (!(fieldValue instanceof LeftValue)) { + throw errorReporter.reportFormat(QLErrorCodes.INVALID_ASSIGNMENT.name(), + QLErrorCodes.INVALID_ASSIGNMENT.getErrorMsg(), + "of field '" + fieldName + "'"); + } + ((LeftValue)fieldValue).set(initValue, errorReporter); + } + qContext.push(new DataValue(instance)); + return QResult.NEXT_INSTRUCTION; + } + + private Object newInstance(QContext qContext) { + Constructor constructor = qContext.getReflectLoader().loadConstructor(newCls, new Class[0]); + try { + return constructor.newInstance(); + } + catch (InvocationTargetException e) { + throw errorReporter.report(e.getTargetException(), + QLErrorCodes.INVOKE_CONSTRUCTOR_INNER_ERROR.name(), + QLErrorCodes.INVOKE_CONSTRUCTOR_INNER_ERROR.getErrorMsg()); + } + catch (Exception e) { + throw errorReporter.report(QLErrorCodes.INVOKE_CONSTRUCTOR_UNKNOWN_ERROR.name(), + QLErrorCodes.INVOKE_CONSTRUCTOR_UNKNOWN_ERROR.getErrorMsg()); + } + } + + @Override + public int stackInput() { + return keys.size(); + } + + @Override + public int stackOutput() { + return 1; + } + + @Override + public void println(int index, int depth, Consumer debug) { + PrintlnUtils.printlnByCurDepth(depth, + index + ": New instace of cls " + newCls.getSimpleName() + " with fields " + keys, + debug); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/instruction/NewInstanceInstruction.java b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/NewInstanceInstruction.java new file mode 100644 index 0000000..9e26aab --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/NewInstanceInstruction.java @@ -0,0 +1,92 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.exception.QLErrorCodes; +import com.alibaba.qlexpress4.runtime.*; +import com.alibaba.qlexpress4.runtime.data.DataValue; +import com.alibaba.qlexpress4.runtime.data.convert.ParametersTypeConvertor; +import com.alibaba.qlexpress4.utils.PrintlnUtils; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; +import java.util.function.Consumer; + +/** + * Operation: new an object of specified class + * Input: ${argNum} + 1 + * Output: 1 + *

+ * Author: DQinYuan + */ +public class NewInstanceInstruction extends QLInstruction { + + private final Class newClz; + + private final int argNum; + + public NewInstanceInstruction(ErrorReporter errorReporter, Class newClz, int argNum) { + super(errorReporter); + this.newClz = newClz; + this.argNum = argNum; + } + + @Override + public QResult execute(QContext qContext, QLOptions qlOptions) { + Parameters parameters = this.argNum == 0 ? null : qContext.pop(this.argNum); + Class[] paramTypes = new Class[this.argNum]; + Object[] objs = new Object[this.argNum]; + + for (int i = 0; i < this.argNum; i++) { + Value v = parameters.get(i); + objs[i] = v.get(); + paramTypes[i] = v.getType(); + } + ReflectLoader reflectLoader = qContext.getReflectLoader(); + Constructor constructor = reflectLoader.loadConstructor(newClz, paramTypes); + if (constructor == null) { + throw errorReporter.reportFormat(QLErrorCodes.NO_SUITABLE_CONSTRUCTOR.name(), + QLErrorCodes.NO_SUITABLE_CONSTRUCTOR.getErrorMsg(), + Arrays.toString(paramTypes)); + } + Object[] convertResult = + ParametersTypeConvertor.cast(objs, constructor.getParameterTypes(), constructor.isVarArgs()); + Object newObject = newObject(constructor, convertResult); + Value dataInstruction = new DataValue(newObject); + qContext.push(dataInstruction); + return QResult.NEXT_INSTRUCTION; + } + + private Object newObject(Constructor constructor, Object[] params) { + try { + return constructor.newInstance(params); + } + catch (InvocationTargetException e) { + throw errorReporter.report(e.getTargetException(), + QLErrorCodes.INVOKE_CONSTRUCTOR_INNER_ERROR.name(), + QLErrorCodes.INVOKE_CONSTRUCTOR_INNER_ERROR.getErrorMsg()); + } + catch (Exception e) { + throw errorReporter.report(QLErrorCodes.INVOKE_CONSTRUCTOR_UNKNOWN_ERROR.name(), + QLErrorCodes.INVOKE_CONSTRUCTOR_UNKNOWN_ERROR.getErrorMsg()); + } + } + + @Override + public int stackInput() { + return argNum; + } + + @Override + public int stackOutput() { + return 1; + } + + @Override + public void println(int index, int depth, Consumer debug) { + PrintlnUtils.printlnByCurDepth(depth, + index + ": New instance of cls " + newClz.getSimpleName() + " with argNum " + argNum, + debug); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/instruction/NewListInstruction.java b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/NewListInstruction.java new file mode 100644 index 0000000..274841a --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/NewListInstruction.java @@ -0,0 +1,56 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.Parameters; +import com.alibaba.qlexpress4.runtime.QContext; +import com.alibaba.qlexpress4.runtime.QResult; +import com.alibaba.qlexpress4.runtime.data.DataValue; +import com.alibaba.qlexpress4.utils.PrintlnUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +/** + * Operation: new a List with top ${initLength} stack element + * Input: ${initLength} + * Output: 1 + *

+ * Author: DQinYuan + */ +public class NewListInstruction extends QLInstruction { + + private final int initLength; + + public NewListInstruction(ErrorReporter errorReporter, int initLength) { + super(errorReporter); + this.initLength = initLength; + } + + @Override + public QResult execute(QContext qContext, QLOptions qlOptions) { + Parameters initItems = qContext.pop(initLength); + List l = new ArrayList<>(initLength); + for (int i = 0; i < initLength; i++) { + l.add(initItems.getValue(i)); + } + qContext.push(new DataValue(l)); + return QResult.NEXT_INSTRUCTION; + } + + @Override + public int stackInput() { + return initLength; + } + + @Override + public int stackOutput() { + return 1; + } + + @Override + public void println(int index, int depth, Consumer debug) { + PrintlnUtils.printlnByCurDepth(depth, index + ": NewList " + initLength, debug); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/instruction/NewMapInstruction.java b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/NewMapInstruction.java new file mode 100644 index 0000000..7aad124 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/NewMapInstruction.java @@ -0,0 +1,57 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.Parameters; +import com.alibaba.qlexpress4.runtime.QContext; +import com.alibaba.qlexpress4.runtime.QResult; +import com.alibaba.qlexpress4.runtime.data.DataValue; +import com.alibaba.qlexpress4.utils.PrintlnUtils; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +/** + * Operation: new a Map with top ${keys.length} stack element + * Input: ${keys.length} + * Output: 1 + *

+ * Author: DQinYuan + */ +public class NewMapInstruction extends QLInstruction { + + private final List keys; + + public NewMapInstruction(ErrorReporter errorReporter, List keys) { + super(errorReporter); + this.keys = keys; + } + + @Override + public QResult execute(QContext qContext, QLOptions qlOptions) { + Parameters initItems = qContext.pop(keys.size()); + Map m = new LinkedHashMap<>(); + for (int i = 0; i < keys.size(); i++) { + m.put(keys.get(i), initItems.get(i).get()); + } + qContext.push(new DataValue(m)); + return QResult.NEXT_INSTRUCTION; + } + + @Override + public int stackInput() { + return keys.size(); + } + + @Override + public int stackOutput() { + return 1; + } + + @Override + public void println(int index, int depth, Consumer debug) { + PrintlnUtils.printlnByCurDepth(depth, index + ": NewMap by keys " + keys, debug); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/instruction/NewScopeInstruction.java b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/NewScopeInstruction.java new file mode 100644 index 0000000..27f9592 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/NewScopeInstruction.java @@ -0,0 +1,47 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.QContext; +import com.alibaba.qlexpress4.runtime.QResult; +import com.alibaba.qlexpress4.utils.PrintlnUtils; + +import java.util.function.Consumer; + +/** + * Operation: new scope + * Input: 0 + * Output: 0 + * + * Author: DQinYuan + */ +public class NewScopeInstruction extends QLInstruction { + + private final String scopeName; + + public NewScopeInstruction(ErrorReporter errorReporter, String scopeName) { + super(errorReporter); + this.scopeName = scopeName; + } + + @Override + public QResult execute(QContext qContext, QLOptions qlOptions) { + qContext.newScope(); + return QResult.NEXT_INSTRUCTION; + } + + @Override + public int stackInput() { + return 0; + } + + @Override + public int stackOutput() { + return 0; + } + + @Override + public void println(int index, int depth, Consumer debug) { + PrintlnUtils.printlnByCurDepth(depth, index + ": NewScope " + scopeName, debug); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/instruction/OperatorInstruction.java b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/OperatorInstruction.java new file mode 100644 index 0000000..17c9c02 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/OperatorInstruction.java @@ -0,0 +1,87 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import java.util.function.Consumer; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.exception.QLErrorCodes; +import com.alibaba.qlexpress4.runtime.QContext; +import com.alibaba.qlexpress4.runtime.QResult; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.data.DataValue; +import com.alibaba.qlexpress4.runtime.operator.BinaryOperator; +import com.alibaba.qlexpress4.runtime.trace.ExpressionTrace; +import com.alibaba.qlexpress4.runtime.util.ThrowUtils; +import com.alibaba.qlexpress4.utils.PrintlnUtils; + +/** + * Operation: do middle operator +=,>>,>>>,<<,. + * Input: 2 + * Output: 1, operator result + *

+ * Author: DQinYuan + */ +public class OperatorInstruction extends QLInstruction { + + private final BinaryOperator operator; + + private final Integer traceKey; + + public OperatorInstruction(ErrorReporter errorReporter, BinaryOperator operator, Integer traceKey) { + super(errorReporter); + this.operator = operator; + this.traceKey = traceKey; + } + + @Override + public QResult execute(QContext qContext, QLOptions qlOptions) { + Value rightValue = qContext.pop(); + Value leftValue = qContext.pop(); + try { + // trace children + ExpressionTrace expressionTrace = qContext.getTraces().getExpressionTraceByKey(traceKey); + if (expressionTrace != null) { + expressionTrace.getChildren().get(0).valueEvaluated(leftValue.get()); + expressionTrace.getChildren().get(1).valueEvaluated(rightValue.get()); + } + + Object result = operator.execute(leftValue, rightValue, qContext, qlOptions, errorReporter); + qContext.push(new DataValue(result)); + + // trace result + if (expressionTrace != null) { + expressionTrace.valueEvaluated(result); + } + + return QResult.NEXT_INSTRUCTION; + } + catch (Throwable t) { + throw ThrowUtils.wrapThrowable(t, + errorReporter, + QLErrorCodes.EXECUTE_OPERATOR_EXCEPTION.name(), + QLErrorCodes.EXECUTE_OPERATOR_EXCEPTION.getErrorMsg(), + String.valueOf(leftValue.get()), + operator.getOperator(), + String.valueOf(rightValue.get())); + } + } + + @Override + public int stackInput() { + return 2; + } + + @Override + public int stackOutput() { + return 1; + } + + @Override + public void println(int index, int depth, Consumer debug) { + PrintlnUtils.printlnByCurDepth(depth, index + ": Operator " + operator.getOperator(), debug); + } + + public BinaryOperator getOperator() { + return operator; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/instruction/PopInstruction.java b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/PopInstruction.java new file mode 100644 index 0000000..5439c41 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/PopInstruction.java @@ -0,0 +1,43 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.QContext; +import com.alibaba.qlexpress4.runtime.QResult; +import com.alibaba.qlexpress4.utils.PrintlnUtils; + +import java.util.function.Consumer; + +/** + * Operation: pop top element + * Input: 1 + * Output: 0 + *

+ * Author: DQinYuan + */ +public class PopInstruction extends QLInstruction { + public PopInstruction(ErrorReporter errorReporter) { + super(errorReporter); + } + + @Override + public QResult execute(QContext qContext, QLOptions qlOptions) { + qContext.pop(); + return QResult.NEXT_INSTRUCTION; + } + + @Override + public int stackInput() { + return 1; + } + + @Override + public int stackOutput() { + return 0; + } + + @Override + public void println(int index, int depth, Consumer debug) { + PrintlnUtils.printlnByCurDepth(depth, index + ": Pop", debug); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/instruction/QLInstruction.java b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/QLInstruction.java new file mode 100644 index 0000000..e5807f5 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/QLInstruction.java @@ -0,0 +1,43 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.QContext; +import com.alibaba.qlexpress4.runtime.QResult; +import java.util.function.Consumer; + +/** + * Instruction Specification: + * + * Operation: What does it do? + * Input: How many stack element it consumes? and their means + * Output: How many stack element it push back? and their means + *

+ * Author: DQinYuan + */ +public abstract class QLInstruction { + + protected final ErrorReporter errorReporter; + + public QLInstruction(ErrorReporter errorReporter) { + this.errorReporter = errorReporter; + } + + public abstract QResult execute(QContext qContext, QLOptions qlOptions); + + /** + * @return input size + */ + public abstract int stackInput(); + + /** + * @return output size + */ + public abstract int stackOutput(); + + public abstract void println(int index, int depth, Consumer debug); + + public ErrorReporter getErrorReporter() { + return errorReporter; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/instruction/ReturnInstruction.java b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/ReturnInstruction.java new file mode 100644 index 0000000..3831b39 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/ReturnInstruction.java @@ -0,0 +1,59 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import java.util.function.Consumer; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.QContext; +import com.alibaba.qlexpress4.runtime.QResult; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.data.DataValue; +import com.alibaba.qlexpress4.runtime.trace.ExpressionTrace; +import com.alibaba.qlexpress4.utils.PrintlnUtils; + +/** + * Operation: return top element and exit lambda + * Input: 1 + * Output: 0 + *

+ * Author: DQinYuan + */ +public class ReturnInstruction extends QLInstruction { + + private final QResult.ResultType resultType; + + private final Integer traceKey; + + public ReturnInstruction(ErrorReporter errorReporter, QResult.ResultType resultType, Integer traceKey) { + super(errorReporter); + this.resultType = resultType; + this.traceKey = traceKey; + } + + @Override + public QResult execute(QContext qContext, QLOptions qlOptions) { + Value returnValue = qContext.pop(); + if (traceKey != null) { + ExpressionTrace expressionTrace = qContext.getTraces().getExpressionTraceByKey(traceKey); + if (expressionTrace != null) { + expressionTrace.valueEvaluated(returnValue.get()); + } + } + return new QResult(new DataValue(returnValue.get()), resultType); + } + + @Override + public int stackInput() { + return 1; + } + + @Override + public int stackOutput() { + return 0; + } + + @Override + public void println(int index, int depth, Consumer debug) { + PrintlnUtils.printlnByCurDepth(depth, index + ": Return", debug); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/instruction/SliceInstruction.java b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/SliceInstruction.java new file mode 100644 index 0000000..b7858a8 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/SliceInstruction.java @@ -0,0 +1,176 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.exception.QLErrorCodes; +import com.alibaba.qlexpress4.runtime.QContext; +import com.alibaba.qlexpress4.runtime.QResult; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.data.DataValue; +import com.alibaba.qlexpress4.runtime.util.ValueUtils; +import com.alibaba.qlexpress4.utils.PrintlnUtils; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +/** + * Operation: slice array or list, like a[2:4], a[4:-1], a[:4], a[5:], a[:] + * Input: 0-2 + * Output: 1 + *

+ * Author: DQinYuan + */ +public class SliceInstruction extends QLInstruction { + + public enum Mode { + LEFT, RIGHT, BOTH, COPY + } + + private final Mode mode; + + public SliceInstruction(ErrorReporter errorReporter, Mode mode) { + super(errorReporter); + this.mode = mode; + } + + @Override + public QResult execute(QContext qContext, QLOptions qlOptions) { + int startInt = 0; + int endInt = 0; + Object indexAble = null; + if (mode == Mode.BOTH) { + Object end = qContext.pop().get(); + Object start = qContext.pop().get(); + indexAble = qContext.pop().get(); + startInt = + ValueUtils + .assertType(start, + Number.class, + QLErrorCodes.INVALID_INDEX.name(), + QLErrorCodes.INVALID_INDEX.getErrorMsg(), + errorReporter) + .intValue(); + endInt = + ValueUtils + .assertType(end, + Number.class, + QLErrorCodes.INVALID_INDEX.name(), + QLErrorCodes.INVALID_INDEX.getErrorMsg(), + errorReporter) + .intValue(); + } + else if (mode == Mode.LEFT) { + Object end = qContext.pop().get(); + indexAble = qContext.pop().get(); + endInt = + ValueUtils + .assertType(end, + Number.class, + QLErrorCodes.INVALID_INDEX.name(), + QLErrorCodes.INVALID_INDEX.getErrorMsg(), + errorReporter) + .intValue(); + } + else if (mode == Mode.RIGHT) { + Object start = qContext.pop().get(); + indexAble = qContext.pop().get(); + startInt = + ValueUtils + .assertType(start, + Number.class, + QLErrorCodes.INVALID_INDEX.name(), + QLErrorCodes.INVALID_INDEX.getErrorMsg(), + errorReporter) + .intValue(); + endInt = indexAbleLen(indexAble); + } + else if (mode == Mode.COPY) { + indexAble = qContext.pop().get(); + endInt = indexAbleLen(indexAble); + } + + if (indexAble instanceof List) { + List result = listSlice((List)indexAble, startInt, endInt); + qContext.push(new DataValue(result)); + return QResult.NEXT_INSTRUCTION; + } + else if (indexAble != null && indexAble.getClass().isArray()) { + Object result = arraySlice(indexAble, startInt, endInt); + qContext.push(new DataValue(result)); + return QResult.NEXT_INSTRUCTION; + } + else if (indexAble == null && qlOptions.isAvoidNullPointer()) { + qContext.push(Value.NULL_VALUE); + return QResult.NEXT_INSTRUCTION; + } + else { + throw errorReporter.reportFormat(QLErrorCodes.NONINDEXABLE_OBJECT.name(), + QLErrorCodes.NONINDEXABLE_OBJECT.getErrorMsg(), + indexAble == null ? "null" : indexAble.getClass().getName()); + } + } + + private int indexAbleLen(Object indexAble) { + if (indexAble instanceof List) { + return ((List)indexAble).size(); + } + else if (indexAble != null && indexAble.getClass().isArray()) { + return Array.getLength(indexAble); + } + else { + throw errorReporter.reportFormat(QLErrorCodes.NONINDEXABLE_OBJECT.name(), + QLErrorCodes.NONINDEXABLE_OBJECT.getErrorMsg(), + indexAble == null ? "null" : indexAble.getClass().getName()); + } + } + + private List listSlice(List listObj, int originStart, int originEnd) { + int start = Math.max(ValueUtils.javaIndex(listObj.size(), originStart), 0); + int end = Math.min(ValueUtils.javaIndex(listObj.size(), originEnd), listObj.size()); + if (start >= end) { + return new ArrayList<>(0); + } + + return listObj.subList(start, end); + } + + private Object arraySlice(Object arrObj, int originStart, int originEnd) { + int arrLen = Array.getLength(arrObj); + int start = Math.max(ValueUtils.javaIndex(arrLen, originStart), 0); + int end = Math.min(ValueUtils.javaIndex(arrLen, originEnd), arrLen); + if (start >= end) { + return Array.newInstance(arrObj.getClass().getComponentType(), 0); + } + + Object newArr = Array.newInstance(arrObj.getClass().getComponentType(), end - start); + for (int i = start; i < end; i++) { + Array.set(newArr, i - start, Array.get(arrObj, i)); + } + return newArr; + } + + @Override + public int stackInput() { + if (mode == Mode.BOTH) { + return 2; + } + else if (mode == Mode.COPY) { + return 0; + } + else { + return 1; + } + } + + @Override + public int stackOutput() { + return 1; + } + + @Override + public void println(int index, int depth, Consumer debug) { + PrintlnUtils.printlnByCurDepth(depth, index + ": Slice", debug); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/instruction/SpreadGetFieldInstruction.java b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/SpreadGetFieldInstruction.java new file mode 100644 index 0000000..07c6a10 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/SpreadGetFieldInstruction.java @@ -0,0 +1,136 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.exception.QLErrorCodes; +import com.alibaba.qlexpress4.runtime.QContext; +import com.alibaba.qlexpress4.runtime.QResult; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.data.DataValue; +import com.alibaba.qlexpress4.utils.PrintlnUtils; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +/** + * Operation: get field of each object in the list + * Input: 1 + * Output: 1, a list composed of field values + * Author: DQinYuan + */ +public class SpreadGetFieldInstruction extends QLInstruction { + + private static final String KEY = "key"; + + private static final String VALUE = "value"; + + private final String fieldName; + + public SpreadGetFieldInstruction(ErrorReporter errorReporter, String fieldName) { + super(errorReporter); + this.fieldName = fieldName; + } + + @Override + public QResult execute(QContext qContext, QLOptions qlOptions) { + Object traversable = qContext.pop().get(); + if (traversable == null) { + if (qlOptions.isAvoidNullPointer()) { + qContext.push(DataValue.NULL_VALUE); + return QResult.NEXT_INSTRUCTION; + } + throw errorReporter.reportFormat(QLErrorCodes.NONTRAVERSABLE_OBJECT.name(), + QLErrorCodes.NONTRAVERSABLE_OBJECT.getErrorMsg(), + "null"); + } + if (traversable instanceof Iterable) { + Iterable iterable = (Iterable)traversable; + List result = new ArrayList<>(); + for (Object item : iterable) { + if (item == null) { + if (qlOptions.isAvoidNullPointer()) { + result.add(null); + continue; + } + throw errorReporter.report(new NullPointerException(), + QLErrorCodes.NULL_FIELD_ACCESS.name(), + QLErrorCodes.NULL_FIELD_ACCESS.getErrorMsg()); + } + Value fieldValue = qContext.getReflectLoader().loadField(item, fieldName, false, errorReporter); + if (fieldValue == null) { + throw errorReporter.reportFormat(QLErrorCodes.FIELD_NOT_FOUND.name(), + QLErrorCodes.FIELD_NOT_FOUND.getErrorMsg(), + fieldName); + } + result.add(fieldValue.get()); + } + qContext.push(new DataValue(result)); + } + else if (traversable instanceof Map) { + Map lhm = (Map)traversable; + List result = new ArrayList<>(); + for (Map.Entry entry : lhm.entrySet()) { + if (KEY.equals(fieldName)) { + result.add(entry.getKey()); + } + else if (VALUE.equals(fieldName)) { + result.add(entry.getValue()); + } + else { + throw errorReporter.reportFormat(QLErrorCodes.FIELD_NOT_FOUND.name(), + QLErrorCodes.FIELD_NOT_FOUND.getErrorMsg(), + fieldName); + } + } + qContext.push(new DataValue(result)); + } + else if (traversable.getClass().isArray()) { + int arrLen = Array.getLength(traversable); + List result = new ArrayList<>(); + for (int i = 0; i < arrLen; i++) { + Object item = Array.get(traversable, i); + if (item == null) { + if (qlOptions.isAvoidNullPointer()) { + result.add(null); + continue; + } + throw errorReporter.report(new NullPointerException(), + QLErrorCodes.NULL_FIELD_ACCESS.name(), + QLErrorCodes.NULL_FIELD_ACCESS.getErrorMsg()); + } + Value fieldValue = qContext.getReflectLoader().loadField(item, fieldName, false, errorReporter); + if (fieldValue == null) { + throw errorReporter.reportFormat(QLErrorCodes.FIELD_NOT_FOUND.name(), + QLErrorCodes.FIELD_NOT_FOUND.getErrorMsg(), + fieldName); + } + result.add(fieldValue.get()); + } + qContext.push(new DataValue(result)); + } + else { + throw errorReporter.reportFormat(QLErrorCodes.NONTRAVERSABLE_OBJECT.name(), + QLErrorCodes.NONTRAVERSABLE_OBJECT.getErrorMsg(), + traversable.getClass().getName()); + } + return QResult.NEXT_INSTRUCTION; + } + + @Override + public int stackInput() { + return 1; + } + + @Override + public int stackOutput() { + return 1; + } + + @Override + public void println(int index, int depth, Consumer debug) { + PrintlnUtils.printlnByCurDepth(depth, index + ": SpreadGetField " + fieldName, debug); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/instruction/SpreadMethodInvokeInstruction.java b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/SpreadMethodInvokeInstruction.java new file mode 100644 index 0000000..5bb73ce --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/SpreadMethodInvokeInstruction.java @@ -0,0 +1,119 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.exception.QLErrorCodes; +import com.alibaba.qlexpress4.runtime.Parameters; +import com.alibaba.qlexpress4.runtime.QContext; +import com.alibaba.qlexpress4.runtime.QResult; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.data.DataValue; +import com.alibaba.qlexpress4.runtime.util.MethodInvokeUtils; +import com.alibaba.qlexpress4.utils.PrintlnUtils; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +/** + * Operation: Invoke specified method of each object in the list + * Input: ${argNum} + 1 + * Output: 1, a list composed of return values from methods. + * Author: DQinYuan + */ +public class SpreadMethodInvokeInstruction extends QLInstruction { + + private final String methodName; + + private final int argNum; + + public SpreadMethodInvokeInstruction(ErrorReporter errorReporter, String methodName, int argNum) { + super(errorReporter); + this.methodName = methodName; + this.argNum = argNum; + } + + @Override + public QResult execute(QContext qContext, QLOptions qlOptions) { + Parameters parameters = qContext.pop(this.argNum + 1); + Object traversable = parameters.get(0).get(); + if (traversable == null) { + if (qlOptions.isAvoidNullPointer()) { + qContext.push(DataValue.NULL_VALUE); + return QResult.NEXT_INSTRUCTION; + } + throw errorReporter.reportFormat(QLErrorCodes.NONTRAVERSABLE_OBJECT.name(), + QLErrorCodes.NONTRAVERSABLE_OBJECT.getErrorMsg(), + "null"); + } + Class[] type = new Class[this.argNum]; + Object[] params = new Object[this.argNum]; + for (int i = 0; i < this.argNum; i++) { + Value v = parameters.get(i + 1); + params[i] = v.get(); + type[i] = v.getType(); + } + + if (traversable instanceof Iterable) { + Iterable iterable = (Iterable)traversable; + List result = new ArrayList<>(); + for (Object item : iterable) { + if (item == null) { + if (qlOptions.isAvoidNullPointer()) { + result.add(null); + continue; + } + throw errorReporter.report(new NullPointerException(), + QLErrorCodes.NULL_METHOD_ACCESS.name(), + QLErrorCodes.NULL_METHOD_ACCESS.getErrorMsg()); + } + Value invokeRes = MethodInvokeUtils + .findMethodAndInvoke(item, methodName, params, type, qContext.getReflectLoader(), errorReporter); + result.add(invokeRes.get()); + } + qContext.push(new DataValue(result)); + } + else if (traversable.getClass().isArray()) { + int arrLen = Array.getLength(traversable); + List result = new ArrayList<>(); + for (int i = 0; i < arrLen; i++) { + Object item = Array.get(traversable, i); + if (item == null) { + if (qlOptions.isAvoidNullPointer()) { + result.add(null); + continue; + } + throw errorReporter.report(new NullPointerException(), + QLErrorCodes.NULL_METHOD_ACCESS.name(), + QLErrorCodes.NULL_METHOD_ACCESS.getErrorMsg()); + } + Value invokeRes = MethodInvokeUtils + .findMethodAndInvoke(item, methodName, params, type, qContext.getReflectLoader(), errorReporter); + result.add(invokeRes.get()); + } + qContext.push(new DataValue(result)); + } + else { + throw errorReporter.reportFormat(QLErrorCodes.NONTRAVERSABLE_OBJECT.name(), + QLErrorCodes.NONTRAVERSABLE_OBJECT.getErrorMsg(), + traversable.getClass().getName()); + } + return QResult.NEXT_INSTRUCTION; + } + + @Override + public int stackInput() { + return argNum + 1; + } + + @Override + public int stackOutput() { + return 1; + } + + @Override + public void println(int index, int depth, Consumer debug) { + PrintlnUtils.printlnByCurDepth(depth, index + ": SpreadMethodInvoke " + methodName, debug); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/instruction/StringJoinInstruction.java b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/StringJoinInstruction.java new file mode 100644 index 0000000..66a56b1 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/StringJoinInstruction.java @@ -0,0 +1,58 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.Parameters; +import com.alibaba.qlexpress4.runtime.QContext; +import com.alibaba.qlexpress4.runtime.QResult; +import com.alibaba.qlexpress4.runtime.data.DataValue; +import com.alibaba.qlexpress4.utils.PrintlnUtils; + +import java.util.function.Consumer; + +/** + * Operation: concat n string on the top of stack + * Input: ${n} + * Output: concat result + * + * Author: DQinYuan + */ +public class StringJoinInstruction extends QLInstruction { + + private final int n; + + public StringJoinInstruction(ErrorReporter errorReporter, int n) { + super(errorReporter); + this.n = n; + } + + @Override + public QResult execute(QContext qContext, QLOptions qlOptions) { + Parameters arguments = qContext.pop(n); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < n; i++) { + sb.append(arguments.get(i).get()); + } + qContext.push(new DataValue(sb.toString())); + return QResult.NEXT_INSTRUCTION; + } + + public int getN() { + return n; + } + + @Override + public int stackInput() { + return n; + } + + @Override + public int stackOutput() { + return 1; + } + + @Override + public void println(int index, int depth, Consumer debug) { + PrintlnUtils.printlnByCurDepth(depth, index + ": StringJoin " + n, debug); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/instruction/ThrowInstruction.java b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/ThrowInstruction.java new file mode 100644 index 0000000..157598e --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/ThrowInstruction.java @@ -0,0 +1,44 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.exception.QLErrorCodes; +import com.alibaba.qlexpress4.runtime.QContext; +import com.alibaba.qlexpress4.runtime.QResult; +import com.alibaba.qlexpress4.utils.PrintlnUtils; + +import java.util.function.Consumer; + +/** + * Operation: throw top element on the stack + * Input: 1 + * Output: 0 + *

+ * Author: DQinYuan + */ +public class ThrowInstruction extends QLInstruction { + public ThrowInstruction(ErrorReporter errorReporter) { + super(errorReporter); + } + + @Override + public QResult execute(QContext qContext, QLOptions qlOptions) { + Object throwObj = qContext.pop().get(); + throw errorReporter.report(throwObj, QLErrorCodes.QL_THROW.name(), QLErrorCodes.QL_THROW.getErrorMsg()); + } + + @Override + public int stackInput() { + return 1; + } + + @Override + public int stackOutput() { + return 0; + } + + @Override + public void println(int index, int depth, Consumer debug) { + PrintlnUtils.printlnByCurDepth(depth, index + ": Throw", debug); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/instruction/TraceEvaludatedInstruction.java b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/TraceEvaludatedInstruction.java new file mode 100644 index 0000000..36f7b4e --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/TraceEvaludatedInstruction.java @@ -0,0 +1,51 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import java.util.function.Consumer; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.QContext; +import com.alibaba.qlexpress4.runtime.QResult; +import com.alibaba.qlexpress4.runtime.trace.ExpressionTrace; +import com.alibaba.qlexpress4.utils.PrintlnUtils; + +/** + * Operation: no op, only for marking evaludated as true + * Input: 0 + * Output: 0 + * + * Author: DQinYuan + */ +public class TraceEvaludatedInstruction extends QLInstruction { + + private final Integer traceKey; + + public TraceEvaludatedInstruction(ErrorReporter errorReporter, Integer traceKey) { + super(errorReporter); + this.traceKey = traceKey; + } + + @Override + public QResult execute(QContext qContext, QLOptions qlOptions) { + ExpressionTrace expressionTrace = qContext.getTraces().getExpressionTraceByKey(traceKey); + if (expressionTrace != null) { + expressionTrace.valueEvaluated(null); + } + return QResult.NEXT_INSTRUCTION; + } + + @Override + public int stackInput() { + return 0; + } + + @Override + public int stackOutput() { + return 0; + } + + @Override + public void println(int index, int depth, Consumer debug) { + PrintlnUtils.printlnByCurDepth(depth, index + ": TraceEvaludated " + traceKey, debug); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/instruction/TracePeekInstruction.java b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/TracePeekInstruction.java new file mode 100644 index 0000000..273022d --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/TracePeekInstruction.java @@ -0,0 +1,51 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.QContext; +import com.alibaba.qlexpress4.runtime.QResult; +import com.alibaba.qlexpress4.runtime.trace.ExpressionTrace; +import com.alibaba.qlexpress4.utils.PrintlnUtils; + +import java.util.function.Consumer; + +/** + * Operation: no op, only for tracing peek value of stack + * Input: 0 + * Output: 0 + * + * Author: DQinYuan + */ +public class TracePeekInstruction extends QLInstruction { + + private final Integer traceKey; + + public TracePeekInstruction(ErrorReporter errorReporter, Integer traceKey) { + super(errorReporter); + this.traceKey = traceKey; + } + + @Override + public QResult execute(QContext qContext, QLOptions qlOptions) { + ExpressionTrace expressionTrace = qContext.getTraces().getExpressionTraceByKey(traceKey); + if (expressionTrace != null) { + expressionTrace.valueEvaluated(qContext.peek().get()); + } + return QResult.NEXT_INSTRUCTION; + } + + @Override + public int stackInput() { + return 0; + } + + @Override + public int stackOutput() { + return 0; + } + + @Override + public void println(int index, int depth, Consumer debug) { + PrintlnUtils.printlnByCurDepth(depth, index + ": TracePeek " + traceKey, debug); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/instruction/TryCatchInstruction.java b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/TryCatchInstruction.java new file mode 100644 index 0000000..0c88d74 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/TryCatchInstruction.java @@ -0,0 +1,148 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.exception.QLErrorCodes; +import com.alibaba.qlexpress4.exception.QLRuntimeException; +import com.alibaba.qlexpress4.runtime.*; +import com.alibaba.qlexpress4.runtime.util.ThrowUtils; +import com.alibaba.qlexpress4.runtime.util.ValueUtils; +import com.alibaba.qlexpress4.utils.PrintlnUtils; + +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +/** + * Operation: try and catch throw element + * Input: 0 + * Output: 1 + * + * Author: DQinYuan + */ +public class TryCatchInstruction extends QLInstruction { + + private final QLambdaDefinition body; + + private final List, QLambdaDefinition>> exceptionTable; + + private final QLambdaDefinition finalBody; + + public TryCatchInstruction(ErrorReporter errorReporter, QLambdaDefinition body, + List, QLambdaDefinition>> exceptionTable, QLambdaDefinition finalBody) { + super(errorReporter); + this.body = body; + this.exceptionTable = exceptionTable; + this.finalBody = finalBody; + } + + @Override + public QResult execute(QContext qContext, QLOptions qlOptions) { + QResult tryCatchResult = tryCatchResult(qContext, qlOptions); + Value resultValue = tryCatchResult.getResult(); + qContext.push(ValueUtils.toImmutable(resultValue)); + + if (finalBody != null) { + callFinal(qContext, qlOptions); + } + if (tryCatchResult.getResultType() == QResult.ResultType.RETURN) { + return tryCatchResult; + } + return QResult.NEXT_INSTRUCTION; + } + + @Override + public int stackInput() { + return 0; + } + + @Override + public int stackOutput() { + return 1; + } + + @Override + public void println(int index, int depth, Consumer debug) { + PrintlnUtils.printlnByCurDepth(depth, index + ": TryCatch", debug); + PrintlnUtils.printlnByCurDepth(depth + 1, "Body", debug); + body.println(depth + 2, debug); + for (Map.Entry, QLambdaDefinition> clsLambdaEn : exceptionTable) { + PrintlnUtils.printlnByCurDepth(depth + 1, clsLambdaEn.getKey().getSimpleName(), debug); + clsLambdaEn.getValue().println(depth + 2, debug); + } + if (finalBody != null) { + PrintlnUtils.printlnByCurDepth(depth + 1, "Finally", debug); + finalBody.println(depth + 2, debug); + } + } + + private void callFinal(QContext qContext, QLOptions qlOptions) { + QLambda finalLambda = finalBody.toLambda(qContext, qlOptions, true); + try { + finalLambda.call(); + } + catch (Throwable t) { + throw ThrowUtils.wrapThrowable(t, + errorReporter, + QLErrorCodes.EXECUTE_FINAL_BLOCK_ERROR.name(), + QLErrorCodes.EXECUTE_FINAL_BLOCK_ERROR.getErrorMsg()); + } + } + + private QResult tryCatchResult(QContext qContext, QLOptions qlOptions) { + try { + QLambda bodyLambda = body.toLambda(qContext, qlOptions, true); + return bodyLambda.call(); + } + catch (QLRuntimeException e) { + Object catchObj = e.getCatchObj(); + if (catchObj == null) { + // ensure catch object can catch all exception + catchObj = new Object(); + } + QResult result = callExceptionHandler(catchObj, qContext, qlOptions); + if (result == null) { + throw e; + } + return result; + } + catch (Throwable t) { + QResult result = callExceptionHandler(t, qContext, qlOptions); + if (result == null) { + throw ThrowUtils.wrapThrowable(t, + errorReporter, + QLErrorCodes.EXECUTE_TRY_BLOCK_ERROR.name(), + QLErrorCodes.EXECUTE_TRY_BLOCK_ERROR.getErrorMsg()); + } + return result; + } + } + + private QResult callExceptionHandler(Object catchObj, QContext qContext, QLOptions qlOptions) { + QLambdaDefinition exceptionHandler = getExceptionHandler(catchObj.getClass()); + if (exceptionHandler == null) { + return null; + } + QLambda catchHandlerLambda = exceptionHandler.toLambda(qContext, qlOptions, true); + + try { + return catchHandlerLambda.call(catchObj); + } + catch (Throwable th) { + throw ThrowUtils.wrapThrowable(th, + errorReporter, + QLErrorCodes.EXECUTE_CATCH_HANDLER_ERROR.name(), + QLErrorCodes.EXECUTE_CATCH_HANDLER_ERROR.getErrorMsg(), + catchObj.getClass().getName()); + } + } + + private QLambdaDefinition getExceptionHandler(Class catchObjClass) { + for (Map.Entry, QLambdaDefinition> clsLambdaEn : exceptionTable) { + if (clsLambdaEn.getKey().isAssignableFrom(catchObjClass)) { + return clsLambdaEn.getValue(); + } + } + return null; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/instruction/UnaryInstruction.java b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/UnaryInstruction.java new file mode 100644 index 0000000..ca038c0 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/UnaryInstruction.java @@ -0,0 +1,64 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import java.util.function.Consumer; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.QContext; +import com.alibaba.qlexpress4.runtime.QResult; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.data.DataValue; +import com.alibaba.qlexpress4.runtime.operator.unary.UnaryOperator; +import com.alibaba.qlexpress4.runtime.trace.ExpressionTrace; +import com.alibaba.qlexpress4.utils.PrintlnUtils; + +/** + * Operation: do unary operator like, ++,--,!,~ + * Input: 1 + * Output: 1, unary result + * + * Author: DQinYuan + */ +public class UnaryInstruction extends QLInstruction { + + private final UnaryOperator unaryOperator; + + private final Integer traceKey; + + public UnaryInstruction(ErrorReporter errorReporter, UnaryOperator unaryOperator, Integer traceKey) { + super(errorReporter); + this.unaryOperator = unaryOperator; + this.traceKey = traceKey; + } + + @Override + public QResult execute(QContext qContext, QLOptions qlOptions) { + Value value = qContext.pop(); + Object result = unaryOperator.execute(value, errorReporter); + qContext.push(new DataValue(result)); + + // trace + ExpressionTrace expressionTrace = qContext.getTraces().getExpressionTraceByKey(traceKey); + if (expressionTrace != null) { + expressionTrace.valueEvaluated(result); + expressionTrace.getChildren().get(0).valueEvaluated(value.get()); + } + + return QResult.NEXT_INSTRUCTION; + } + + @Override + public int stackInput() { + return 1; + } + + @Override + public int stackOutput() { + return 1; + } + + @Override + public void println(int index, int depth, Consumer debug) { + PrintlnUtils.printlnByCurDepth(depth, index + ": UnaryOp " + unaryOperator.getOperator(), debug); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/instruction/WhileInstruction.java b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/WhileInstruction.java new file mode 100644 index 0000000..b990eb9 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/instruction/WhileInstruction.java @@ -0,0 +1,97 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.exception.QLErrorCodes; +import com.alibaba.qlexpress4.runtime.*; +import com.alibaba.qlexpress4.runtime.scope.QvmBlockScope; +import com.alibaba.qlexpress4.runtime.util.ThrowUtils; +import com.alibaba.qlexpress4.utils.PrintlnUtils; + +import java.util.Collections; +import java.util.HashMap; +import java.util.function.Consumer; + +/** + * Operation: while (condition) do body + * Input: 0 + * Output: 0 + *

+ * Author: DQinYuan + */ +public class WhileInstruction extends QLInstruction { + + private final QLambdaDefinition condition; + + private final QLambdaDefinition body; + + private final int whileScopeMaxStackSize; + + public WhileInstruction(ErrorReporter errorReporter, QLambdaDefinition condition, QLambdaDefinition body, + int whileScopeMaxStackSize) { + super(errorReporter); + this.condition = condition; + this.body = body; + this.whileScopeMaxStackSize = whileScopeMaxStackSize; + } + + @Override + public QResult execute(QContext qContext, QLOptions qlOptions) { + DelegateQContext whileScopeContext = new DelegateQContext(qContext, + new QvmBlockScope(qContext, Collections.emptyMap(), whileScopeMaxStackSize, ExceptionTable.EMPTY)); + QLambda conditionLambda = condition.toLambda(whileScopeContext, qlOptions, false); + QLambda bodyLambda = body.toLambda(whileScopeContext, qlOptions, true); + whileBody: while (evalCondition(conditionLambda)) { + try { + QResult bodyResult = bodyLambda.call(); + switch (bodyResult.getResultType()) { + case RETURN: + return bodyResult; + case BREAK: + break whileBody; + } + } + catch (Throwable t) { + throw ThrowUtils + .wrapThrowable(t, errorReporter, "WHILE_BODY_EXECUTE_ERROR", "while body execute error"); + } + } + return QResult.NEXT_INSTRUCTION; + } + + @Override + public int stackInput() { + return 0; + } + + @Override + public int stackOutput() { + return 0; + } + + @Override + public void println(int index, int depth, Consumer debug) { + PrintlnUtils.printlnByCurDepth(depth, index + ": While", debug); + PrintlnUtils.printlnByCurDepth(depth + 1, "Condition", debug); + condition.println(depth + 2, debug); + PrintlnUtils.printlnByCurDepth(depth + 1, "Body", debug); + body.println(depth + 2, debug); + } + + private boolean evalCondition(QLambda conditionLambda) { + try { + Object conditionResult = conditionLambda.call().getResult().get(); + if (!(conditionResult instanceof Boolean)) { + throw errorReporter.report(QLErrorCodes.WHILE_CONDITION_BOOL_REQUIRED.name(), + QLErrorCodes.WHILE_CONDITION_BOOL_REQUIRED.getErrorMsg()); + } + return (boolean)conditionResult; + } + catch (Throwable t) { + throw ThrowUtils.wrapThrowable(t, + errorReporter, + QLErrorCodes.WHILE_CONDITION_ERROR.name(), + QLErrorCodes.WHILE_CONDITION_ERROR.getErrorMsg()); + } + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/BinaryOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/BinaryOperator.java new file mode 100644 index 0000000..4e8e892 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/BinaryOperator.java @@ -0,0 +1,25 @@ +package com.alibaba.qlexpress4.runtime.operator; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.QRuntime; +import com.alibaba.qlexpress4.runtime.Value; + +/** + * 二元操作符 + * Author: DQinYuan + * date 2022/1/12 2:34 下午 + */ +public interface BinaryOperator extends Operator { + /** + * 执行操作符计算 + * + * @param left left operand + * @param right right operand + * @param qRuntime runtime of qlexpress + * @param qlOptions options of current script run + * @param errorReporter error reporter + * @return result of operator + */ + Object execute(Value left, Value right, QRuntime qRuntime, QLOptions qlOptions, ErrorReporter errorReporter); +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/CustomBinaryOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/CustomBinaryOperator.java new file mode 100644 index 0000000..9911a78 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/CustomBinaryOperator.java @@ -0,0 +1,17 @@ +package com.alibaba.qlexpress4.runtime.operator; + +import com.alibaba.qlexpress4.runtime.Value; + +/** + * @author bingo + */ +public interface CustomBinaryOperator { + /** + * @param left left operand + * @param right right operand + * @return result + * @throws Throwable {@link com.alibaba.qlexpress4.exception.UserDefineException} for custom error message + */ + Object execute(Value left, Value right) + throws Throwable; +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/InstanceOfOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/InstanceOfOperator.java new file mode 100644 index 0000000..9bd558b --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/InstanceOfOperator.java @@ -0,0 +1,53 @@ +package com.alibaba.qlexpress4.runtime.operator; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.QLPrecedences; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.MetaClass; +import com.alibaba.qlexpress4.runtime.QRuntime; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.base.BaseBinaryOperator; + +/** + * @author bingo + */ +public class InstanceOfOperator extends BaseBinaryOperator { + private static final InstanceOfOperator INSTANCE = new InstanceOfOperator(); + + private InstanceOfOperator() { + } + + public static InstanceOfOperator getInstance() { + return INSTANCE; + } + + @Override + public Object execute(Value left, Value right, QRuntime qRuntime, QLOptions qlOptions, + ErrorReporter errorReporter) { + Object sourceObject = left.get(); + Object targetClass = right.get(); + if (targetClass == null) { + throw buildInvalidOperandTypeException(left, right, errorReporter); + } + if (targetClass instanceof MetaClass) { + targetClass = ((MetaClass)targetClass).getClz(); + } + else if (!(targetClass instanceof Class)) { + throw buildInvalidOperandTypeException(left, right, errorReporter); + } + if (sourceObject == null) { + return false; + } + return ((Class)targetClass).isAssignableFrom(sourceObject.getClass()); + } + + @Override + public String getOperator() { + return "instanceof"; + } + + @Override + public int getPriority() { + return QLPrecedences.COMPARE; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/Operator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/Operator.java new file mode 100644 index 0000000..8a6ef54 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/Operator.java @@ -0,0 +1,18 @@ +package com.alibaba.qlexpress4.runtime.operator; + +/** + * 操作符接口 + * + * @author bingo + */ +public interface Operator { + /** + * @return 操作符 + */ + String getOperator(); + + /** + * @return 操作符优先级 + */ + int getPriority(); +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/OperatorManager.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/OperatorManager.java new file mode 100644 index 0000000..1cfe985 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/OperatorManager.java @@ -0,0 +1,321 @@ +package com.alibaba.qlexpress4.runtime.operator; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.QLPrecedences; +import com.alibaba.qlexpress4.aparser.OperatorFactory; +import com.alibaba.qlexpress4.aparser.ParserOperatorManager; +import com.alibaba.qlexpress4.aparser.QLexer; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.exception.UserDefineException; +import com.alibaba.qlexpress4.runtime.QRuntime; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.arithmetic.DivideAssignOperator; +import com.alibaba.qlexpress4.runtime.operator.arithmetic.DivideOperator; +import com.alibaba.qlexpress4.runtime.operator.arithmetic.MinusAssignOperator; +import com.alibaba.qlexpress4.runtime.operator.arithmetic.MinusOperator; +import com.alibaba.qlexpress4.runtime.operator.arithmetic.MultiplyAssignOperator; +import com.alibaba.qlexpress4.runtime.operator.arithmetic.MultiplyOperator; +import com.alibaba.qlexpress4.runtime.operator.arithmetic.PlusAssignOperator; +import com.alibaba.qlexpress4.runtime.operator.arithmetic.PlusOperator; +import com.alibaba.qlexpress4.runtime.operator.arithmetic.RemainderAssignOperator; +import com.alibaba.qlexpress4.runtime.operator.arithmetic.RemainderOperator; +import com.alibaba.qlexpress4.runtime.operator.assign.AssignOperator; +import com.alibaba.qlexpress4.runtime.operator.bit.BitwiseAndAssignOperator; +import com.alibaba.qlexpress4.runtime.operator.bit.BitwiseAndOperator; +import com.alibaba.qlexpress4.runtime.operator.bit.BitwiseInvertOperator; +import com.alibaba.qlexpress4.runtime.operator.bit.BitwiseLeftShiftAssignOperator; +import com.alibaba.qlexpress4.runtime.operator.bit.BitwiseLeftShiftOperator; +import com.alibaba.qlexpress4.runtime.operator.bit.BitwiseOrAssignOperator; +import com.alibaba.qlexpress4.runtime.operator.bit.BitwiseOrOperator; +import com.alibaba.qlexpress4.runtime.operator.bit.BitwiseRightShiftAssignOperator; +import com.alibaba.qlexpress4.runtime.operator.bit.BitwiseRightShiftOperator; +import com.alibaba.qlexpress4.runtime.operator.bit.BitwiseRightShiftUnsignedAssignOperator; +import com.alibaba.qlexpress4.runtime.operator.bit.BitwiseRightShiftUnsignedOperator; +import com.alibaba.qlexpress4.runtime.operator.bit.BitwiseXorAssignOperator; +import com.alibaba.qlexpress4.runtime.operator.bit.BitwiseXorOperator; +import com.alibaba.qlexpress4.runtime.operator.collection.InOperator; +import com.alibaba.qlexpress4.runtime.operator.collection.NotInOperator; +import com.alibaba.qlexpress4.runtime.operator.compare.EqualOperator; +import com.alibaba.qlexpress4.runtime.operator.compare.GreaterEqualOperator; +import com.alibaba.qlexpress4.runtime.operator.compare.GreaterOperator; +import com.alibaba.qlexpress4.runtime.operator.compare.LessEqualOperator; +import com.alibaba.qlexpress4.runtime.operator.compare.LessOperator; +import com.alibaba.qlexpress4.runtime.operator.compare.UnequalOperator; +import com.alibaba.qlexpress4.runtime.operator.logic.LogicAndOperator; +import com.alibaba.qlexpress4.runtime.operator.logic.LogicNotOperator; +import com.alibaba.qlexpress4.runtime.operator.logic.LogicOrOperator; +import com.alibaba.qlexpress4.runtime.operator.string.LikeOperator; +import com.alibaba.qlexpress4.runtime.operator.string.NotLikeOperator; +import com.alibaba.qlexpress4.runtime.operator.unary.MinusMinusPrefixUnaryOperator; +import com.alibaba.qlexpress4.runtime.operator.unary.MinusMinusSuffixUnaryOperator; +import com.alibaba.qlexpress4.runtime.operator.unary.MinusUnaryOperator; +import com.alibaba.qlexpress4.runtime.operator.unary.PlusPlusPrefixUnaryOperator; +import com.alibaba.qlexpress4.runtime.operator.unary.PlusPlusSuffixUnaryOperator; +import com.alibaba.qlexpress4.runtime.operator.unary.PlusUnaryOperator; +import com.alibaba.qlexpress4.runtime.operator.unary.UnaryOperator; +import com.alibaba.qlexpress4.runtime.util.ThrowUtils; + +/** + * @author bingo + */ +public class OperatorManager implements OperatorFactory, ParserOperatorManager { + private static final Map DEFAULT_BINARY_OPERATOR_MAP = new ConcurrentHashMap<>(64); + + private static final Map DEFAULT_PREFIX_UNARY_OPERATOR_MAP = new ConcurrentHashMap<>(8); + + private static final Map DEFAULT_SUFFIX_UNARY_OPERATOR_MAP = new ConcurrentHashMap<>(8); + + static { + List binaryOperatorList = new ArrayList<>(64); + binaryOperatorList.add(AssignOperator.getInstance()); + binaryOperatorList.add(PlusOperator.getInstance()); + binaryOperatorList.add(PlusAssignOperator.getInstance()); + binaryOperatorList.add(MinusOperator.getInstance()); + binaryOperatorList.add(MinusAssignOperator.getInstance()); + binaryOperatorList.add(MultiplyOperator.getInstance()); + binaryOperatorList.add(MultiplyAssignOperator.getInstance()); + binaryOperatorList.add(DivideOperator.getInstance()); + binaryOperatorList.add(DivideAssignOperator.getInstance()); + binaryOperatorList.add(RemainderOperator.getInstance("%")); + binaryOperatorList.add(RemainderAssignOperator.getInstance()); + //binaryOperatorList.add(RemainderOperator.getInstance("mod")); + binaryOperatorList.add(BitwiseAndOperator.getInstance()); + binaryOperatorList.add(BitwiseAndAssignOperator.getInstance()); + binaryOperatorList.add(BitwiseOrOperator.getInstance()); + binaryOperatorList.add(BitwiseOrAssignOperator.getInstance()); + binaryOperatorList.add(BitwiseXorOperator.getInstance()); + binaryOperatorList.add(BitwiseXorAssignOperator.getInstance()); + binaryOperatorList.add(BitwiseLeftShiftOperator.getInstance()); + binaryOperatorList.add(BitwiseLeftShiftAssignOperator.getInstance()); + binaryOperatorList.add(BitwiseRightShiftOperator.getInstance()); + binaryOperatorList.add(BitwiseRightShiftAssignOperator.getInstance()); + binaryOperatorList.add(BitwiseRightShiftUnsignedOperator.getInstance()); + binaryOperatorList.add(BitwiseRightShiftUnsignedAssignOperator.getInstance()); + binaryOperatorList.add(LogicAndOperator.getInstance("&&")); + binaryOperatorList.add(LogicAndOperator.getInstance("and")); + binaryOperatorList.add(LogicOrOperator.getInstance("||")); + binaryOperatorList.add(LogicOrOperator.getInstance("or")); + binaryOperatorList.add(EqualOperator.getInstance()); + binaryOperatorList.add(UnequalOperator.getInstance("!=")); + binaryOperatorList.add(UnequalOperator.getInstance("<>")); + //binaryOperatorList.add(PrismaticUnequalOperator.getInstance()); + binaryOperatorList.add(GreaterOperator.getInstance()); + binaryOperatorList.add(GreaterEqualOperator.getInstance()); + binaryOperatorList.add(LessOperator.getInstance()); + binaryOperatorList.add(LessEqualOperator.getInstance()); + binaryOperatorList.add(InOperator.getInstance()); + binaryOperatorList.add(NotInOperator.getInstance()); + binaryOperatorList.add(LikeOperator.getInstance()); + binaryOperatorList.add(NotLikeOperator.getInstance()); + binaryOperatorList.add(InstanceOfOperator.getInstance()); + for (BinaryOperator binaryOperator : binaryOperatorList) { + DEFAULT_BINARY_OPERATOR_MAP.put(binaryOperator.getOperator(), binaryOperator); + } + + List prefixUnaryOperatorList = new ArrayList<>(8); + prefixUnaryOperatorList.add(PlusUnaryOperator.getInstance()); + prefixUnaryOperatorList.add(MinusUnaryOperator.getInstance()); + prefixUnaryOperatorList.add(PlusPlusPrefixUnaryOperator.getInstance()); + prefixUnaryOperatorList.add(MinusMinusPrefixUnaryOperator.getInstance()); + prefixUnaryOperatorList.add(BitwiseInvertOperator.getInstance()); + prefixUnaryOperatorList.add(LogicNotOperator.getInstance()); + for (UnaryOperator unaryOperator : prefixUnaryOperatorList) { + DEFAULT_PREFIX_UNARY_OPERATOR_MAP.put(unaryOperator.getOperator(), unaryOperator); + } + + List suffixUnaryOperatorList = new ArrayList<>(8); + suffixUnaryOperatorList.add(PlusPlusSuffixUnaryOperator.getInstance()); + suffixUnaryOperatorList.add(MinusMinusSuffixUnaryOperator.getInstance()); + for (UnaryOperator unaryOperator : suffixUnaryOperatorList) { + DEFAULT_SUFFIX_UNARY_OPERATOR_MAP.put(unaryOperator.getOperator(), unaryOperator); + } + } + + private static final Map ALIASABLE_KEYWORDS = new HashMap<>(); + + static { + ALIASABLE_KEYWORDS.put("if", QLexer.IF); + ALIASABLE_KEYWORDS.put("then", QLexer.THEN); + ALIASABLE_KEYWORDS.put("else", QLexer.ELSE); + ALIASABLE_KEYWORDS.put("for", QLexer.FOR); + ALIASABLE_KEYWORDS.put("while", QLexer.WHILE); + ALIASABLE_KEYWORDS.put("break", QLexer.BREAK); + ALIASABLE_KEYWORDS.put("continue", QLexer.CONTINUE); + ALIASABLE_KEYWORDS.put("return", QLexer.RETURN); + ALIASABLE_KEYWORDS.put("function", QLexer.FUNCTION); + ALIASABLE_KEYWORDS.put("macro", QLexer.MACRO); + ALIASABLE_KEYWORDS.put("new", QLexer.NEW); + ALIASABLE_KEYWORDS.put("null", QLexer.NULL); + ALIASABLE_KEYWORDS.put("true", QLexer.TRUE); + ALIASABLE_KEYWORDS.put("false", QLexer.FALSE); + } + + private final Map customBinaryOperatorMap = new ConcurrentHashMap<>(); + + private final Map keyWordAliases = new ConcurrentHashMap<>(); + + /** + * Register a custom binary operator if it does not clash with a built-in operator. + * + * @param operatorName operator lexeme, for example {@code "**"} + * @param customBinaryOperator implementation of the custom operator + * @param priority operator precedence, see {@link QLPrecedences} + * @return {@code true} if registered successfully; {@code false} if the name is already used + */ + public boolean addBinaryOperator(String operatorName, CustomBinaryOperator customBinaryOperator, int priority) { + if (DEFAULT_BINARY_OPERATOR_MAP.containsKey(operatorName)) { + return false; + } + BinaryOperator preBinaryOperator = customBinaryOperatorMap.putIfAbsent(operatorName, + adapt2BinOp(operatorName, customBinaryOperator, priority)); + return preBinaryOperator == null; + } + + public boolean replaceDefaultOperator(String operatorName, CustomBinaryOperator customBinaryOperator) { + BinaryOperator defaultOperator = DEFAULT_BINARY_OPERATOR_MAP.get(operatorName); + if (defaultOperator == null) { + return false; + } + BinaryOperator preBinaryOperator = customBinaryOperatorMap.putIfAbsent(operatorName, + adapt2BinOp(operatorName, customBinaryOperator, defaultOperator.getPriority())); + return preBinaryOperator == null; + } + + private BinaryOperator adapt2BinOp(String operatorName, CustomBinaryOperator customBinaryOperator, int priority) { + return new BinaryOperator() { + @Override + public Object execute(Value left, Value right, QRuntime qRuntime, QLOptions qlOptions, + ErrorReporter errorReporter) { + try { + return customBinaryOperator.execute(left, right); + } + catch (UserDefineException e) { + throw ThrowUtils.reportUserDefinedException(errorReporter, e); + } + catch (Throwable t) { + throw ThrowUtils.wrapThrowable(t, + errorReporter, + "OPERATOR_INNER_EXCEPTION", + "custom operator '" + operatorName + "' inner exception"); + } + } + + @Override + public String getOperator() { + return operatorName; + } + + @Override + public int getPriority() { + return priority; + } + }; + } + + /** + * @param operatorLexeme like +, =, *, / + * @return binary operator + */ + public BinaryOperator getBinaryOperator(String operatorLexeme) { + BinaryOperator customBinaryOperator = customBinaryOperatorMap.get(operatorLexeme); + if (customBinaryOperator != null) { + return customBinaryOperator; + } + + return DEFAULT_BINARY_OPERATOR_MAP.get(operatorLexeme); + } + + /** + * like --1 ++1 !true ~1 ^1 + * + * @param operatorLexeme ++, -- + * @return prefix unary operator + */ + public UnaryOperator getPrefixUnaryOperator(String operatorLexeme) { + return DEFAULT_PREFIX_UNARY_OPERATOR_MAP.get(operatorLexeme); + } + + /** + * like 1-- 1++ + * + * @param operatorLexeme ++, -- + * @return suffix unary operator + */ + public UnaryOperator getSuffixUnaryOperator(String operatorLexeme) { + return DEFAULT_SUFFIX_UNARY_OPERATOR_MAP.get(operatorLexeme); + } + + @Override + public boolean isOpType(String lexeme, OpType opType) { + switch (opType) { + case MIDDLE: + return getBinaryOperator(lexeme) != null; + case PREFIX: + return DEFAULT_PREFIX_UNARY_OPERATOR_MAP.containsKey(lexeme); + case SUFFIX: + return DEFAULT_SUFFIX_UNARY_OPERATOR_MAP.containsKey(lexeme); + } + return false; + } + + @Override + public Integer precedence(String lexeme) { + return getBinaryOperator(lexeme).getPriority(); + } + + @Override + public Integer getAlias(String lexeme) { + return keyWordAliases.get(lexeme); + } + + public boolean addKeyWordAlias(String lexeme, String keyWord) { + Integer keyWordId = ALIASABLE_KEYWORDS.get(keyWord); + if (keyWordId == null) { + return false; + } + keyWordAliases.put(lexeme, keyWordId); + return true; + } + + public boolean addOperatorAlias(String lexeme, String operator) { + BinaryOperator originDefaultOp = DEFAULT_BINARY_OPERATOR_MAP.get(operator); + if (originDefaultOp != null) { + BinaryOperator newOperator = adaptOriginOperator(originDefaultOp, lexeme); + BinaryOperator prev = customBinaryOperatorMap.putIfAbsent(lexeme, newOperator); + return prev == null; + } + BinaryOperator originCusOp = customBinaryOperatorMap.get(operator); + if (originCusOp != null) { + BinaryOperator newOperator = adaptOriginOperator(originCusOp, lexeme); + BinaryOperator prev = customBinaryOperatorMap.putIfAbsent(lexeme, newOperator); + return prev == null; + } + return false; + } + + private BinaryOperator adaptOriginOperator(BinaryOperator originOperator, String lexeme) { + return new BinaryOperator() { + @Override + public Object execute(Value left, Value right, QRuntime qRuntime, QLOptions qlOptions, + ErrorReporter errorReporter) { + return originOperator.execute(left, right, qRuntime, qlOptions, errorReporter); + } + + @Override + public String getOperator() { + return lexeme; + } + + @Override + public int getPriority() { + return originOperator.getPriority(); + } + }; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/arithmetic/DivideAssignOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/arithmetic/DivideAssignOperator.java new file mode 100644 index 0000000..7b587aa --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/arithmetic/DivideAssignOperator.java @@ -0,0 +1,43 @@ +package com.alibaba.qlexpress4.runtime.operator.arithmetic; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.QLPrecedences; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.LeftValue; +import com.alibaba.qlexpress4.runtime.QRuntime; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.base.BaseBinaryOperator; + +/** + * @author bingo + */ +public class DivideAssignOperator extends BaseBinaryOperator { + private static final DivideAssignOperator INSTANCE = new DivideAssignOperator(); + + private DivideAssignOperator() { + } + + public static DivideAssignOperator getInstance() { + return INSTANCE; + } + + @Override + public Object execute(Value left, Value right, QRuntime qRuntime, QLOptions qlOptions, + ErrorReporter errorReporter) { + assertLeftValue(left, errorReporter); + LeftValue leftValue = (LeftValue)left; + Object result = divide(left, right, qlOptions, errorReporter); + leftValue.set(result, errorReporter); + return result; + } + + @Override + public String getOperator() { + return "/="; + } + + @Override + public int getPriority() { + return QLPrecedences.ASSIGN; + } +} \ No newline at end of file diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/arithmetic/DivideOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/arithmetic/DivideOperator.java new file mode 100644 index 0000000..0e64f23 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/arithmetic/DivideOperator.java @@ -0,0 +1,38 @@ +package com.alibaba.qlexpress4.runtime.operator.arithmetic; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.QLPrecedences; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.QRuntime; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.base.BaseBinaryOperator; + +/** + * @author bingo + */ +public class DivideOperator extends BaseBinaryOperator { + private static final DivideOperator INSTANCE = new DivideOperator(); + + private DivideOperator() { + } + + public static DivideOperator getInstance() { + return INSTANCE; + } + + @Override + public Object execute(Value left, Value right, QRuntime qRuntime, QLOptions qlOptions, + ErrorReporter errorReporter) { + return divide(left, right, qlOptions, errorReporter); + } + + @Override + public String getOperator() { + return "/"; + } + + @Override + public int getPriority() { + return QLPrecedences.MULTI; + } +} \ No newline at end of file diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/arithmetic/MinusAssignOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/arithmetic/MinusAssignOperator.java new file mode 100644 index 0000000..c060f9b --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/arithmetic/MinusAssignOperator.java @@ -0,0 +1,43 @@ +package com.alibaba.qlexpress4.runtime.operator.arithmetic; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.QLPrecedences; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.LeftValue; +import com.alibaba.qlexpress4.runtime.QRuntime; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.base.BaseBinaryOperator; + +/** + * @author bingo + */ +public class MinusAssignOperator extends BaseBinaryOperator { + private static final MinusAssignOperator INSTANCE = new MinusAssignOperator(); + + private MinusAssignOperator() { + } + + public static MinusAssignOperator getInstance() { + return INSTANCE; + } + + @Override + public Object execute(Value left, Value right, QRuntime qRuntime, QLOptions qlOptions, + ErrorReporter errorReporter) { + assertLeftValue(left, errorReporter); + LeftValue leftValue = (LeftValue)left; + Object result = minus(left, right, qlOptions, errorReporter); + leftValue.set(result, errorReporter); + return result; + } + + @Override + public String getOperator() { + return "-="; + } + + @Override + public int getPriority() { + return QLPrecedences.ASSIGN; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/arithmetic/MinusOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/arithmetic/MinusOperator.java new file mode 100644 index 0000000..d1fa92e --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/arithmetic/MinusOperator.java @@ -0,0 +1,38 @@ +package com.alibaba.qlexpress4.runtime.operator.arithmetic; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.QLPrecedences; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.QRuntime; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.base.BaseBinaryOperator; + +/** + * @author bingo + */ +public class MinusOperator extends BaseBinaryOperator { + private static final MinusOperator INSTANCE = new MinusOperator(); + + private MinusOperator() { + } + + public static MinusOperator getInstance() { + return INSTANCE; + } + + @Override + public Object execute(Value left, Value right, QRuntime qRuntime, QLOptions qlOptions, + ErrorReporter errorReporter) { + return minus(left, right, qlOptions, errorReporter); + } + + @Override + public String getOperator() { + return "-"; + } + + @Override + public int getPriority() { + return QLPrecedences.ADD; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/arithmetic/MultiplyAssignOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/arithmetic/MultiplyAssignOperator.java new file mode 100644 index 0000000..6cb9bec --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/arithmetic/MultiplyAssignOperator.java @@ -0,0 +1,43 @@ +package com.alibaba.qlexpress4.runtime.operator.arithmetic; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.QLPrecedences; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.LeftValue; +import com.alibaba.qlexpress4.runtime.QRuntime; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.base.BaseBinaryOperator; + +/** + * @author bingo + */ +public class MultiplyAssignOperator extends BaseBinaryOperator { + private static final MultiplyAssignOperator INSTANCE = new MultiplyAssignOperator(); + + private MultiplyAssignOperator() { + } + + public static MultiplyAssignOperator getInstance() { + return INSTANCE; + } + + @Override + public Object execute(Value left, Value right, QRuntime qRuntime, QLOptions qlOptions, + ErrorReporter errorReporter) { + assertLeftValue(left, errorReporter); + LeftValue leftValue = (LeftValue)left; + Object result = multiply(left, right, qlOptions, errorReporter); + leftValue.set(result, errorReporter); + return result; + } + + @Override + public String getOperator() { + return "*="; + } + + @Override + public int getPriority() { + return QLPrecedences.ASSIGN; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/arithmetic/MultiplyOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/arithmetic/MultiplyOperator.java new file mode 100644 index 0000000..8f8c542 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/arithmetic/MultiplyOperator.java @@ -0,0 +1,38 @@ +package com.alibaba.qlexpress4.runtime.operator.arithmetic; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.QLPrecedences; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.QRuntime; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.base.BaseBinaryOperator; + +/** + * @author bingo + */ +public class MultiplyOperator extends BaseBinaryOperator { + private static final MultiplyOperator INSTANCE = new MultiplyOperator(); + + private MultiplyOperator() { + } + + public static MultiplyOperator getInstance() { + return INSTANCE; + } + + @Override + public Object execute(Value left, Value right, QRuntime qRuntime, QLOptions qlOptions, + ErrorReporter errorReporter) { + return multiply(left, right, qlOptions, errorReporter); + } + + @Override + public String getOperator() { + return "*"; + } + + @Override + public int getPriority() { + return QLPrecedences.MULTI; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/arithmetic/PlusAssignOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/arithmetic/PlusAssignOperator.java new file mode 100644 index 0000000..98e5dee --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/arithmetic/PlusAssignOperator.java @@ -0,0 +1,43 @@ +package com.alibaba.qlexpress4.runtime.operator.arithmetic; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.QLPrecedences; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.LeftValue; +import com.alibaba.qlexpress4.runtime.QRuntime; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.base.BaseBinaryOperator; + +/** + * @author bingo + */ +public class PlusAssignOperator extends BaseBinaryOperator { + private static final PlusAssignOperator INSTANCE = new PlusAssignOperator(); + + private PlusAssignOperator() { + } + + public static PlusAssignOperator getInstance() { + return INSTANCE; + } + + @Override + public Object execute(Value left, Value right, QRuntime qRuntime, QLOptions qlOptions, + ErrorReporter errorReporter) { + assertLeftValue(left, errorReporter); + LeftValue leftValue = (LeftValue)left; + Object result = plus(left, right, qlOptions, errorReporter); + leftValue.set(result, errorReporter); + return result; + } + + @Override + public String getOperator() { + return "+="; + } + + @Override + public int getPriority() { + return QLPrecedences.ASSIGN; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/arithmetic/PlusOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/arithmetic/PlusOperator.java new file mode 100644 index 0000000..c3c7b23 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/arithmetic/PlusOperator.java @@ -0,0 +1,40 @@ +package com.alibaba.qlexpress4.runtime.operator.arithmetic; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.QLPrecedences; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.QRuntime; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.base.BaseBinaryOperator; + +/** + * QLExpress只支持String和Number类型的+ + * + * @author bingo + */ +public class PlusOperator extends BaseBinaryOperator { + private static final PlusOperator INSTANCE = new PlusOperator(); + + private PlusOperator() { + } + + public static PlusOperator getInstance() { + return INSTANCE; + } + + @Override + public Object execute(Value left, Value right, QRuntime qRuntime, QLOptions qlOptions, + ErrorReporter errorReporter) { + return plus(left, right, qlOptions, errorReporter); + } + + @Override + public String getOperator() { + return "+"; + } + + @Override + public int getPriority() { + return QLPrecedences.ADD; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/arithmetic/RemainderAssignOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/arithmetic/RemainderAssignOperator.java new file mode 100644 index 0000000..413bb1b --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/arithmetic/RemainderAssignOperator.java @@ -0,0 +1,43 @@ +package com.alibaba.qlexpress4.runtime.operator.arithmetic; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.QLPrecedences; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.LeftValue; +import com.alibaba.qlexpress4.runtime.QRuntime; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.base.BaseBinaryOperator; + +/** + * @author bingo + */ +public class RemainderAssignOperator extends BaseBinaryOperator { + private static final RemainderAssignOperator INSTANCE = new RemainderAssignOperator(); + + private RemainderAssignOperator() { + } + + public static RemainderAssignOperator getInstance() { + return INSTANCE; + } + + @Override + public Object execute(Value left, Value right, QRuntime qRuntime, QLOptions qlOptions, + ErrorReporter errorReporter) { + assertLeftValue(left, errorReporter); + LeftValue leftValue = (LeftValue)left; + Object result = remainder(left, right, qlOptions, errorReporter); + leftValue.set(result, errorReporter); + return result; + } + + @Override + public String getOperator() { + return "%="; + } + + @Override + public int getPriority() { + return QLPrecedences.ASSIGN; + } +} \ No newline at end of file diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/arithmetic/RemainderOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/arithmetic/RemainderOperator.java new file mode 100644 index 0000000..247e831 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/arithmetic/RemainderOperator.java @@ -0,0 +1,49 @@ +package com.alibaba.qlexpress4.runtime.operator.arithmetic; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.QLPrecedences; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.QRuntime; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.base.BaseBinaryOperator; + +/** + * @author bingo + */ +public class RemainderOperator extends BaseBinaryOperator { + private static final Map INSTANCE_CACHE = new ConcurrentHashMap<>(2); + + static { + INSTANCE_CACHE.put("%", new RemainderOperator("%")); + //INSTANCE_CACHE.put("mod", new RemainderOperator("mod")); + } + + private final String operator; + + private RemainderOperator(String operator) { + this.operator = operator; + } + + public static RemainderOperator getInstance(String operator) { + return INSTANCE_CACHE.get(operator); + } + + @Override + public Object execute(Value left, Value right, QRuntime qRuntime, QLOptions qlOptions, + ErrorReporter errorReporter) { + return remainder(left, right, qlOptions, errorReporter); + } + + @Override + public String getOperator() { + return operator; + } + + @Override + public int getPriority() { + return QLPrecedences.MULTI; + } +} \ No newline at end of file diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/assign/AssignOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/assign/AssignOperator.java new file mode 100644 index 0000000..696be77 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/assign/AssignOperator.java @@ -0,0 +1,43 @@ +package com.alibaba.qlexpress4.runtime.operator.assign; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.QLPrecedences; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.LeftValue; +import com.alibaba.qlexpress4.runtime.QRuntime; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.base.BaseBinaryOperator; + +/** + * @author bingo + */ +public class AssignOperator extends BaseBinaryOperator { + private static final AssignOperator INSTANCE = new AssignOperator(); + + private AssignOperator() { + } + + public static AssignOperator getInstance() { + return INSTANCE; + } + + @Override + public Object execute(Value left, Value right, QRuntime qRuntime, QLOptions qlOptions, + ErrorReporter errorReporter) { + assertLeftValue(left, errorReporter); + LeftValue leftValue = (LeftValue)left; + Object newValue = right.get(); + leftValue.set(newValue, errorReporter); + return newValue; + } + + @Override + public String getOperator() { + return "="; + } + + @Override + public int getPriority() { + return QLPrecedences.ASSIGN; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/base/BaseBinaryOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/base/BaseBinaryOperator.java new file mode 100644 index 0000000..48bd867 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/base/BaseBinaryOperator.java @@ -0,0 +1,386 @@ +package com.alibaba.qlexpress4.runtime.operator.base; + +import java.util.Collection; +import java.util.Objects; +import java.util.Optional; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.exception.QLErrorCodes; +import com.alibaba.qlexpress4.exception.QLRuntimeException; +import com.alibaba.qlexpress4.runtime.LeftValue; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.data.DataValue; +import com.alibaba.qlexpress4.runtime.operator.BinaryOperator; +import com.alibaba.qlexpress4.runtime.operator.number.NumberMath; + +/** + * @author bingo + */ +public abstract class BaseBinaryOperator implements BinaryOperator { + protected boolean isSameType(Value left, Value right) { + return left.getTypeName() != null && right.getTypeName() != null + && Objects.equals(left.getTypeName(), right.getTypeName()); + } + + protected boolean isInstanceofComparable(Value value) { + return value.get() instanceof Comparable; + } + + protected boolean isBothBoolean(Value left, Value right) { + return left.get() instanceof Boolean && right.get() instanceof Boolean; + } + + protected boolean isBooleanAndNull(Value left, Value right) { + return (left.get() == null && right.get() instanceof Boolean) + || (left.get() instanceof Boolean && right.get() == null); + } + + protected boolean isBothNumber(Value left, Value right) { + return left.get() instanceof Number && right.get() instanceof Number; + } + + protected boolean isBothNumberOrChar(Object leftValue, Object rightValue) { + return (leftValue instanceof Character || leftValue instanceof Number) + && (rightValue instanceof Character || rightValue instanceof Number); + } + + protected Number char2Number(Object charOrNumber) { + return charOrNumber instanceof Character ? (int)(Character)charOrNumber : (Number)charOrNumber; + } + + protected boolean isNumberCharacter(Value left, Value right) { + return (left.get() instanceof Character && right.get() instanceof Number) + || (left.get() instanceof Number && right.get() instanceof Character); + } + + protected boolean isNumber(Value value) { + return value.get() instanceof Number; + } + + protected void assertLeftValue(Value left, ErrorReporter errorReporter) { + if (!(left instanceof LeftValue)) { + throw errorReporter.reportFormat(QLErrorCodes.INVALID_ASSIGNMENT.name(), + QLErrorCodes.INVALID_ASSIGNMENT.getErrorMsg(), + "on the left side"); + } + } + + protected Object plus(Value left, Value right, QLOptions qlOptions, ErrorReporter errorReporter) { + Object leftValue = left.get(); + Object rightValue = right.get(); + + if (leftValue instanceof String) { + return (String)leftValue + rightValue; + } + + if (rightValue instanceof String) { + return leftValue + (String)rightValue; + } + + if (isBothNumber(left, right)) { + return add(qlOptions, (Number)leftValue, (Number)rightValue); + } + + if (isBothNumberOrChar(leftValue, rightValue)) { + return add(qlOptions, char2Number(leftValue), char2Number(rightValue)); + } + + throw buildInvalidOperandTypeException(left, right, errorReporter); + } + + private Number add(QLOptions qlOptions, Number leftValue, Number rightValue) { + if (qlOptions.isPrecise()) { + return NumberMath.add(NumberMath.toBigDecimal(leftValue), NumberMath.toBigDecimal(rightValue)); + } + else { + return NumberMath.add(leftValue, rightValue); + } + } + + protected Object minus(Value left, Value right, QLOptions qlOptions, ErrorReporter errorReporter) { + Object leftValue = left.get(); + Object rightValue = right.get(); + + if (isBothNumber(left, right)) { + return subtract(qlOptions, (Number)leftValue, (Number)rightValue); + } + + if (isBothNumberOrChar(leftValue, rightValue)) { + return subtract(qlOptions, char2Number(leftValue), char2Number(rightValue)); + } + + throw buildInvalidOperandTypeException(left, right, errorReporter); + } + + private Number subtract(QLOptions qlOptions, Number leftValue, Number rightValue) { + if (qlOptions.isPrecise()) { + return NumberMath.subtract(NumberMath.toBigDecimal(leftValue), NumberMath.toBigDecimal(rightValue)); + } + else { + return NumberMath.subtract(leftValue, rightValue); + } + } + + protected Object multiply(Value left, Value right, QLOptions qlOptions, ErrorReporter errorReporter) { + Object leftValue = left.get(); + Object rightValue = right.get(); + + if (isBothNumber(left, right)) { + if (qlOptions.isPrecise()) { + return NumberMath.multiply(NumberMath.toBigDecimal((Number)leftValue), + NumberMath.toBigDecimal((Number)rightValue)); + } + else { + return NumberMath.multiply((Number)leftValue, (Number)rightValue); + } + } + + throw buildInvalidOperandTypeException(left, right, errorReporter); + } + + protected Object divide(Value left, Value right, QLOptions qlOptions, ErrorReporter errorReporter) { + Object leftValue = left.get(); + Object rightValue = right.get(); + + if (isBothNumber(left, right)) { + try { + if (qlOptions.isPrecise()) { + return NumberMath.divide(NumberMath.toBigDecimal((Number)leftValue), + NumberMath.toBigDecimal((Number)rightValue)); + } + else { + return NumberMath.divide((Number)leftValue, (Number)rightValue); + } + } + catch (ArithmeticException arithmeticException) { + throw errorReporter.report(arithmeticException, + QLErrorCodes.INVALID_ARITHMETIC.name(), + arithmeticException.getMessage()); + } + } + + throw buildInvalidOperandTypeException(left, right, errorReporter); + } + + protected Object remainder(Value left, Value right, QLOptions qlOptions, ErrorReporter errorReporter) { + Object leftValue = left.get(); + Object rightValue = right.get(); + + if (isBothNumber(left, right)) { + if (qlOptions.isPrecise()) { + return NumberMath.remainder(NumberMath.toBigDecimal((Number)leftValue), + NumberMath.toBigDecimal((Number)rightValue)); + } + else { + return NumberMath.remainder((Number)leftValue, (Number)rightValue); + } + } + + throw buildInvalidOperandTypeException(left, right, errorReporter); + } + + protected Object bitwiseAnd(Value left, Value right, ErrorReporter errorReporter) { + if (isBothBoolean(left, right) || isBooleanAndNull(left, right)) { + return (Boolean)Optional.ofNullable(left.get()).orElse(Boolean.FALSE) + & (Boolean)Optional.ofNullable(right.get()).orElse(Boolean.FALSE); + } + + if (isBothNumber(left, right)) { + Number leftValue = (Number)left.get(); + Number rightValue = (Number)right.get(); + return NumberMath.and(leftValue, rightValue); + } + + throw buildInvalidOperandTypeException(left, right, errorReporter); + } + + protected Object bitwiseOr(Value left, Value right, ErrorReporter errorReporter) { + if (isBothBoolean(left, right) || isBooleanAndNull(left, right)) { + return (Boolean)Optional.ofNullable(left.get()).orElse(Boolean.FALSE) + | (Boolean)Optional.ofNullable(right.get()).orElse(Boolean.FALSE); + } + + if (isBothNumber(left, right)) { + Number leftValue = (Number)left.get(); + Number rightValue = (Number)right.get(); + return NumberMath.or(leftValue, rightValue); + } + + throw buildInvalidOperandTypeException(left, right, errorReporter); + } + + protected Object bitwiseXor(Value left, Value right, ErrorReporter errorReporter) { + if (isBothBoolean(left, right) || isBooleanAndNull(left, right)) { + return (Boolean)Optional.ofNullable(left.get()).orElse(Boolean.FALSE) + ^ (Boolean)Optional.ofNullable(right.get()).orElse(Boolean.FALSE); + } + + if (isBothNumber(left, right)) { + Number leftValue = (Number)left.get(); + Number rightValue = (Number)right.get(); + return NumberMath.xor(leftValue, rightValue); + } + + throw buildInvalidOperandTypeException(left, right, errorReporter); + } + + protected Object leftShift(Value left, Value right, ErrorReporter errorReporter) { + if (isBothNumber(left, right)) { + Number leftValue = (Number)left.get(); + Number rightValue = (Number)right.get(); + return NumberMath.leftShift(leftValue, rightValue); + } + + throw buildInvalidOperandTypeException(left, right, errorReporter); + } + + protected Object rightShift(Value left, Value right, ErrorReporter errorReporter) { + if (isBothNumber(left, right)) { + Number leftValue = (Number)left.get(); + Number rightValue = (Number)right.get(); + return NumberMath.rightShift(leftValue, rightValue); + } + + throw buildInvalidOperandTypeException(left, right, errorReporter); + } + + protected Object rightShiftUnsigned(Value left, Value right, ErrorReporter errorReporter) { + if (isBothNumber(left, right)) { + Number leftValue = (Number)left.get(); + Number rightValue = (Number)right.get(); + return NumberMath.rightShiftUnsigned(leftValue, rightValue); + } + + throw buildInvalidOperandTypeException(left, right, errorReporter); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + protected int compare(Value left, Value right, ErrorReporter errorReporter) { + if (Objects.equals(left.get(), right.get())) { + return 0; + } + + if (isBothNumber(left, right)) { + return NumberMath.compareTo((Number)left.get(), (Number)right.get()); + } + + if (isNumberCharacter(left, right)) { + if (isNumber(left)) { + return NumberMath.compareTo((Number)left.get(), (int)(Character)right.get()); + } + else { + return NumberMath.compareTo((int)(Character)left.get(), (Number)right.get()); + } + } + + if (isSameType(left, right) && isInstanceofComparable(left)) { + return ((Comparable)(left.get())).compareTo(right.get()); + } + + throw buildInvalidOperandTypeException(left, right, errorReporter); + } + + protected boolean equals(Value left, Value right, ErrorReporter errorReporter) { + Object leftValue = left.get(); + Object rightValue = right.get(); + if (isBothNumber(left, right) || isNumberCharacter(left, right) + || (isSameType(left, right) && isInstanceofComparable(left))) { + return compare(left, right, errorReporter) == 0; + } + else { + return Objects.equals(leftValue, rightValue); + } + } + + protected boolean in(Value left, Value right, ErrorReporter errorReporter) { + Object rightOperand = right.get(); + Object leftOperand = left.get(); + if (leftOperand == null && rightOperand == null) { + return true; + } + else if (leftOperand == null || rightOperand == null) { + return false; + } + + if (rightOperand instanceof Collection) { + Collection rightCollection = (Collection)rightOperand; + for (Object rightElement : rightCollection) { + if (equals(left, new DataValue(rightElement), errorReporter)) { + return true; + } + } + return false; + } + else if (rightOperand.getClass().isArray()) { + Object[] rightArray = (Object[])rightOperand; + for (Object rightElement : rightArray) { + if (equals(left, new DataValue(rightElement), errorReporter)) { + return true; + } + } + return false; + } + else if (rightOperand instanceof String) { + return ((String)rightOperand).contains(String.valueOf(leftOperand)); + } + else { + throw buildInvalidOperandTypeException(left, right, errorReporter); + } + } + + protected boolean like(Value left, Value right, ErrorReporter errorReporter) { + Object target = left.get(); + Object pattern = right.get(); + if (target == null && pattern == null) { + return true; + } + else if (target == null || pattern == null) { + return false; + } + + if (!(target instanceof String) || !(pattern instanceof String)) { + throw buildInvalidOperandTypeException(left, right, errorReporter); + } + return matchPattern((String)target, (String)pattern); + } + + private static boolean matchPattern(String s, String pattern) { + int sPointer = 0, pPointer = 0; + int sLen = s.length(), pLen = pattern.length(); + int sRecall = -1, pRecall = -1; + while (sPointer < sLen) { + if (pPointer < pLen && (s.charAt(sPointer) == pattern.charAt(pPointer))) { + sPointer++; + pPointer++; + } + else if (pPointer < pLen && pattern.charAt(pPointer) == '%') { + sRecall = sPointer; + pRecall = pPointer; + pPointer++; + } + else if (sRecall >= 0) { + sPointer = ++sRecall; + pPointer = pRecall + 1; + } + else { + return false; + } + } + while (pPointer < pLen && pattern.charAt(pPointer) == '%') { + pPointer++; + } + return pPointer == pLen; + } + + protected QLRuntimeException buildInvalidOperandTypeException(Value left, Value right, + ErrorReporter errorReporter) { + return errorReporter.reportFormat(QLErrorCodes.INVALID_BINARY_OPERAND.name(), + QLErrorCodes.INVALID_BINARY_OPERAND.getErrorMsg(), + getOperator(), + left.getTypeName(), + left.get(), + right.getTypeName(), + right.get()); + } +} \ No newline at end of file diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/base/BaseUnaryOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/base/BaseUnaryOperator.java new file mode 100644 index 0000000..6b2cc5f --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/base/BaseUnaryOperator.java @@ -0,0 +1,17 @@ +package com.alibaba.qlexpress4.runtime.operator.base; + +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.exception.QLErrorCodes; +import com.alibaba.qlexpress4.exception.QLRuntimeException; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.unary.UnaryOperator; + +/** + * @author bingo + */ +public abstract class BaseUnaryOperator implements UnaryOperator { + protected QLRuntimeException buildInvalidOperandTypeException(Value value, ErrorReporter errorReporter) { + return errorReporter.reportFormat(QLErrorCodes.INVALID_UNARY_OPERAND + .name(), QLErrorCodes.INVALID_UNARY_OPERAND.getErrorMsg(), getOperator(), value.getTypeName(), value.get()); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseAndAssignOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseAndAssignOperator.java new file mode 100644 index 0000000..a6f3116 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseAndAssignOperator.java @@ -0,0 +1,43 @@ +package com.alibaba.qlexpress4.runtime.operator.bit; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.QLPrecedences; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.LeftValue; +import com.alibaba.qlexpress4.runtime.QRuntime; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.base.BaseBinaryOperator; + +/** + * @author bingo + */ +public class BitwiseAndAssignOperator extends BaseBinaryOperator { + private static final BitwiseAndAssignOperator INSTANCE = new BitwiseAndAssignOperator(); + + private BitwiseAndAssignOperator() { + } + + public static BitwiseAndAssignOperator getInstance() { + return INSTANCE; + } + + @Override + public Object execute(Value left, Value right, QRuntime qRuntime, QLOptions qlOptions, + ErrorReporter errorReporter) { + assertLeftValue(left, errorReporter); + LeftValue leftValue = (LeftValue)left; + Object result = bitwiseAnd(left, right, errorReporter); + leftValue.set(result, errorReporter); + return result; + } + + @Override + public String getOperator() { + return "&="; + } + + @Override + public int getPriority() { + return QLPrecedences.ASSIGN; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseAndOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseAndOperator.java new file mode 100644 index 0000000..4d211a2 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseAndOperator.java @@ -0,0 +1,38 @@ +package com.alibaba.qlexpress4.runtime.operator.bit; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.QLPrecedences; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.QRuntime; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.base.BaseBinaryOperator; + +/** + * @author bingo + */ +public class BitwiseAndOperator extends BaseBinaryOperator { + private static final BitwiseAndOperator INSTANCE = new BitwiseAndOperator(); + + private BitwiseAndOperator() { + } + + public static BitwiseAndOperator getInstance() { + return INSTANCE; + } + + @Override + public String getOperator() { + return "&"; + } + + @Override + public int getPriority() { + return QLPrecedences.BIT_AND; + } + + @Override + public Object execute(Value left, Value right, QRuntime qRuntime, QLOptions qlOptions, + ErrorReporter errorReporter) { + return bitwiseAnd(left, right, errorReporter); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseInvertOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseInvertOperator.java new file mode 100644 index 0000000..590ee44 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseInvertOperator.java @@ -0,0 +1,41 @@ +package com.alibaba.qlexpress4.runtime.operator.bit; + +import com.alibaba.qlexpress4.QLPrecedences; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.base.BaseUnaryOperator; +import com.alibaba.qlexpress4.runtime.operator.number.NumberMath; + +/** + * @author bingo + */ +public class BitwiseInvertOperator extends BaseUnaryOperator { + private static final BitwiseInvertOperator INSTANCE = new BitwiseInvertOperator(); + + private BitwiseInvertOperator() { + } + + public static BitwiseInvertOperator getInstance() { + return INSTANCE; + } + + @Override + public Object execute(Value value, ErrorReporter errorReporter) { + Object operand = value.get(); + if (!(operand instanceof Number)) { + throw buildInvalidOperandTypeException(value, errorReporter); + } + + return NumberMath.bitwiseNegate((Number)operand); + } + + @Override + public String getOperator() { + return "~"; + } + + @Override + public int getPriority() { + return QLPrecedences.UNARY; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseLeftShiftAssignOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseLeftShiftAssignOperator.java new file mode 100644 index 0000000..85e53fb --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseLeftShiftAssignOperator.java @@ -0,0 +1,43 @@ +package com.alibaba.qlexpress4.runtime.operator.bit; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.QLPrecedences; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.LeftValue; +import com.alibaba.qlexpress4.runtime.QRuntime; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.base.BaseBinaryOperator; + +/** + * @author bingo + */ +public class BitwiseLeftShiftAssignOperator extends BaseBinaryOperator { + private static final BitwiseLeftShiftAssignOperator INSTANCE = new BitwiseLeftShiftAssignOperator(); + + private BitwiseLeftShiftAssignOperator() { + } + + public static BitwiseLeftShiftAssignOperator getInstance() { + return INSTANCE; + } + + @Override + public Object execute(Value left, Value right, QRuntime qRuntime, QLOptions qlOptions, + ErrorReporter errorReporter) { + assertLeftValue(left, errorReporter); + LeftValue leftValue = (LeftValue)left; + Object result = leftShift(left, right, errorReporter); + leftValue.set(result, errorReporter); + return result; + } + + @Override + public String getOperator() { + return "<<="; + } + + @Override + public int getPriority() { + return QLPrecedences.ASSIGN; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseLeftShiftOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseLeftShiftOperator.java new file mode 100644 index 0000000..23d02d5 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseLeftShiftOperator.java @@ -0,0 +1,38 @@ +package com.alibaba.qlexpress4.runtime.operator.bit; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.QLPrecedences; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.QRuntime; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.base.BaseBinaryOperator; + +/** + * @author bingo + */ +public class BitwiseLeftShiftOperator extends BaseBinaryOperator { + private static final BitwiseLeftShiftOperator INSTANCE = new BitwiseLeftShiftOperator(); + + private BitwiseLeftShiftOperator() { + } + + public static BitwiseLeftShiftOperator getInstance() { + return INSTANCE; + } + + @Override + public String getOperator() { + return "<<"; + } + + @Override + public int getPriority() { + return QLPrecedences.BIT_MOVE; + } + + @Override + public Object execute(Value left, Value right, QRuntime qRuntime, QLOptions qlOptions, + ErrorReporter errorReporter) { + return leftShift(left, right, errorReporter); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseOrAssignOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseOrAssignOperator.java new file mode 100644 index 0000000..2e40723 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseOrAssignOperator.java @@ -0,0 +1,43 @@ +package com.alibaba.qlexpress4.runtime.operator.bit; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.QLPrecedences; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.LeftValue; +import com.alibaba.qlexpress4.runtime.QRuntime; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.base.BaseBinaryOperator; + +/** + * @author bingo + */ +public class BitwiseOrAssignOperator extends BaseBinaryOperator { + private static final BitwiseOrAssignOperator INSTANCE = new BitwiseOrAssignOperator(); + + private BitwiseOrAssignOperator() { + } + + public static BitwiseOrAssignOperator getInstance() { + return INSTANCE; + } + + @Override + public Object execute(Value left, Value right, QRuntime qRuntime, QLOptions qlOptions, + ErrorReporter errorReporter) { + assertLeftValue(left, errorReporter); + LeftValue leftValue = (LeftValue)left; + Object result = bitwiseOr(left, right, errorReporter); + leftValue.set(result, errorReporter); + return result; + } + + @Override + public String getOperator() { + return "|="; + } + + @Override + public int getPriority() { + return QLPrecedences.ASSIGN; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseOrOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseOrOperator.java new file mode 100644 index 0000000..95e5ad6 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseOrOperator.java @@ -0,0 +1,38 @@ +package com.alibaba.qlexpress4.runtime.operator.bit; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.QLPrecedences; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.QRuntime; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.base.BaseBinaryOperator; + +/** + * @author bingo + */ +public class BitwiseOrOperator extends BaseBinaryOperator { + private static final BitwiseOrOperator INSTANCE = new BitwiseOrOperator(); + + private BitwiseOrOperator() { + } + + public static BitwiseOrOperator getInstance() { + return INSTANCE; + } + + @Override + public String getOperator() { + return "|"; + } + + @Override + public int getPriority() { + return QLPrecedences.BIT_OR; + } + + @Override + public Object execute(Value left, Value right, QRuntime qRuntime, QLOptions qlOptions, + ErrorReporter errorReporter) { + return bitwiseOr(left, right, errorReporter); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseRightShiftAssignOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseRightShiftAssignOperator.java new file mode 100644 index 0000000..146b63e --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseRightShiftAssignOperator.java @@ -0,0 +1,43 @@ +package com.alibaba.qlexpress4.runtime.operator.bit; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.QLPrecedences; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.LeftValue; +import com.alibaba.qlexpress4.runtime.QRuntime; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.base.BaseBinaryOperator; + +/** + * @author bingo + */ +public class BitwiseRightShiftAssignOperator extends BaseBinaryOperator { + private static final BitwiseRightShiftAssignOperator INSTANCE = new BitwiseRightShiftAssignOperator(); + + private BitwiseRightShiftAssignOperator() { + } + + public static BitwiseRightShiftAssignOperator getInstance() { + return INSTANCE; + } + + @Override + public Object execute(Value left, Value right, QRuntime qRuntime, QLOptions qlOptions, + ErrorReporter errorReporter) { + assertLeftValue(left, errorReporter); + LeftValue leftValue = (LeftValue)left; + Object result = rightShift(left, right, errorReporter); + leftValue.set(result, errorReporter); + return result; + } + + @Override + public String getOperator() { + return ">>="; + } + + @Override + public int getPriority() { + return QLPrecedences.ASSIGN; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseRightShiftOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseRightShiftOperator.java new file mode 100644 index 0000000..5b27058 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseRightShiftOperator.java @@ -0,0 +1,38 @@ +package com.alibaba.qlexpress4.runtime.operator.bit; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.QLPrecedences; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.QRuntime; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.base.BaseBinaryOperator; + +/** + * @author bingo + */ +public class BitwiseRightShiftOperator extends BaseBinaryOperator { + private static final BitwiseRightShiftOperator INSTANCE = new BitwiseRightShiftOperator(); + + private BitwiseRightShiftOperator() { + } + + public static BitwiseRightShiftOperator getInstance() { + return INSTANCE; + } + + @Override + public String getOperator() { + return ">>"; + } + + @Override + public int getPriority() { + return QLPrecedences.BIT_MOVE; + } + + @Override + public Object execute(Value left, Value right, QRuntime qRuntime, QLOptions qlOptions, + ErrorReporter errorReporter) { + return rightShift(left, right, errorReporter); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseRightShiftUnsignedAssignOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseRightShiftUnsignedAssignOperator.java new file mode 100644 index 0000000..603f6ab --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseRightShiftUnsignedAssignOperator.java @@ -0,0 +1,44 @@ +package com.alibaba.qlexpress4.runtime.operator.bit; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.QLPrecedences; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.LeftValue; +import com.alibaba.qlexpress4.runtime.QRuntime; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.base.BaseBinaryOperator; + +/** + * @author bingo + */ +public class BitwiseRightShiftUnsignedAssignOperator extends BaseBinaryOperator { + private static final BitwiseRightShiftUnsignedAssignOperator INSTANCE = + new BitwiseRightShiftUnsignedAssignOperator(); + + private BitwiseRightShiftUnsignedAssignOperator() { + } + + public static BitwiseRightShiftUnsignedAssignOperator getInstance() { + return INSTANCE; + } + + @Override + public Object execute(Value left, Value right, QRuntime qRuntime, QLOptions qlOptions, + ErrorReporter errorReporter) { + assertLeftValue(left, errorReporter); + LeftValue leftValue = (LeftValue)left; + Object result = rightShiftUnsigned(left, right, errorReporter); + leftValue.set(result, errorReporter); + return result; + } + + @Override + public String getOperator() { + return ">>>="; + } + + @Override + public int getPriority() { + return QLPrecedences.ASSIGN; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseRightShiftUnsignedOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseRightShiftUnsignedOperator.java new file mode 100644 index 0000000..b532148 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseRightShiftUnsignedOperator.java @@ -0,0 +1,38 @@ +package com.alibaba.qlexpress4.runtime.operator.bit; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.QLPrecedences; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.QRuntime; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.base.BaseBinaryOperator; + +/** + * @author bingo + */ +public class BitwiseRightShiftUnsignedOperator extends BaseBinaryOperator { + private static final BitwiseRightShiftUnsignedOperator INSTANCE = new BitwiseRightShiftUnsignedOperator(); + + private BitwiseRightShiftUnsignedOperator() { + } + + public static BitwiseRightShiftUnsignedOperator getInstance() { + return INSTANCE; + } + + @Override + public String getOperator() { + return ">>>"; + } + + @Override + public int getPriority() { + return QLPrecedences.BIT_MOVE; + } + + @Override + public Object execute(Value left, Value right, QRuntime qRuntime, QLOptions qlOptions, + ErrorReporter errorReporter) { + return rightShiftUnsigned(left, right, errorReporter); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseXorAssignOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseXorAssignOperator.java new file mode 100644 index 0000000..9addb70 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseXorAssignOperator.java @@ -0,0 +1,43 @@ +package com.alibaba.qlexpress4.runtime.operator.bit; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.QLPrecedences; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.LeftValue; +import com.alibaba.qlexpress4.runtime.QRuntime; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.base.BaseBinaryOperator; + +/** + * @author bingo + */ +public class BitwiseXorAssignOperator extends BaseBinaryOperator { + private static final BitwiseXorAssignOperator INSTANCE = new BitwiseXorAssignOperator(); + + private BitwiseXorAssignOperator() { + } + + public static BitwiseXorAssignOperator getInstance() { + return INSTANCE; + } + + @Override + public String getOperator() { + return "^="; + } + + @Override + public int getPriority() { + return QLPrecedences.ASSIGN; + } + + @Override + public Object execute(Value left, Value right, QRuntime qRuntime, QLOptions qlOptions, + ErrorReporter errorReporter) { + assertLeftValue(left, errorReporter); + LeftValue leftValue = (LeftValue)left; + Object result = bitwiseXor(left, right, errorReporter); + leftValue.set(result, errorReporter); + return result; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseXorOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseXorOperator.java new file mode 100644 index 0000000..efadcc7 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/bit/BitwiseXorOperator.java @@ -0,0 +1,38 @@ +package com.alibaba.qlexpress4.runtime.operator.bit; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.QLPrecedences; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.QRuntime; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.base.BaseBinaryOperator; + +/** + * @author bingo + */ +public class BitwiseXorOperator extends BaseBinaryOperator { + private static final BitwiseXorOperator INSTANCE = new BitwiseXorOperator(); + + private BitwiseXorOperator() { + } + + public static BitwiseXorOperator getInstance() { + return INSTANCE; + } + + @Override + public String getOperator() { + return "^"; + } + + @Override + public int getPriority() { + return QLPrecedences.XOR; + } + + @Override + public Object execute(Value left, Value right, QRuntime qRuntime, QLOptions qlOptions, + ErrorReporter errorReporter) { + return bitwiseXor(left, right, errorReporter); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/collection/InOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/collection/InOperator.java new file mode 100644 index 0000000..4d0d613 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/collection/InOperator.java @@ -0,0 +1,38 @@ +package com.alibaba.qlexpress4.runtime.operator.collection; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.QLPrecedences; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.QRuntime; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.base.BaseBinaryOperator; + +/** + * @author bingo + */ +public class InOperator extends BaseBinaryOperator { + private static final InOperator INSTANCE = new InOperator(); + + private InOperator() { + } + + public static InOperator getInstance() { + return INSTANCE; + } + + @Override + public Object execute(Value left, Value right, QRuntime qRuntime, QLOptions qlOptions, + ErrorReporter errorReporter) { + return in(left, right, errorReporter); + } + + @Override + public String getOperator() { + return "in"; + } + + @Override + public int getPriority() { + return QLPrecedences.IN_LIKE; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/collection/NotInOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/collection/NotInOperator.java new file mode 100644 index 0000000..08b165a --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/collection/NotInOperator.java @@ -0,0 +1,38 @@ +package com.alibaba.qlexpress4.runtime.operator.collection; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.QLPrecedences; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.QRuntime; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.base.BaseBinaryOperator; + +/** + * @author bingo + */ +public class NotInOperator extends BaseBinaryOperator { + private static final NotInOperator INSTANCE = new NotInOperator(); + + private NotInOperator() { + } + + public static NotInOperator getInstance() { + return INSTANCE; + } + + @Override + public Object execute(Value left, Value right, QRuntime qRuntime, QLOptions qlOptions, + ErrorReporter errorReporter) { + return !in(left, right, errorReporter); + } + + @Override + public String getOperator() { + return "not_in"; + } + + @Override + public int getPriority() { + return QLPrecedences.IN_LIKE; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/compare/EqualOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/compare/EqualOperator.java new file mode 100644 index 0000000..bcf470a --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/compare/EqualOperator.java @@ -0,0 +1,38 @@ +package com.alibaba.qlexpress4.runtime.operator.compare; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.QLPrecedences; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.QRuntime; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.base.BaseBinaryOperator; + +/** + * @author bingo + */ +public class EqualOperator extends BaseBinaryOperator { + private static final EqualOperator INSTANCE = new EqualOperator(); + + private EqualOperator() { + } + + public static EqualOperator getInstance() { + return INSTANCE; + } + + @Override + public String getOperator() { + return "=="; + } + + @Override + public int getPriority() { + return QLPrecedences.EQUAL; + } + + @Override + public Object execute(Value left, Value right, QRuntime qRuntime, QLOptions qlOptions, + ErrorReporter errorReporter) { + return equals(left, right, errorReporter); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/compare/GreaterEqualOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/compare/GreaterEqualOperator.java new file mode 100644 index 0000000..178c6f2 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/compare/GreaterEqualOperator.java @@ -0,0 +1,41 @@ +package com.alibaba.qlexpress4.runtime.operator.compare; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.QLPrecedences; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.QRuntime; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.base.BaseBinaryOperator; + +/** + * @author bingo + */ +public class GreaterEqualOperator extends BaseBinaryOperator { + private static final GreaterEqualOperator INSTANCE = new GreaterEqualOperator(); + + private GreaterEqualOperator() { + } + + public static GreaterEqualOperator getInstance() { + return INSTANCE; + } + + @Override + public String getOperator() { + return ">="; + } + + @Override + public int getPriority() { + return QLPrecedences.COMPARE; + } + + @Override + public Object execute(Value left, Value right, QRuntime qRuntime, QLOptions qlOptions, + ErrorReporter errorReporter) { + if (qlOptions.isAvoidNullPointer() && (left.get() == null || right.get() == null)) { + return false; + } + return compare(left, right, errorReporter) >= 0; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/compare/GreaterOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/compare/GreaterOperator.java new file mode 100644 index 0000000..1b004f5 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/compare/GreaterOperator.java @@ -0,0 +1,41 @@ +package com.alibaba.qlexpress4.runtime.operator.compare; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.QLPrecedences; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.QRuntime; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.base.BaseBinaryOperator; + +/** + * @author bingo + */ +public class GreaterOperator extends BaseBinaryOperator { + private static final GreaterOperator INSTANCE = new GreaterOperator(); + + private GreaterOperator() { + } + + public static GreaterOperator getInstance() { + return INSTANCE; + } + + @Override + public String getOperator() { + return ">"; + } + + @Override + public int getPriority() { + return QLPrecedences.COMPARE; + } + + @Override + public Object execute(Value left, Value right, QRuntime qRuntime, QLOptions qlOptions, + ErrorReporter errorReporter) { + if (qlOptions.isAvoidNullPointer() && (left.get() == null || right.get() == null)) { + return false; + } + return compare(left, right, errorReporter) > 0; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/compare/LessEqualOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/compare/LessEqualOperator.java new file mode 100644 index 0000000..9234310 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/compare/LessEqualOperator.java @@ -0,0 +1,41 @@ +package com.alibaba.qlexpress4.runtime.operator.compare; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.QLPrecedences; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.QRuntime; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.base.BaseBinaryOperator; + +/** + * @author bingo + */ +public class LessEqualOperator extends BaseBinaryOperator { + private static final LessEqualOperator INSTANCE = new LessEqualOperator(); + + private LessEqualOperator() { + } + + public static LessEqualOperator getInstance() { + return INSTANCE; + } + + @Override + public String getOperator() { + return "<="; + } + + @Override + public int getPriority() { + return QLPrecedences.COMPARE; + } + + @Override + public Object execute(Value left, Value right, QRuntime qRuntime, QLOptions qlOptions, + ErrorReporter errorReporter) { + if (qlOptions.isAvoidNullPointer() && (left.get() == null || right.get() == null)) { + return false; + } + return compare(left, right, errorReporter) <= 0; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/compare/LessOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/compare/LessOperator.java new file mode 100644 index 0000000..4879748 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/compare/LessOperator.java @@ -0,0 +1,41 @@ +package com.alibaba.qlexpress4.runtime.operator.compare; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.QLPrecedences; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.QRuntime; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.base.BaseBinaryOperator; + +/** + * @author bingo + */ +public class LessOperator extends BaseBinaryOperator { + private static final LessOperator INSTANCE = new LessOperator(); + + private LessOperator() { + } + + public static LessOperator getInstance() { + return INSTANCE; + } + + @Override + public String getOperator() { + return "<"; + } + + @Override + public int getPriority() { + return QLPrecedences.COMPARE; + } + + @Override + public Object execute(Value left, Value right, QRuntime qRuntime, QLOptions qlOptions, + ErrorReporter errorReporter) { + if (qlOptions.isAvoidNullPointer() && (left.get() == null || right.get() == null)) { + return false; + } + return compare(left, right, errorReporter) < 0; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/compare/UnequalOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/compare/UnequalOperator.java new file mode 100644 index 0000000..a60553a --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/compare/UnequalOperator.java @@ -0,0 +1,49 @@ +package com.alibaba.qlexpress4.runtime.operator.compare; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.QLPrecedences; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.QRuntime; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.base.BaseBinaryOperator; + +/** + * @author bingo + */ +public class UnequalOperator extends BaseBinaryOperator { + private static final Map INSTANCE_CACHE = new ConcurrentHashMap<>(2); + + static { + INSTANCE_CACHE.put("!=", new UnequalOperator("!=")); + INSTANCE_CACHE.put("<>", new UnequalOperator("<>")); + } + + private final String operator; + + private UnequalOperator(String operator) { + this.operator = operator; + } + + public static UnequalOperator getInstance(String operator) { + return INSTANCE_CACHE.get(operator); + } + + @Override + public String getOperator() { + return operator; + } + + @Override + public int getPriority() { + return QLPrecedences.EQUAL; + } + + @Override + public Object execute(Value left, Value right, QRuntime qRuntime, QLOptions qlOptions, + ErrorReporter errorReporter) { + return !equals(left, right, errorReporter); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/logic/LogicAndOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/logic/LogicAndOperator.java new file mode 100644 index 0000000..69df6e4 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/logic/LogicAndOperator.java @@ -0,0 +1,62 @@ +package com.alibaba.qlexpress4.runtime.operator.logic; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.QLPrecedences; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.QRuntime; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.base.BaseBinaryOperator; + +/** + * @author bingo + */ +public class LogicAndOperator extends BaseBinaryOperator { + private static final Map INSTANCE_CACHE = new ConcurrentHashMap<>(2); + + static { + INSTANCE_CACHE.put("&&", new LogicAndOperator("&&")); + INSTANCE_CACHE.put("and", new LogicAndOperator("and")); + } + + private final String operator; + + private LogicAndOperator(String operator) { + this.operator = operator; + } + + public static LogicAndOperator getInstance(String operator) { + return INSTANCE_CACHE.get(operator); + } + + @Override + public Object execute(Value left, Value right, QRuntime qRuntime, QLOptions qlOptions, + ErrorReporter errorReporter) { + Object leftValue = left.get(); + Object rightValue = right.get(); + if (leftValue == null) { + leftValue = false; + } + if (rightValue == null) { + rightValue = false; + } + + if (!(leftValue instanceof Boolean) || !(rightValue instanceof Boolean)) { + throw buildInvalidOperandTypeException(left, right, errorReporter); + } + + return (Boolean)leftValue && (Boolean)rightValue; + } + + @Override + public String getOperator() { + return operator; + } + + @Override + public int getPriority() { + return QLPrecedences.AND; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/logic/LogicNotOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/logic/LogicNotOperator.java new file mode 100644 index 0000000..f90350c --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/logic/LogicNotOperator.java @@ -0,0 +1,43 @@ +package com.alibaba.qlexpress4.runtime.operator.logic; + +import com.alibaba.qlexpress4.QLPrecedences; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.base.BaseUnaryOperator; + +/** + * @author bingo + */ +public class LogicNotOperator extends BaseUnaryOperator { + private static final LogicNotOperator INSTANCE = new LogicNotOperator(); + + private LogicNotOperator() { + } + + public static LogicNotOperator getInstance() { + return INSTANCE; + } + + @Override + public Object execute(Value value, ErrorReporter errorReporter) { + Object operand = value.get(); + if (operand == null) { + operand = false; + } + if (!(operand instanceof Boolean)) { + throw buildInvalidOperandTypeException(value, errorReporter); + } + + return !(Boolean)operand; + } + + @Override + public String getOperator() { + return "!"; + } + + @Override + public int getPriority() { + return QLPrecedences.UNARY; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/logic/LogicOrOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/logic/LogicOrOperator.java new file mode 100644 index 0000000..d64a520 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/logic/LogicOrOperator.java @@ -0,0 +1,55 @@ +package com.alibaba.qlexpress4.runtime.operator.logic; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.QLPrecedences; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.QRuntime; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.base.BaseBinaryOperator; + +/** + * @author bingo + */ +public class LogicOrOperator extends BaseBinaryOperator { + private static final Map INSTANCE_CACHE = new ConcurrentHashMap<>(2); + + static { + INSTANCE_CACHE.put("||", new LogicOrOperator("||")); + INSTANCE_CACHE.put("or", new LogicOrOperator("or")); + } + + private final String operator; + + private LogicOrOperator(String operator) { + this.operator = operator; + } + + public static LogicOrOperator getInstance(String operator) { + return INSTANCE_CACHE.get(operator); + } + + @Override + public Object execute(Value left, Value right, QRuntime qRuntime, QLOptions qlOptions, + ErrorReporter errorReporter) { + Object leftValue = left.get(); + Object rightValue = right.get(); + if (!(leftValue instanceof Boolean) || !(rightValue instanceof Boolean)) { + throw buildInvalidOperandTypeException(left, right, errorReporter); + } + + return (Boolean)leftValue || (Boolean)rightValue; + } + + @Override + public String getOperator() { + return operator; + } + + @Override + public int getPriority() { + return QLPrecedences.OR; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/number/BigDecimalMath.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/number/BigDecimalMath.java new file mode 100644 index 0000000..3c16b58 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/number/BigDecimalMath.java @@ -0,0 +1,109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.alibaba.qlexpress4.runtime.operator.number; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; + +/** + * BigDecimal NumberMath operations + */ +public final class BigDecimalMath extends NumberMath { + + // This is an arbitrary value, picked as a reasonable choice for a precision + // for typical user math when a non-terminating result would otherwise occur. + public static final int DIVISION_EXTRA_PRECISION = Integer.getInteger("qlexpress4.division.extra.precision", 10); + + //This is an arbitrary value, picked as a reasonable choice for a rounding point + //for typical user math. + public static final int DIVISION_MIN_SCALE = Integer.getInteger("qlexpress4.division.min.scale", 10); + + public static final BigDecimalMath INSTANCE = new BigDecimalMath(); + + private BigDecimalMath() { + } + + @Override + public Number absImpl(Number number) { + return toBigDecimal(number).abs(); + } + + @Override + public Number addImpl(Number left, Number right) { + return toBigDecimal(left).add(toBigDecimal(right)); + } + + @Override + public Number subtractImpl(Number left, Number right) { + return toBigDecimal(left).subtract(toBigDecimal(right)); + } + + @Override + public Number multiplyImpl(Number left, Number right) { + return toBigDecimal(left).multiply(toBigDecimal(right)); + } + + @Override + public Number divideImpl(Number left, Number right) { + BigDecimal bigLeft = toBigDecimal(left); + BigDecimal bigRight = toBigDecimal(right); + try { + return bigLeft.divide(bigRight); + } + catch (ArithmeticException e) { + // set a DEFAULT precision if otherwise non-terminating + int precision = Math.max(bigLeft.precision(), bigRight.precision()) + DIVISION_EXTRA_PRECISION; + BigDecimal result = bigLeft.divide(bigRight, new MathContext(precision)); + int scale = Math.max(Math.max(bigLeft.scale(), bigRight.scale()), DIVISION_MIN_SCALE); + if (result.scale() > scale) { + result = result.setScale(scale, RoundingMode.HALF_UP); + } + return result; + } + } + + @Override + public int compareToImpl(Number left, Number right) { + return toBigDecimal(left).compareTo(toBigDecimal(right)); + } + + @Override + public Number unaryMinusImpl(Number left) { + return toBigDecimal(left).negate(); + } + + @Override + public Number unaryPlusImpl(Number left) { + return toBigDecimal(left); + } + + @Override + public Number remainderImpl(Number left, Number right) { + return toBigDecimal(left).remainder(toBigDecimal(right)); + } + + @Override + public Number modImpl(Number self, Number divisor) { + BigDecimal selfDecimal = toBigDecimal(self); + BigDecimal divDecimal = toBigDecimal(divisor); + BigDecimal remainder = selfDecimal.remainder(divDecimal); + return remainder.signum() < 0 ? remainder.add(divDecimal) : remainder; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/number/BigIntegerMath.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/number/BigIntegerMath.java new file mode 100644 index 0000000..07af321 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/number/BigIntegerMath.java @@ -0,0 +1,116 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.alibaba.qlexpress4.runtime.operator.number; + +/** + * BigInteger NumberMath operations + * reference groovy source code + */ +public final class BigIntegerMath extends NumberMath { + + public static final BigIntegerMath INSTANCE = new BigIntegerMath(); + + private BigIntegerMath() { + } + + @Override + public Number absImpl(Number number) { + return toBigInteger(number).abs(); + } + + @Override + public Number addImpl(Number left, Number right) { + return toBigInteger(left).add(toBigInteger(right)); + } + + @Override + public Number subtractImpl(Number left, Number right) { + return toBigInteger(left).subtract(toBigInteger(right)); + } + + @Override + public Number multiplyImpl(Number left, Number right) { + return toBigInteger(left).multiply(toBigInteger(right)); + } + + @Override + public Number divideImpl(Number left, Number right) { + return BigDecimalMath.INSTANCE.divideImpl(left, right); + } + + @Override + public int compareToImpl(Number left, Number right) { + return toBigInteger(left).compareTo(toBigInteger(right)); + } + + @Override + public Number intDivImpl(Number left, Number right) { + return toBigInteger(left).divide(toBigInteger(right)); + } + + @Override + public Number modImpl(Number left, Number right) { + return toBigInteger(left).mod(toBigInteger(right)); + } + + @Override + public Number remainderImpl(Number left, Number right) { + return toBigInteger(left).remainder(toBigInteger(right)); + } + + @Override + public Number unaryMinusImpl(Number left) { + return toBigInteger(left).negate(); + } + + @Override + public Number unaryPlusImpl(Number left) { + return toBigInteger(left); + } + + @Override + public Number bitwiseNegateImpl(Number left) { + return toBigInteger(left).not(); + } + + @Override + public Number orImpl(Number left, Number right) { + return toBigInteger(left).or(toBigInteger(right)); + } + + @Override + public Number andImpl(Number left, Number right) { + return toBigInteger(left).and(toBigInteger(right)); + } + + @Override + public Number xorImpl(Number left, Number right) { + return toBigInteger(left).xor(toBigInteger(right)); + } + + @Override + public Number leftShiftImpl(Number left, Number right) { + return toBigInteger(left).shiftLeft(right.intValue()); + } + + @Override + public Number rightShiftImpl(Number left, Number right) { + return toBigInteger(left).shiftRight(right.intValue()); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/number/FloatingPointMath.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/number/FloatingPointMath.java new file mode 100644 index 0000000..3306cca --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/number/FloatingPointMath.java @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.alibaba.qlexpress4.runtime.operator.number; + +/** + * FloatingPoint (Double and Float) NumberMath operations + * reference groovy source code + */ +public final class FloatingPointMath extends NumberMath { + + public static final FloatingPointMath INSTANCE = new FloatingPointMath(); + + private FloatingPointMath() { + } + + @Override + public Number absImpl(Number number) { + return Math.abs(number.doubleValue()); + } + + @Override + public Number addImpl(Number left, Number right) { + return left.doubleValue() + right.doubleValue(); + } + + @Override + public Number subtractImpl(Number left, Number right) { + return left.doubleValue() - right.doubleValue(); + } + + @Override + public Number multiplyImpl(Number left, Number right) { + return left.doubleValue() * right.doubleValue(); + } + + @Override + public Number divideImpl(Number left, Number right) { + return left.doubleValue() / right.doubleValue(); + } + + @Override + public int compareToImpl(Number left, Number right) { + return Double.compare(left.doubleValue(), right.doubleValue()); + } + + @Override + public Number remainderImpl(Number left, Number right) { + return left.doubleValue() % right.doubleValue(); + } + + @Override + public Number modImpl(Number left, Number right) { + return toBigInteger(left).mod(toBigInteger(right)).doubleValue(); + } + + @Override + public Number unaryMinusImpl(Number left) { + return -left.doubleValue(); + } + + @Override + public Number unaryPlusImpl(Number left) { + return left.doubleValue(); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/number/IntegerMath.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/number/IntegerMath.java new file mode 100644 index 0000000..412bd3b --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/number/IntegerMath.java @@ -0,0 +1,123 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.alibaba.qlexpress4.runtime.operator.number; + +/** + * Integer NumberMath operations + * reference groovy source code + */ +public final class IntegerMath extends NumberMath { + + public static final IntegerMath INSTANCE = new IntegerMath(); + + private IntegerMath() { + } + + @Override + public Number absImpl(Number number) { + return Math.abs(number.intValue()); + } + + @Override + public Number addImpl(Number left, Number right) { + return left.intValue() + right.intValue(); + } + + @Override + public Number subtractImpl(Number left, Number right) { + return left.intValue() - right.intValue(); + } + + @Override + public Number multiplyImpl(Number left, Number right) { + return left.intValue() * right.intValue(); + } + + @Override + public Number divideImpl(Number left, Number right) { + return BigDecimalMath.INSTANCE.divideImpl(left, right); + } + + @Override + public int compareToImpl(Number left, Number right) { + int leftVal = left.intValue(); + int rightVal = right.intValue(); + return Integer.compare(leftVal, rightVal); + } + + @Override + public Number orImpl(Number left, Number right) { + return left.intValue() | right.intValue(); + } + + @Override + public Number andImpl(Number left, Number right) { + return left.intValue() & right.intValue(); + } + + @Override + public Number xorImpl(Number left, Number right) { + return left.intValue() ^ right.intValue(); + } + + @Override + public Number intDivImpl(Number left, Number right) { + return left.intValue() / right.intValue(); + } + + @Override + public Number modImpl(Number left, Number right) { + return toBigInteger(left).mod(toBigInteger(right)).intValue(); + } + + @Override + public Number remainderImpl(Number left, Number right) { + return left.intValue() % right.intValue(); + } + + @Override + public Number unaryMinusImpl(Number left) { + return -left.intValue(); + } + + @Override + public Number unaryPlusImpl(Number left) { + return left.intValue(); + } + + @Override + public Number bitwiseNegateImpl(Number left) { + return ~left.intValue(); + } + + @Override + public Number leftShiftImpl(Number left, Number right) { + return left.intValue() << right.intValue(); + } + + @Override + public Number rightShiftImpl(Number left, Number right) { + return left.intValue() >> right.intValue(); + } + + @Override + public Number rightShiftUnsignedImpl(Number left, Number right) { + return left.intValue() >>> right.intValue(); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/number/LongMath.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/number/LongMath.java new file mode 100644 index 0000000..6a78454 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/number/LongMath.java @@ -0,0 +1,127 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.alibaba.qlexpress4.runtime.operator.number; + +/** + * Long NumberMath operations + * reference groovy source code + */ +public final class LongMath extends NumberMath { + + public static final LongMath INSTANCE = new LongMath(); + + private LongMath() { + } + + @Override + public Number absImpl(Number number) { + return Math.abs(number.longValue()); + } + + @Override + public Number addImpl(Number left, Number right) { + return left.longValue() + right.longValue(); + } + + @Override + public Number subtractImpl(Number left, Number right) { + return left.longValue() - right.longValue(); + } + + @Override + public Number multiplyImpl(Number left, Number right) { + return left.longValue() * right.longValue(); + } + + @Override + public Number divideImpl(Number left, Number right) { + return BigDecimalMath.INSTANCE.divideImpl(left, right); + } + + @Override + public int compareToImpl(Number left, Number right) { + long leftVal = left.longValue(); + long rightVal = right.longValue(); + return Long.compare(leftVal, rightVal); + } + + @Override + public Number intDivImpl(Number left, Number right) { + return left.longValue() / right.longValue(); + } + + @Override + public Number remainderImpl(Number left, Number right) { + return left.longValue() % right.longValue(); + } + + @Override + public Number modImpl(Number left, Number right) { + return toBigInteger(left).mod(toBigInteger(right)).longValue(); + } + + @Override + public Number unaryMinusImpl(Number left) { + return -left.longValue(); + } + + @Override + public Number unaryPlusImpl(Number left) { + return left.longValue(); + } + + @Override + public Number bitwiseNegateImpl(Number left) { + return ~left.longValue(); + } + + @Override + public Number orImpl(Number left, Number right) { + return left.longValue() | right.longValue(); + } + + @Override + public Number andImpl(Number left, Number right) { + return left.longValue() & right.longValue(); + } + + @Override + public Number xorImpl(Number left, Number right) { + return left.longValue() ^ right.longValue(); + } + + @Override + public Number leftShiftImpl(Number left, Number right) { + return left.longValue() << right.longValue(); + } + + @Override + public Number rightShiftImpl(Number left, Number right) { + return left.longValue() >> right.longValue(); + } + + @Override + public Number rightShiftUnsignedImpl(Number left, Number right) { + return left.longValue() >>> right.longValue(); + } + + public Number bitAndImpl(Number left, Number right) { + return left.longValue() & right.longValue(); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/number/NumberMath.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/number/NumberMath.java new file mode 100644 index 0000000..cc04d31 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/number/NumberMath.java @@ -0,0 +1,338 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.alibaba.qlexpress4.runtime.operator.number; + +import java.math.BigDecimal; +import java.math.BigInteger; + +/** + * Stateless objects used to perform math on the various Number subclasses. + * Instances are required so that polymorphic calls work properly, but each + * subclass creates a singleton instance to minimize garbage. All methods + * must be thread-safe. + * + * The design goals of this class are as follows: + *

    + *
  1. Support a 'least surprising' math model to scripting language users. This + * means that exact, or decimal math should be used for default calculations. This + * scheme assumes that by default, groovy literals with decimal points are instantiated + * as BigDecimal objects rather than binary floating points (Float, Double). + *
  2. Do not force the appearance of exactness on a number that is by definition not + * guaranteed to be exact. In particular this means that if an operand in a NumberMath + * operation is a binary floating point number, ensure that the result remains a binary floating point + * number (i.e. never automatically promote a binary floating point number to a BigDecimal). + * This has the effect of preserving the expectations of binary floating point users and helps performance. + *
  3. Provide an implementation that is as close as practical to the Java 1.5 BigDecimal math model which implements + * precision based floating point decimal math (ANSI X3.274-1996 and ANSI X3.274-1996/AM 1-2000 (section 7.4). + *
+ * reference groovy source code + */ +public abstract class NumberMath { + + public static Number abs(Number number) { + return getMath(number).absImpl(number); + } + + public static Number add(Number left, Number right) { + return getMath(left, right).addImpl(left, right); + } + + public static Number subtract(Number left, Number right) { + return getMath(left, right).subtractImpl(left, right); + } + + public static Number multiply(Number left, Number right) { + return getMath(left, right).multiplyImpl(left, right); + } + + public static Number divide(Number left, Number right) { + return getMath(left, right).divideImpl(left, right); + } + + public static int compareTo(Number left, Number right) { + return getMath(left, right).compareToImpl(left, right); + } + + public static Number or(Number left, Number right) { + return getMath(left, right).orImpl(left, right); + } + + public static Number and(Number left, Number right) { + return getMath(left, right).andImpl(left, right); + } + + public static Number xor(Number left, Number right) { + return getMath(left, right).xorImpl(left, right); + } + + public static Number intDiv(Number left, Number right) { + return getMath(left, right).intDivImpl(left, right); + } + + // retain for backwards compatibility + public static Number mod(Number left, Number right) { + return getMath(left, right).modImpl(left, right); + } + + public static Number remainder(Number left, Number right) { + return getMath(left, right).remainderImpl(left, right); + } + + /* + * For this operation, consider the operands independently. Throw an exception if the right operand + * (shift distance) is not an integral type. For the left operand (shift value) also require an integral + * type, but do NOT promote from Integer to Long. This is consistent with Java, and makes sense for the + * shift operators. + */ + public static Number leftShift(Number left, Number right) { + if (isFloatingPoint(right) || isBigDecimal(right)) { + throw new UnsupportedOperationException("Shift distance must be an integral type, but " + right + " (" + + right.getClass().getName() + ") was supplied"); + } + return getMath(left).leftShiftImpl(left, right); + } + + /* + * For this operation, consider the operands independently. Throw an exception if the right operand + * (shift distance) is not an integral type. For the left operand (shift value) also require an integral + * type, but do NOT promote from Integer to Long. This is consistent with Java, and makes sense for the + * shift operators. + */ + public static Number rightShift(Number left, Number right) { + if (isFloatingPoint(right) || isBigDecimal(right)) { + throw new UnsupportedOperationException("Shift distance must be an integral type, but " + right + " (" + + right.getClass().getName() + ") was supplied"); + } + return getMath(left).rightShiftImpl(left, right); + } + + /* + * For this operation, consider the operands independently. Throw an exception if the right operand + * (shift distance) is not an integral type. For the left operand (shift value) also require an integral + * type, but do NOT promote from Integer to Long. This is consistent with Java, and makes sense for the + * shift operators. + */ + public static Number rightShiftUnsigned(Number left, Number right) { + if (isFloatingPoint(right) || isBigDecimal(right)) { + throw new UnsupportedOperationException("Shift distance must be an integral type, but " + right + " (" + + right.getClass().getName() + ") was supplied"); + } + return getMath(left).rightShiftUnsignedImpl(left, right); + } + + public static Number bitwiseNegate(Number left) { + return getMath(left).bitwiseNegateImpl(left); + } + + public static Number unaryMinus(Number left) { + return getMath(left).unaryMinusImpl(left); + } + + public static Number unaryPlus(Number left) { + return getMath(left).unaryPlusImpl(left); + } + + public static boolean isFloatingPoint(Number number) { + return number instanceof Double || number instanceof Float; + } + + public static boolean isInteger(Number number) { + return number instanceof Integer; + } + + public static boolean isShort(Number number) { + return number instanceof Short; + } + + public static boolean isByte(Number number) { + return number instanceof Byte; + } + + public static boolean isLong(Number number) { + return number instanceof Long; + } + + public static boolean isBigDecimal(Number number) { + return number instanceof BigDecimal; + } + + public static boolean isBigInteger(Number number) { + return number instanceof BigInteger; + } + + public static BigDecimal toBigDecimal(Number n) { + if (n instanceof BigDecimal) { + return (BigDecimal)n; + } + if (n instanceof BigInteger) { + return new BigDecimal((BigInteger)n); + } + if (n instanceof Integer || n instanceof Long || n instanceof Byte || n instanceof Short) { + return BigDecimal.valueOf(n.longValue()); + } + try { + return new BigDecimal(n.toString()); + } + catch (NumberFormatException nfe) { + return BigDecimal.valueOf(n.doubleValue()); + } + } + + public static BigInteger toBigInteger(Number n) { + if (n instanceof BigInteger) { + return (BigInteger)n; + } + if (n instanceof Integer || n instanceof Long || n instanceof Byte || n instanceof Short) { + return BigInteger.valueOf(n.longValue()); + } + + if (n instanceof Float || n instanceof Double) { + BigDecimal bd = new BigDecimal(n.toString()); + return bd.toBigInteger(); + } + if (n instanceof BigDecimal) { + return ((BigDecimal)n).toBigInteger(); + } + + return new BigInteger(n.toString()); + } + + /* + * Determine which NumberMath instance to use, given the supplied operands. This method implements + * the type promotion rules discussed in the documentation. Note that by the time this method is + * called, any Byte, Character or Short operands will have been promoted to Integer. For reference, + * here is the promotion matrix: + * bD bI D F L I + * bD bD bD D D bD bD + * bI bD bI D D bI bI + * D D D D D D D + * F D D D D D D + * L bD bI D D L L + * I bD bI D D L I + * + * Note that for division, if either operand isFloatingPoint, the result will be floating. Otherwise, + * the result is BigDecimal + */ + public static NumberMath getMath(Number left, Number right) { + // FloatingPointMath wins according to promotion Matrix + if (isFloatingPoint(left) || isFloatingPoint(right)) { + return FloatingPointMath.INSTANCE; + } + NumberMath leftMath = getMath(left); + NumberMath rightMath = getMath(right); + + if (leftMath == BigDecimalMath.INSTANCE || rightMath == BigDecimalMath.INSTANCE) { + return BigDecimalMath.INSTANCE; + } + if (leftMath == BigIntegerMath.INSTANCE || rightMath == BigIntegerMath.INSTANCE) { + return BigIntegerMath.INSTANCE; + } + if (leftMath == LongMath.INSTANCE || rightMath == LongMath.INSTANCE) { + return LongMath.INSTANCE; + } + if (leftMath == IntegerMath.INSTANCE || rightMath == IntegerMath.INSTANCE) { + return IntegerMath.INSTANCE; + } + // also for custom Number implementations + return BigDecimalMath.INSTANCE; + } + + /* package private */ + static NumberMath getMath(Number number) { + if (isLong(number)) { + return LongMath.INSTANCE; + } + if (isFloatingPoint(number)) { + return FloatingPointMath.INSTANCE; + } + if (isBigDecimal(number)) { + return BigDecimalMath.INSTANCE; + } + if (isBigInteger(number)) { + return BigIntegerMath.INSTANCE; + } + if (isInteger(number) || isShort(number) || isByte(number)) { + return IntegerMath.INSTANCE; + } + // also for custom Number implementations + return BigDecimalMath.INSTANCE; + } + + //Subclasses implement according to the type promotion hierarchy rules + protected abstract Number absImpl(Number number); + + public abstract Number addImpl(Number left, Number right); + + public abstract Number subtractImpl(Number left, Number right); + + public abstract Number multiplyImpl(Number left, Number right); + + public abstract Number divideImpl(Number left, Number right); + + public abstract int compareToImpl(Number left, Number right); + + protected abstract Number unaryMinusImpl(Number left); + + protected abstract Number unaryPlusImpl(Number left); + + protected Number bitwiseNegateImpl(Number left) { + throw createUnsupportedException("bitwiseNegate()", left); + } + + protected Number orImpl(Number left, Number right) { + throw createUnsupportedException("or()", left); + } + + protected Number andImpl(Number left, Number right) { + throw createUnsupportedException("and()", left); + } + + protected Number xorImpl(Number left, Number right) { + throw createUnsupportedException("xor()", left); + } + + protected Number remainderImpl(Number left, Number right) { + throw createUnsupportedException("remainder()", left); + } + + protected Number modImpl(Number left, Number right) { + throw createUnsupportedException("mod()", left); + } + + protected Number intDivImpl(Number left, Number right) { + throw createUnsupportedException("intDiv()", left); + } + + protected Number leftShiftImpl(Number left, Number right) { + throw createUnsupportedException("leftShift()", left); + } + + protected Number rightShiftImpl(Number left, Number right) { + throw createUnsupportedException("rightShift()", left); + } + + protected Number rightShiftUnsignedImpl(Number left, Number right) { + throw createUnsupportedException("rightShiftUnsigned()", left); + } + + protected UnsupportedOperationException createUnsupportedException(String operation, Number left) { + return new UnsupportedOperationException( + "Cannot use " + operation + " on this number type: " + left.getClass().getName() + " with value: " + left); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/string/LikeOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/string/LikeOperator.java new file mode 100644 index 0000000..d04ca0d --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/string/LikeOperator.java @@ -0,0 +1,36 @@ +package com.alibaba.qlexpress4.runtime.operator.string; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.QLPrecedences; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.QRuntime; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.BinaryOperator; +import com.alibaba.qlexpress4.runtime.operator.base.BaseBinaryOperator; + +/** + * Author: DQinYuan + */ +public class LikeOperator extends BaseBinaryOperator { + private static final LikeOperator INSTANCE = new LikeOperator(); + + public static BinaryOperator getInstance() { + return INSTANCE; + } + + @Override + public Object execute(Value left, Value right, QRuntime qRuntime, QLOptions qlOptions, + ErrorReporter errorReporter) { + return like(left, right, errorReporter); + } + + @Override + public String getOperator() { + return "like"; + } + + @Override + public int getPriority() { + return QLPrecedences.IN_LIKE; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/string/NotLikeOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/string/NotLikeOperator.java new file mode 100644 index 0000000..1a6f9fd --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/string/NotLikeOperator.java @@ -0,0 +1,36 @@ +package com.alibaba.qlexpress4.runtime.operator.string; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.QLPrecedences; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.QRuntime; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.BinaryOperator; +import com.alibaba.qlexpress4.runtime.operator.base.BaseBinaryOperator; + +/** + * @author bingo + */ +public class NotLikeOperator extends BaseBinaryOperator { + private static final NotLikeOperator INSTANCE = new NotLikeOperator(); + + public static BinaryOperator getInstance() { + return INSTANCE; + } + + @Override + public Object execute(Value left, Value right, QRuntime qRuntime, QLOptions qlOptions, + ErrorReporter errorReporter) { + return !like(left, right, errorReporter); + } + + @Override + public String getOperator() { + return "not_like"; + } + + @Override + public int getPriority() { + return QLPrecedences.IN_LIKE; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/unary/MinusMinusPrefixUnaryOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/unary/MinusMinusPrefixUnaryOperator.java new file mode 100644 index 0000000..5339862 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/unary/MinusMinusPrefixUnaryOperator.java @@ -0,0 +1,45 @@ +package com.alibaba.qlexpress4.runtime.operator.unary; + +import com.alibaba.qlexpress4.QLPrecedences; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.LeftValue; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.base.BaseUnaryOperator; +import com.alibaba.qlexpress4.runtime.operator.number.NumberMath; + +/** + * @author bingo + */ +public class MinusMinusPrefixUnaryOperator extends BaseUnaryOperator { + private static final MinusMinusPrefixUnaryOperator INSTANCE = new MinusMinusPrefixUnaryOperator(); + + private MinusMinusPrefixUnaryOperator() { + } + + public static MinusMinusPrefixUnaryOperator getInstance() { + return INSTANCE; + } + + @Override + public String getOperator() { + return "--"; + } + + @Override + public int getPriority() { + return QLPrecedences.UNARY; + } + + @Override + public Object execute(Value value, ErrorReporter errorReporter) { + Object operand = value.get(); + if (!(operand instanceof Number)) { + throw buildInvalidOperandTypeException(value, errorReporter); + } + + if (value instanceof LeftValue) { + ((LeftValue)value).set(NumberMath.subtract((Number)operand, 1), errorReporter); + } + return operand; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/unary/MinusMinusSuffixUnaryOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/unary/MinusMinusSuffixUnaryOperator.java new file mode 100644 index 0000000..0ae9b8d --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/unary/MinusMinusSuffixUnaryOperator.java @@ -0,0 +1,46 @@ +package com.alibaba.qlexpress4.runtime.operator.unary; + +import com.alibaba.qlexpress4.QLPrecedences; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.LeftValue; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.base.BaseUnaryOperator; +import com.alibaba.qlexpress4.runtime.operator.number.NumberMath; + +/** + * @author bingo + */ +public class MinusMinusSuffixUnaryOperator extends BaseUnaryOperator { + private static final MinusMinusSuffixUnaryOperator INSTANCE = new MinusMinusSuffixUnaryOperator(); + + private MinusMinusSuffixUnaryOperator() { + } + + public static MinusMinusSuffixUnaryOperator getInstance() { + return INSTANCE; + } + + @Override + public String getOperator() { + return "--"; + } + + @Override + public int getPriority() { + return QLPrecedences.UNARY_SUFFIX; + } + + @Override + public Object execute(Value value, ErrorReporter errorReporter) { + Object operand = value.get(); + if (!(operand instanceof Number)) { + throw buildInvalidOperandTypeException(value, errorReporter); + } + + Number result = NumberMath.subtract((Number)operand, 1); + if (value instanceof LeftValue) { + ((LeftValue)value).set(result, errorReporter); + } + return result; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/unary/MinusUnaryOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/unary/MinusUnaryOperator.java new file mode 100644 index 0000000..3b185b0 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/unary/MinusUnaryOperator.java @@ -0,0 +1,41 @@ +package com.alibaba.qlexpress4.runtime.operator.unary; + +import com.alibaba.qlexpress4.QLPrecedences; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.base.BaseUnaryOperator; +import com.alibaba.qlexpress4.runtime.operator.number.NumberMath; + +/** + * @author bingo + */ +public class MinusUnaryOperator extends BaseUnaryOperator { + private static final MinusUnaryOperator INSTANCE = new MinusUnaryOperator(); + + private MinusUnaryOperator() { + } + + public static MinusUnaryOperator getInstance() { + return INSTANCE; + } + + @Override + public String getOperator() { + return "-"; + } + + @Override + public int getPriority() { + return QLPrecedences.UNARY; + } + + @Override + public Object execute(Value value, ErrorReporter errorReporter) { + Object operand = value.get(); + if (!(operand instanceof Number)) { + throw buildInvalidOperandTypeException(value, errorReporter); + } + + return NumberMath.unaryMinus((Number)operand); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/unary/PlusPlusPrefixUnaryOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/unary/PlusPlusPrefixUnaryOperator.java new file mode 100644 index 0000000..38dd7f2 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/unary/PlusPlusPrefixUnaryOperator.java @@ -0,0 +1,46 @@ +package com.alibaba.qlexpress4.runtime.operator.unary; + +import com.alibaba.qlexpress4.QLPrecedences; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.LeftValue; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.base.BaseUnaryOperator; +import com.alibaba.qlexpress4.runtime.operator.number.NumberMath; + +/** + * @author bingo + */ +public class PlusPlusPrefixUnaryOperator extends BaseUnaryOperator { + private static final PlusPlusPrefixUnaryOperator INSTANCE = new PlusPlusPrefixUnaryOperator(); + + private PlusPlusPrefixUnaryOperator() { + } + + public static PlusPlusPrefixUnaryOperator getInstance() { + return INSTANCE; + } + + @Override + public String getOperator() { + return "++"; + } + + @Override + public int getPriority() { + return QLPrecedences.UNARY; + } + + @Override + public Object execute(Value value, ErrorReporter errorReporter) { + Object operand = value.get(); + if (!(operand instanceof Number)) { + throw buildInvalidOperandTypeException(value, errorReporter); + } + + Number result = NumberMath.add((Number)operand, 1); + if (value instanceof LeftValue) { + ((LeftValue)value).set(result, errorReporter); + } + return result; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/unary/PlusPlusSuffixUnaryOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/unary/PlusPlusSuffixUnaryOperator.java new file mode 100644 index 0000000..110a8a5 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/unary/PlusPlusSuffixUnaryOperator.java @@ -0,0 +1,45 @@ +package com.alibaba.qlexpress4.runtime.operator.unary; + +import com.alibaba.qlexpress4.QLPrecedences; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.LeftValue; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.base.BaseUnaryOperator; +import com.alibaba.qlexpress4.runtime.operator.number.NumberMath; + +/** + * @author bingo + */ +public class PlusPlusSuffixUnaryOperator extends BaseUnaryOperator { + private static final PlusPlusSuffixUnaryOperator INSTANCE = new PlusPlusSuffixUnaryOperator(); + + private PlusPlusSuffixUnaryOperator() { + } + + public static PlusPlusSuffixUnaryOperator getInstance() { + return INSTANCE; + } + + @Override + public String getOperator() { + return "++"; + } + + @Override + public int getPriority() { + return QLPrecedences.UNARY_SUFFIX; + } + + @Override + public Object execute(Value value, ErrorReporter errorReporter) { + Object operand = value.get(); + if (!(operand instanceof Number)) { + throw buildInvalidOperandTypeException(value, errorReporter); + } + + if (value instanceof LeftValue) { + ((LeftValue)value).set(NumberMath.add((Number)operand, 1), errorReporter); + } + return operand; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/unary/PlusUnaryOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/unary/PlusUnaryOperator.java new file mode 100644 index 0000000..4125157 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/unary/PlusUnaryOperator.java @@ -0,0 +1,41 @@ +package com.alibaba.qlexpress4.runtime.operator.unary; + +import com.alibaba.qlexpress4.QLPrecedences; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.base.BaseUnaryOperator; +import com.alibaba.qlexpress4.runtime.operator.number.NumberMath; + +/** + * @author bingo + */ +public class PlusUnaryOperator extends BaseUnaryOperator { + private static final PlusUnaryOperator INSTANCE = new PlusUnaryOperator(); + + private PlusUnaryOperator() { + } + + public static PlusUnaryOperator getInstance() { + return INSTANCE; + } + + @Override + public String getOperator() { + return "+"; + } + + @Override + public int getPriority() { + return QLPrecedences.UNARY; + } + + @Override + public Object execute(Value value, ErrorReporter errorReporter) { + Object operand = value.get(); + if (!(operand instanceof Number)) { + throw buildInvalidOperandTypeException(value, errorReporter); + } + + return NumberMath.unaryPlus((Number)operand); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/operator/unary/UnaryOperator.java b/src/main/java/com/alibaba/qlexpress4/runtime/operator/unary/UnaryOperator.java new file mode 100644 index 0000000..1cb3590 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/operator/unary/UnaryOperator.java @@ -0,0 +1,20 @@ +package com.alibaba.qlexpress4.runtime.operator.unary; + +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.Operator; + +/** + * unary operator, include: + * prefix operator + * suffix operator + * Author: DQinYuan + */ +public interface UnaryOperator extends Operator { + /** + * @param value operand + * @param errorReporter operator + * @return result of operator + */ + Object execute(Value value, ErrorReporter errorReporter); +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/scope/QScope.java b/src/main/java/com/alibaba/qlexpress4/runtime/scope/QScope.java new file mode 100644 index 0000000..575768b --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/scope/QScope.java @@ -0,0 +1,91 @@ +package com.alibaba.qlexpress4.runtime.scope; + +import com.alibaba.qlexpress4.runtime.*; +import com.alibaba.qlexpress4.runtime.function.CustomFunction; + +import java.util.Map; + +/** + * Author: DQinYuan + */ +public interface QScope { + + /** + * get assignable symbol variable by name + * @param varName variable name + * @return value, null if not exist + */ + Value getSymbol(String varName); + + /** + * get symbol variable value by name + * @param varName variable name + * @return inner value, null if not exist + */ + default Object getSymbolValue(String varName) { + Value symbolVal = getSymbol(varName); + return symbolVal == null ? null : symbolVal.get(); + } + + /** + * define a symbol in local scope + * @param varName variable name + * @param varClz class of variable + * @param value init value + */ + void defineLocalSymbol(String varName, Class varClz, Object value); + + /** + * define local function in scope + * @param functionName name of function + * @param function implement of function + */ + void defineFunction(String functionName, CustomFunction function); + + /** + * get function or lambda define + * @param functionName name of function + * @return null if not exist + */ + CustomFunction getFunction(String functionName); + + /** + * get function table in this scope + * @return function table + */ + Map getFunctionTable(); + + /** + * push value on the top of stack + * @param value pushed element + */ + void push(Value value); + + /** + * pop number elements on top of stack + * @param number pop elements' number + * @return popped elements + */ + Parameters pop(int number); + + /** + * pop one element on top of stack + * @return popped element + */ + Value pop(); + + /** + * @return top element of stack without pop + */ + Value peek(); + + /** + * @return parent scope + */ + QScope getParent(); + + /** + * @return new scope base origin stack + */ + QScope newScope(); +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/scope/QvmBlockScope.java b/src/main/java/com/alibaba/qlexpress4/runtime/scope/QvmBlockScope.java new file mode 100644 index 0000000..de3bedd --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/scope/QvmBlockScope.java @@ -0,0 +1,96 @@ +package com.alibaba.qlexpress4.runtime.scope; + +import com.alibaba.qlexpress4.runtime.*; +import com.alibaba.qlexpress4.runtime.data.AssignableDataValue; +import com.alibaba.qlexpress4.runtime.function.CustomFunction; + +import java.util.HashMap; +import java.util.Map; + +/** + * Author: DQinYuan + */ +public class QvmBlockScope implements QScope { + + private final QScope parent; + + private final Map symbolTable; + + private final Map functionTable; + + private final FixedSizeStack opStack; + + private final ExceptionTable exceptionTable; + + public QvmBlockScope(QScope parent, Map symbolTable, int maxStackSize, + ExceptionTable exceptionTable) { + this(parent, symbolTable, new FixedSizeStack(maxStackSize), exceptionTable); + } + + public QvmBlockScope(QScope parent, Map symbolTable, FixedSizeStack reuseStack, + ExceptionTable exceptionTable) { + this.parent = parent; + this.symbolTable = symbolTable; + this.functionTable = new HashMap<>(); + this.opStack = reuseStack; + this.exceptionTable = exceptionTable; + } + + @Override + public Value getSymbol(String varName) { + Value localSymbol = symbolTable.get(varName); + return localSymbol != null ? localSymbol : parent.getSymbol(varName); + } + + @Override + public void defineLocalSymbol(String varName, Class varClz, Object value) { + symbolTable.put(varName, new AssignableDataValue(varName, value, varClz)); + } + + @Override + public void defineFunction(String functionName, CustomFunction function) { + functionTable.put(functionName, function); + } + + @Override + public CustomFunction getFunction(String functionName) { + CustomFunction function = functionTable.get(functionName); + return function == null ? parent.getFunction(functionName) : function; + } + + @Override + public Map getFunctionTable() { + return functionTable; + } + + @Override + public void push(Value value) { + opStack.push(value); + } + + @Override + public Parameters pop(int number) { + return opStack.pop(number); + } + + @Override + public Value pop() { + return opStack.pop(); + } + + @Override + public Value peek() { + return opStack.peak(); + } + + @Override + public QScope getParent() { + return parent; + } + + @Override + public QScope newScope() { + return new QvmBlockScope(this, new HashMap<>(), opStack, exceptionTable); + } + +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/trace/ExpressionTrace.java b/src/main/java/com/alibaba/qlexpress4/runtime/trace/ExpressionTrace.java new file mode 100644 index 0000000..d7cbce5 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/trace/ExpressionTrace.java @@ -0,0 +1,96 @@ +package com.alibaba.qlexpress4.runtime.trace; + +import com.alibaba.qlexpress4.utils.PrintlnUtils; + +import java.util.List; + +public class ExpressionTrace { + + public ExpressionTrace(TraceType type, String token, List children, Integer line, Integer col, + Integer position) { + this.type = type; + this.token = token; + this.children = children; + this.line = line; + this.col = col; + this.position = position; + } + + private final TraceType type; + + private final String token; + + /** + * Intermediate calculation result of this trace point + */ + private Object value; + + /** + * true if this point is evaluated in this execution + * false if short-circuited + */ + private boolean evaluated; + + private final List children; + + /** + * The corresponding line number in the source code + */ + private final int line; + + /** + * The corresponding col number in the source code + */ + private final int col; + + /** + * The corresponding position of character in the source code string + */ + private final int position; + + public String toPrettyString(int indent) { + StringBuilder nodeStringBuilder = new StringBuilder( + PrintlnUtils.buildIndentString(indent, type + " " + token + " " + (evaluated ? value : ""))).append('\n'); + for (ExpressionTrace child : children) { + nodeStringBuilder.append(child.toPrettyString(indent + 2)); + } + return nodeStringBuilder.toString(); + } + + public void valueEvaluated(Object value) { + this.value = value; + this.evaluated = true; + } + + public TraceType getType() { + return type; + } + + public String getToken() { + return token; + } + + public Object getValue() { + return value; + } + + public boolean isEvaluated() { + return evaluated; + } + + public List getChildren() { + return children; + } + + public int getLine() { + return line; + } + + public int getCol() { + return col; + } + + public int getPosition() { + return position; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/trace/QTraces.java b/src/main/java/com/alibaba/qlexpress4/runtime/trace/QTraces.java new file mode 100644 index 0000000..90e3dbf --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/trace/QTraces.java @@ -0,0 +1,30 @@ +package com.alibaba.qlexpress4.runtime.trace; + +import java.util.List; +import java.util.Map; + +public class QTraces { + + private final List expressionTraces; + + private final Map expressionTraceMap; + + public QTraces(List expressionTraces, Map expressionTraceMap) { + this.expressionTraces = expressionTraces; + this.expressionTraceMap = expressionTraceMap; + } + + public ExpressionTrace getExpressionTraceByKey(Integer traceKey) { + if (traceKey == null) { + return null; + } + if (expressionTraceMap == null) { + return null; + } + return expressionTraceMap.get(traceKey); + } + + public List getExpressionTraces() { + return expressionTraces; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/trace/TracePointTree.java b/src/main/java/com/alibaba/qlexpress4/runtime/trace/TracePointTree.java new file mode 100644 index 0000000..f9c2ec9 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/trace/TracePointTree.java @@ -0,0 +1,62 @@ +package com.alibaba.qlexpress4.runtime.trace; + +import com.alibaba.qlexpress4.utils.PrintlnUtils; + +import java.util.List; + +public class TracePointTree { + private final TraceType type; + + private final String token; + + private final List children; + + private final int line; + + private final int col; + + private final int position; + + public TracePointTree(TraceType type, String token, List children, int line, int col, + int position) { + this.type = type; + this.token = token; + this.children = children; + this.line = line; + this.col = col; + this.position = position; + } + + public String toPrettyString(int indent) { + StringBuilder nodeStringBuilder = + new StringBuilder(PrintlnUtils.buildIndentString(indent, type + " " + token)).append('\n'); + for (TracePointTree child : children) { + nodeStringBuilder.append(child.toPrettyString(indent + 2)); + } + return nodeStringBuilder.toString(); + } + + public TraceType getType() { + return type; + } + + public String getToken() { + return token; + } + + public List getChildren() { + return children; + } + + public int getLine() { + return line; + } + + public int getCol() { + return col; + } + + public int getPosition() { + return position; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/trace/TraceType.java b/src/main/java/com/alibaba/qlexpress4/runtime/trace/TraceType.java new file mode 100644 index 0000000..4400ca1 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/trace/TraceType.java @@ -0,0 +1,10 @@ +package com.alibaba.qlexpress4.runtime.trace; + +public enum TraceType { + // parent + OPERATOR, FUNCTION, METHOD, FIELD, LIST, MAP, IF, RETURN, BLOCK, + // children + VARIABLE, VALUE, DEFINE_FUNCTION, DEFINE_MACRO, + // other composite children + PRIMARY, STATEMENT +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/util/MethodInvokeUtils.java b/src/main/java/com/alibaba/qlexpress4/runtime/util/MethodInvokeUtils.java new file mode 100644 index 0000000..0552766 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/util/MethodInvokeUtils.java @@ -0,0 +1,73 @@ +package com.alibaba.qlexpress4.runtime.util; + +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.exception.QLErrorCodes; +import com.alibaba.qlexpress4.exception.UserDefineException; +import com.alibaba.qlexpress4.member.MethodHandler; +import com.alibaba.qlexpress4.runtime.IMethod; +import com.alibaba.qlexpress4.runtime.QLambda; +import com.alibaba.qlexpress4.runtime.QResult; +import com.alibaba.qlexpress4.runtime.ReflectLoader; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.data.DataValue; +import com.alibaba.qlexpress4.runtime.data.convert.ParametersTypeConvertor; + +import java.util.Arrays; +import java.util.Map; + +/** + * Author: DQinYuan + */ +public class MethodInvokeUtils { + + public static Value findMethodAndInvoke(Object bean, String methodName, Object[] params, Class[] type, + ReflectLoader reflectLoader, ErrorReporter errorReporter) { + IMethod method = reflectLoader.loadMethod(bean, methodName, type); + if (method == null) { + QLambda qLambdaInnerMethod = findQLambdaInstance(bean, methodName); + if (qLambdaInnerMethod != null) { + try { + QResult qResult = qLambdaInnerMethod.call(params); + return ValueUtils.toImmutable(qResult.getResult()); + } + catch (UserDefineException e) { + throw ThrowUtils.reportUserDefinedException(errorReporter, e); + } + catch (Throwable t) { + throw ThrowUtils.wrapThrowable(t, + errorReporter, + QLErrorCodes.INVOKE_LAMBDA_ERROR.name(), + QLErrorCodes.INVOKE_LAMBDA_ERROR.getErrorMsg()); + } + } + else { + throw errorReporter.report(QLErrorCodes.METHOD_NOT_FOUND.name(), + String.format(QLErrorCodes.METHOD_NOT_FOUND.getErrorMsg(), methodName, Arrays.toString(params))); + } + } + else { + // method invoke + Object[] convertResult = + ParametersTypeConvertor.cast(params, method.getParameterTypes(), method.isVarArgs()); + try { + Object value = MethodHandler.Access.accessMethodValue(method, bean, convertResult); + return new DataValue(value); + } + catch (Exception e) { + throw ReflectLoader.unwrapMethodInvokeEx(errorReporter, methodName, e); + } + } + } + + private static QLambda findQLambdaInstance(Object bean, String methodName) { + if (bean instanceof Map) { + Map map = (Map)bean; + Object mapValue = map.get(methodName); + if (mapValue instanceof QLambda) { + return (QLambda)mapValue; + } + } + return null; + } + +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/util/ThrowUtils.java b/src/main/java/com/alibaba/qlexpress4/runtime/util/ThrowUtils.java new file mode 100644 index 0000000..b44f193 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/util/ThrowUtils.java @@ -0,0 +1,29 @@ +package com.alibaba.qlexpress4.runtime.util; + +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.exception.QLErrorCodes; +import com.alibaba.qlexpress4.exception.QLRuntimeException; +import com.alibaba.qlexpress4.exception.UserDefineException; + +import java.util.Objects; + +/** + * Author: DQinYuan + */ +public class ThrowUtils { + + public static QLRuntimeException wrapThrowable(Throwable t, ErrorReporter errorReporter, String errCode, + String errMsg, String... args) { + return t instanceof QLRuntimeException ? (QLRuntimeException)t + : errorReporter.reportFormatWithCatch(t, errCode, errMsg, (Object[])args); + } + + public static QLRuntimeException reportUserDefinedException(ErrorReporter errorReporter, UserDefineException e) { + if (Objects.equals(e.getType(), UserDefineException.ExceptionType.INVALID_ARGUMENT)) { + throw errorReporter.report(QLErrorCodes.INVALID_ARGUMENT.name(), e.getMessage()); + } + else { + throw errorReporter.report(QLErrorCodes.BIZ_EXCEPTION.name(), e.getMessage()); + } + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/runtime/util/ValueUtils.java b/src/main/java/com/alibaba/qlexpress4/runtime/util/ValueUtils.java new file mode 100644 index 0000000..f3e12df --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/runtime/util/ValueUtils.java @@ -0,0 +1,28 @@ +package com.alibaba.qlexpress4.runtime.util; + +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.LeftValue; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.data.DataValue; + +/** + * Author: DQinYuan + */ +public class ValueUtils { + + public static Value toImmutable(Value origin) { + return origin instanceof LeftValue ? new DataValue(origin) : origin; + } + + public static T assertType(Object obj, Class assertType, String errCode, String errMsg, + ErrorReporter errorReporter) { + if (obj != null && assertType.isAssignableFrom(obj.getClass())) { + return assertType.cast(obj); + } + throw errorReporter.report(errCode, errMsg); + } + + public static int javaIndex(int length, int qlIndex) { + return qlIndex < 0 ? length + qlIndex : qlIndex; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/security/QLSecurityStrategy.java b/src/main/java/com/alibaba/qlexpress4/security/QLSecurityStrategy.java new file mode 100644 index 0000000..bdb734e --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/security/QLSecurityStrategy.java @@ -0,0 +1,34 @@ +package com.alibaba.qlexpress4.security; + +import java.lang.reflect.Member; +import java.util.Set; + +/** + * Author: DQinYuan + */ +public interface QLSecurityStrategy { + + static QLSecurityStrategy open() { + return StrategyOpen.getInstance(); + } + + static QLSecurityStrategy isolation() { + return StrategyIsolation.getInstance(); + } + + static QLSecurityStrategy blackList(Set blackList) { + return new StrategyBlackList(blackList); + } + + static QLSecurityStrategy whiteList(Set whiteList) { + return new StrategyWhiteList(whiteList); + } + + /** + * check if member secure + * @param member member of object + * @return true if secure + */ + boolean check(Member member); + +} diff --git a/src/main/java/com/alibaba/qlexpress4/security/StrategyBlackList.java b/src/main/java/com/alibaba/qlexpress4/security/StrategyBlackList.java new file mode 100644 index 0000000..9081b05 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/security/StrategyBlackList.java @@ -0,0 +1,23 @@ +package com.alibaba.qlexpress4.security; + +import java.lang.reflect.Member; +import java.util.Set; + +/** + * + * A security policy that prohibits access to Java members in the blacklist. + * Author: DQinYuan + */ +public class StrategyBlackList implements QLSecurityStrategy { + + private final Set blackList; + + public StrategyBlackList(Set blackList) { + this.blackList = blackList; + } + + @Override + public boolean check(Member member) { + return !blackList.contains(member); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/security/StrategyIsolation.java b/src/main/java/com/alibaba/qlexpress4/security/StrategyIsolation.java new file mode 100644 index 0000000..78ff867 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/security/StrategyIsolation.java @@ -0,0 +1,21 @@ +package com.alibaba.qlexpress4.security; + +import java.lang.reflect.Member; + +/** + * A security policy that isolates qlexpress script with jvm + * Author: DQinYuan + */ +public class StrategyIsolation implements QLSecurityStrategy { + + private static final StrategyIsolation INSTANCE = new StrategyIsolation(); + + public static StrategyIsolation getInstance() { + return INSTANCE; + } + + @Override + public boolean check(Member member) { + throw new IllegalStateException(); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/security/StrategyOpen.java b/src/main/java/com/alibaba/qlexpress4/security/StrategyOpen.java new file mode 100644 index 0000000..18086f6 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/security/StrategyOpen.java @@ -0,0 +1,21 @@ +package com.alibaba.qlexpress4.security; + +import java.lang.reflect.Member; + +/** + * A security policy that allows access to all Java classes within the application. + * Author: DQinYuan + */ +public class StrategyOpen implements QLSecurityStrategy { + + private static final StrategyOpen INSTANCE = new StrategyOpen(); + + public static StrategyOpen getInstance() { + return INSTANCE; + } + + @Override + public boolean check(Member member) { + return true; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/security/StrategyWhiteList.java b/src/main/java/com/alibaba/qlexpress4/security/StrategyWhiteList.java new file mode 100644 index 0000000..892728e --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/security/StrategyWhiteList.java @@ -0,0 +1,22 @@ +package com.alibaba.qlexpress4.security; + +import java.lang.reflect.Member; +import java.util.Set; + +/** + * A security policy that only permits access to Java members in the whitelist. + * Author: DQinYuan + */ +public class StrategyWhiteList implements QLSecurityStrategy { + + private final Set whiteList; + + public StrategyWhiteList(Set whiteList) { + this.whiteList = whiteList; + } + + @Override + public boolean check(Member member) { + return whiteList.contains(member); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/utils/BasicUtil.java b/src/main/java/com/alibaba/qlexpress4/utils/BasicUtil.java new file mode 100644 index 0000000..56bbf24 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/utils/BasicUtil.java @@ -0,0 +1,100 @@ +package com.alibaba.qlexpress4.utils; + +import com.alibaba.qlexpress4.runtime.Nothing; +import com.alibaba.qlexpress4.runtime.Parameters; + +import java.lang.reflect.*; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.HashMap; +import java.util.Map; + +import static java.lang.Character.toUpperCase; + +/** + * Author: TaoKan + */ +public class BasicUtil { + public static final String LENGTH = "length"; + + public static final String CLASS = "class"; + + private static final Map, Class> primitiveMap; + + static { + primitiveMap = new HashMap<>(8); + primitiveMap.put(Boolean.class, boolean.class); + primitiveMap.put(Character.class, char.class); + primitiveMap.put(Double.class, double.class); + primitiveMap.put(Float.class, float.class); + primitiveMap.put(Integer.class, int.class); + primitiveMap.put(Long.class, long.class); + primitiveMap.put(Byte.class, byte.class); + primitiveMap.put(Short.class, short.class); + } + + public static Class transToPrimitive(Class clazz) { + return primitiveMap.get(clazz); + } + + public static Integer numberPromoteLevel(Class numCls) { + if (numCls == byte.class || numCls == Byte.class) { + return 0; + } + if (numCls == short.class || numCls == Short.class) { + return 1; + } + if (numCls == int.class || numCls == Integer.class) { + return 2; + } + if (numCls == long.class || numCls == Long.class) { + return 3; + } + if (numCls == BigInteger.class) { + return 4; + } + if (numCls == float.class || numCls == Float.class) { + return 5; + } + if (numCls == double.class || numCls == Double.class) { + return 6; + } + if (numCls == BigDecimal.class) { + return 7; + } + return null; + } + + public static boolean isPublic(Member member) { + return Modifier.isPublic(member.getModifiers()); + } + + public static boolean isStatic(Member member) { + return Modifier.isStatic(member.getModifiers()); + } + + public static String getGetter(String s) { + return "get" + toUpperCase(s.charAt(0)) + s.substring(1); + } + + public static String getSetter(String s) { + return "set" + toUpperCase(s.charAt(0)) + s.substring(1); + } + + public static String getIsGetter(String s) { + return "is" + toUpperCase(s.charAt(0)) + s.substring(1); + } + + public static Class[] getTypeOfObject(Object[] objects) { + Class[] classes = new Class[objects.length]; + for (int i = 0; i < objects.length; i++) { + if (objects[i] == null) { + classes[i] = Nothing.class; + } + else { + classes[i] = objects[i].getClass(); + } + } + return classes; + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/utils/CacheUtil.java b/src/main/java/com/alibaba/qlexpress4/utils/CacheUtil.java new file mode 100644 index 0000000..8cc1d69 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/utils/CacheUtil.java @@ -0,0 +1,17 @@ +package com.alibaba.qlexpress4.utils; + +import com.alibaba.qlexpress4.member.MethodHandler; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Author: TaoKan + */ +public class CacheUtil { + private static final Map FUNCTION_INTERFACE_CACHE = new ConcurrentHashMap<>(); + + public static boolean isFunctionInterface(Class clazz) { + return FUNCTION_INTERFACE_CACHE.computeIfAbsent(clazz, + ignore -> clazz.isInterface() && MethodHandler.hasOnlyOneAbstractMethod(clazz.getMethods())); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/utils/PrintlnUtils.java b/src/main/java/com/alibaba/qlexpress4/utils/PrintlnUtils.java new file mode 100644 index 0000000..e6b8367 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/utils/PrintlnUtils.java @@ -0,0 +1,28 @@ +package com.alibaba.qlexpress4.utils; + +import java.util.function.Consumer; + +/** + * Author: DQinYuan + */ +public class PrintlnUtils { + + public static void printlnByCurDepth(int depth, String str, Consumer debug) { + debug.accept(buildIndentString(depth, str)); + } + + public static String buildIndentString(int indent, String originStr) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < indent; i++) { + if (i == indent - 1) { + builder.append("| "); + } + else { + builder.append(" "); + } + } + builder.append(originStr); + return builder.toString(); + } + +} diff --git a/src/main/java/com/alibaba/qlexpress4/utils/QLAliasUtils.java b/src/main/java/com/alibaba/qlexpress4/utils/QLAliasUtils.java new file mode 100644 index 0000000..7e772ce --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/utils/QLAliasUtils.java @@ -0,0 +1,18 @@ +package com.alibaba.qlexpress4.utils; + +import com.alibaba.qlexpress4.annotation.QLAlias; + +public class QLAliasUtils { + + public static boolean matchQLAlias(String matchName, QLAlias[] qlAliases) { + for (QLAlias alias : qlAliases) { + for (int i = 0; i < alias.value().length; i++) { + if (matchName.equals(alias.value()[i])) { + return true; + } + } + } + return false; + } + +} diff --git a/src/main/java/com/alibaba/qlexpress4/utils/QLFunctionUtil.java b/src/main/java/com/alibaba/qlexpress4/utils/QLFunctionUtil.java new file mode 100644 index 0000000..ed34545 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/utils/QLFunctionUtil.java @@ -0,0 +1,19 @@ +package com.alibaba.qlexpress4.utils; + +import com.alibaba.qlexpress4.annotation.QLFunction; + +import java.lang.reflect.Method; + +/** + * Author: TaoKan + */ +public class QLFunctionUtil { + + public static String[] getQLFunctionValue(Method method) { + return method.getAnnotation(QLFunction.class).value(); + } + + public static boolean containsQLFunctionForMethod(Method method) { + return method.isAnnotationPresent(QLFunction.class); + } +} diff --git a/src/main/java/com/alibaba/qlexpress4/utils/QLStringUtils.java b/src/main/java/com/alibaba/qlexpress4/utils/QLStringUtils.java new file mode 100644 index 0000000..71e4cf4 --- /dev/null +++ b/src/main/java/com/alibaba/qlexpress4/utils/QLStringUtils.java @@ -0,0 +1,64 @@ +package com.alibaba.qlexpress4.utils; + +public class QLStringUtils { + + public static String parseStringEscape(String originStr) { + return parseStringEscapeStartEnd(originStr, 1, originStr.length() - 1); + } + + public static String parseStringEscapeStartEnd(String originStr, int start, int end) { + StringBuilder result = new StringBuilder(); + final byte init = 0; + final byte escape = 1; + byte state = 0; + + int i = start; + while (i < end) { + char cur = originStr.charAt(i++); + switch (state) { + case init: + if (cur == '\\') { + state = escape; + } + else { + result.append(cur); + } + break; + case escape: + state = init; + switch (cur) { + case 'b': + result.append('\b'); + break; + case 't': + result.append('\t'); + break; + case 'n': + result.append('\n'); + break; + case 'f': + result.append('\f'); + break; + case 'r': + result.append('\r'); + break; + case '"': + result.append('"'); + break; + case '\'': + result.append('\''); + break; + case '\\': + result.append('\\'); + break; + case '$': + result.append('$'); + break; + } + break; + } + } + return result.toString(); + } + +} diff --git a/src/test/java/com/alibaba/qlexpress4/ClearDfaCacheTest.java b/src/test/java/com/alibaba/qlexpress4/ClearDfaCacheTest.java new file mode 100644 index 0000000..bc87b2a --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/ClearDfaCacheTest.java @@ -0,0 +1,69 @@ +package com.alibaba.qlexpress4; + +import com.alibaba.qlexpress4.runtime.context.ExpressContext; +import org.junit.Assert; +import org.junit.Test; + +import java.io.IOException; +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryMXBean; +import java.lang.management.MemoryUsage; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.DecimalFormat; +import java.util.Objects; + +public class ClearDfaCacheTest { + + private static final DecimalFormat MEM_FORMAT = new DecimalFormat("#,###.##"); + + @Test + public void clearDFACacheTest() + throws URISyntaxException, IOException { + String complexDataProcessingExpress = + new String(Files.readAllBytes(getPerfRoot().resolve("complexDataProcessing.ql"))); + Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + + double beforeMemoryUsed = getMemoryUsedMB(); + runner.parseToSyntaxTree(complexDataProcessingExpress); + runner.clearDFACache(); + Assert.assertTrue(getMemoryUsedMB() <= beforeMemoryUsed + 1); + } + + @Test + public void bestPractice() + throws URISyntaxException, IOException { + String exampleExpress = "1+1"; + // tag::clearDFACacheBestPractice[] + /* + * 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(exampleExpress); + 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(exampleExpress, ExpressContext.EMPTY_CONTEXT, QLOptions.builder().cache(true).build()); + } + // end::clearDFACacheBestPractice[] + } + + private double getMemoryUsedMB() { + System.gc(); + MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean(); + MemoryUsage heap = memoryMXBean.getHeapMemoryUsage(); + long memory = heap.getUsed(); + return memory / (1024.0 * 1024.0); + } + + private Path getPerfRoot() + throws URISyntaxException { + return Paths.get(Objects.requireNonNull(getClass().getClassLoader().getResource("perf")).toURI()); + } +} diff --git a/src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java b/src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java new file mode 100644 index 0000000..88dbd4e --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java @@ -0,0 +1,1609 @@ +package com.alibaba.qlexpress4; + +import com.alibaba.qlexpress4.annotation.QLFunction; +import com.alibaba.qlexpress4.aparser.ImportManager; +import com.alibaba.qlexpress4.aparser.InterpolationMode; +import com.alibaba.qlexpress4.api.BatchAddFunctionResult; +import com.alibaba.qlexpress4.exception.QLErrorCodes; +import com.alibaba.qlexpress4.exception.QLException; +import com.alibaba.qlexpress4.exception.QLRuntimeException; +import com.alibaba.qlexpress4.exception.QLSyntaxException; +import com.alibaba.qlexpress4.exception.QLTimeoutException; +import com.alibaba.qlexpress4.inport.MyDesk; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.context.DynamicVariableContext; +import com.alibaba.qlexpress4.runtime.context.ExpressContext; +import com.alibaba.qlexpress4.runtime.data.DataValue; +import com.alibaba.qlexpress4.runtime.function.ExtensionFunction; +import com.alibaba.qlexpress4.runtime.trace.ExpressionTrace; +import com.alibaba.qlexpress4.runtime.trace.TracePointTree; +import com.alibaba.qlexpress4.security.QLSecurityStrategy; +import com.alibaba.qlexpress4.test.function.HelloFunction; +import com.alibaba.qlexpress4.test.qlalias.Order; +import com.alibaba.qlexpress4.test.qlalias.Patient; +import com.alibaba.qlexpress4.test.qlalias.Person; +import com.alibaba.qlexpress4.test.qlalias.User; +import org.junit.Assert; +import org.junit.Test; + +import java.lang.reflect.Member; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import static org.junit.Assert.*; + +/** + * Author: DQinYuan + */ +public class Express4RunnerTest { + + @Test + public void parseToCacheTest() { + // tag::parseToCache[] + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + express4Runner.parseToDefinitionWithCache("a+b"); + // end::parseToCache[] + } + + @Test + public void addFunctionsDefinedInScriptTest() + throws InterruptedException { + // tag::addFunctionsDefinedInScript[] + Express4Runner express4Runner = + new Express4Runner(InitOptions.builder().securityStrategy(QLSecurityStrategy.open()).build()); + BatchAddFunctionResult addResult = express4Runner.addFunctionsDefinedInScript( + "function myAdd(a,b) {\n" + " return a+b;" + "}\n" + "\n" + "function getCurrentTime() {\n" + + " return System.currentTimeMillis();\n" + "}" + "\n" + "defineTime=System.currentTimeMillis();\n" + + "function defineTime() {\n" + " return defineTime;" + "}\n", + ExpressContext.EMPTY_CONTEXT, + QLOptions.DEFAULT_OPTIONS); + assertEquals(3, addResult.getSucc().size()); + QLResult result = express4Runner.execute("myAdd(1,2)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS); + assertEquals(3, result.getResult()); + + QLResult resultCurTime1 = + express4Runner.execute("getCurrentTime()", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS); + Thread.sleep(1000); + QLResult resultCurTime2 = + express4Runner.execute("getCurrentTime()", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS); + assertNotSame(resultCurTime1.getResult(), resultCurTime2.getResult()); + + /* + * The defineTime variable is defined outside the function and is initialized when the function is defined; + * it is not recalculated afterward, so the value returned is always the time at which the function was defined. + */ + QLResult resultDefineTime1 = + express4Runner.execute("defineTime()", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS); + Thread.sleep(1000); + QLResult resultDefineTime2 = + express4Runner.execute("defineTime()", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS); + assertSame(resultDefineTime1.getResult(), resultDefineTime2.getResult()); + // end::addFunctionsDefinedInScript[] + } + + @Test + public void expressionTraceTest() { + // tag::expressionTrace[] + Express4Runner express4Runner = new Express4Runner(InitOptions.builder().traceExpression(true).build()); + express4Runner.addFunction("myTest", (Predicate)i -> i > 10); + + Map context = new HashMap<>(); + context.put("a", true); + QLResult result = express4Runner + .execute("a && (!myTest(11) || false)", context, QLOptions.builder().traceExpression(true).build()); + Assert.assertFalse((Boolean)result.getResult()); + + List expressionTraces = result.getExpressionTraces(); + Assert.assertEquals(1, expressionTraces.size()); + ExpressionTrace expressionTrace = expressionTraces.get(0); + Assert.assertEquals("OPERATOR && false\n" + " | VARIABLE a true\n" + " | OPERATOR || false\n" + + " | OPERATOR ! false\n" + " | FUNCTION myTest true\n" + " | VALUE 11 11\n" + + " | VALUE false false\n", expressionTrace.toPrettyString(0)); + + // short circuit + context.put("a", false); + QLResult resultShortCircuit = express4Runner.execute("(a && true) && (!myTest(11) || false)", + context, + QLOptions.builder().traceExpression(true).build()); + Assert.assertFalse((Boolean)resultShortCircuit.getResult()); + ExpressionTrace expressionTraceShortCircuit = resultShortCircuit.getExpressionTraces().get(0); + Assert.assertEquals( + "OPERATOR && false\n" + " | OPERATOR && false\n" + " | VARIABLE a false\n" + " | VALUE true \n" + + " | OPERATOR || \n" + " | OPERATOR ! \n" + " | FUNCTION myTest \n" + + " | VALUE 11 \n" + " | VALUE false \n", + expressionTraceShortCircuit.toPrettyString(0)); + Assert.assertTrue(expressionTraceShortCircuit.getChildren().get(0).isEvaluated()); + Assert.assertFalse(expressionTraceShortCircuit.getChildren().get(1).isEvaluated()); + + // in + QLResult resultIn = express4Runner + .execute("'ab' in ['cc', 'dd', 'ff']", context, QLOptions.builder().traceExpression(true).build()); + Assert.assertFalse((Boolean)resultIn.getResult()); + ExpressionTrace expressionTraceIn = resultIn.getExpressionTraces().get(0); + Assert + .assertEquals( + "OPERATOR in false\n" + " | VALUE 'ab' ab\n" + " | LIST [ [cc, dd, ff]\n" + " | VALUE 'cc' cc\n" + + " | VALUE 'dd' dd\n" + " | VALUE 'ff' ff\n", + expressionTraceIn.toPrettyString(0)); + // end::expressionTrace[] + + QLResult resultTernary = express4Runner + .execute("true? 2: 1;false? 2: 1", context, QLOptions.builder().traceExpression(true).build()); + Assert.assertEquals("OPERATOR ? 2\n" + " | VALUE true true\n" + " | VALUE 2 2\n" + " | VALUE 1 \n", + resultTernary.getExpressionTraces().get(0).toPrettyString(0)); + Assert.assertEquals("OPERATOR ? 1\n" + " | VALUE false false\n" + " | VALUE 2 \n" + " | VALUE 1 1\n", + resultTernary.getExpressionTraces().get(1).toPrettyString(0)); + + QLResult resultIf = express4Runner + .execute("if(true) {11} else {13}", context, QLOptions.builder().traceExpression(true).build()); + Assert.assertEquals("IF if 11\n" + " | VALUE true true\n" + " | BLOCK { 11\n" + " | VALUE 11 11\n" + + " | BLOCK { \n" + " | VALUE 13 \n", resultIf.getExpressionTraces().get(0).toPrettyString(0)); + + QLResult resultAssign = + express4Runner.execute("aab = 11", context, QLOptions.builder().traceExpression(true).build()); + Assert.assertEquals("OPERATOR = 11\n" + " | VARIABLE aab null\n" + " | VALUE 11 11\n", + resultAssign.getExpressionTraces().get(0).toPrettyString(0)); + + QLResult resultAssignChange = express4Runner.execute("aab = 111", + Collections.singletonMap("aab", 100), + QLOptions.builder().traceExpression(true).build()); + Assert.assertEquals("OPERATOR = 111\n" + " | VARIABLE aab 100\n" + " | VALUE 111 111\n", + resultAssignChange.getExpressionTraces().get(0).toPrettyString(0)); + + QLResult resultAssignFunctionCall = express4Runner.execute("m = {bbb:6};aaa = () -> m;aaa().bbb=10;m.bbb", + new HashMap<>(), + QLOptions.builder().traceExpression(true).build()); + Assert.assertEquals(10, resultAssignFunctionCall.getResult()); + Assert.assertEquals("OPERATOR = {bbb=10}\n" + " | VARIABLE m null\n" + " | MAP { {bbb=10}\n", + resultAssignFunctionCall.getExpressionTraces().get(0).toPrettyString(0)); + Assert.assertEquals("OPERATOR = 10\n" + " | FIELD bbb 6\n" + " | FUNCTION aaa \n" + " | VALUE 10 10\n", + resultAssignFunctionCall.getExpressionTraces().get(2).toPrettyString(0)); + + QLResult resultBlock = express4Runner + .execute("a = {m=10;m+11}", new HashMap<>(), QLOptions.builder().traceExpression(true).build()); + Assert.assertEquals( + "OPERATOR = 21\n" + " | VARIABLE a null\n" + " | BLOCK { 21\n" + " | OPERATOR = 10\n" + + " | VARIABLE m null\n" + " | VALUE 10 10\n" + " | OPERATOR + 21\n" + + " | VARIABLE m 10\n" + " | VALUE 11 11\n", + resultBlock.getExpressionTraces().get(0).toPrettyString(0)); + + QLResult resultNestedIf = express4Runner.execute("if(false) {11} else if (1>10) {15} else {}", + new HashMap<>(), + QLOptions.builder().traceExpression(true).build()); + Assert.assertEquals( + "IF if null\n" + " | VALUE false false\n" + " | BLOCK { \n" + " | VALUE 11 \n" + " | IF if null\n" + + " | OPERATOR > false\n" + " | VALUE 1 1\n" + " | VALUE 10 10\n" + + " | BLOCK { \n" + " | VALUE 15 \n" + " | BLOCK { null\n", + resultNestedIf.getExpressionTraces().get(0).toPrettyString(0)); + + QLResult resultLocalVeriableStmt = + express4Runner.execute("int a = 1;", new HashMap<>(), QLOptions.builder().traceExpression(true).build()); + Assert.assertEquals("STATEMENT int null\n", + resultLocalVeriableStmt.getExpressionTraces().get(0).toPrettyString(0)); + + QLResult resultThrow = express4Runner + .execute("if (true) 10 else throw 1", new HashMap<>(), QLOptions.builder().traceExpression(true).build()); + Assert.assertEquals("IF if 10\n" + " | VALUE true true\n" + " | VALUE 10 10\n" + " | STATEMENT throw \n", + resultThrow.getExpressionTraces().get(0).toPrettyString(0)); + + QLResult resultIfWithFunctionCall = express4Runner + .execute("if (myTest(11)) 10 else 1", new HashMap<>(), QLOptions.builder().traceExpression(true).build()); + Assert.assertEquals("IF if 10\n" + " | FUNCTION myTest true\n" + " | VALUE 11 11\n" + " | VALUE 10 10\n" + + " | VALUE 1 \n", resultIfWithFunctionCall.getExpressionTraces().get(0).toPrettyString(0)); + + QLResult resultWhile = express4Runner + .execute("while(false) {m=10}", new HashMap<>(), QLOptions.builder().traceExpression(true).build()); + Assert.assertEquals("STATEMENT while null\n", resultWhile.getExpressionTraces().get(0).toPrettyString(0)); + + // traditional for statement test + QLResult resultTraditionalFor = express4Runner + .execute("for(int i=0; i<3; i++) {i}", new HashMap<>(), QLOptions.builder().traceExpression(true).build()); + Assert.assertEquals("STATEMENT for null\n", + resultTraditionalFor.getExpressionTraces().get(0).toPrettyString(0)); + + // for each statement test + QLResult resultForEach = express4Runner.execute("for(int item : [1,2,3]) {item}", + new HashMap<>(), + QLOptions.builder().traceExpression(true).build()); + Assert.assertEquals("STATEMENT for null\n", resultForEach.getExpressionTraces().get(0).toPrettyString(0)); + + // function statement test + QLResult resultFunction = express4Runner.execute("function testFunc() {return 10}", + new HashMap<>(), + QLOptions.builder().traceExpression(true).build()); + Assert.assertEquals("DEFINE_FUNCTION testFunc null\n", + resultFunction.getExpressionTraces().get(0).toPrettyString(0)); + + // macro statement test + QLResult resultMacro = express4Runner + .execute("macro testMacro {return 20}", new HashMap<>(), QLOptions.builder().traceExpression(true).build()); + Assert.assertEquals("DEFINE_MACRO testMacro null\n", + resultMacro.getExpressionTraces().get(0).toPrettyString(0)); + + // break statement test + QLResult resultBreak = + express4Runner.execute("break", new HashMap<>(), QLOptions.builder().traceExpression(true).build()); + Assert.assertEquals("STATEMENT break null\n", resultBreak.getExpressionTraces().get(0).toPrettyString(0)); + + // continue statement test + QLResult resultContinue = + express4Runner.execute("continue", new HashMap<>(), QLOptions.builder().traceExpression(true).build()); + Assert.assertEquals("STATEMENT continue null\n", resultContinue.getExpressionTraces().get(0).toPrettyString(0)); + + QLResult resultReturn = + express4Runner.execute("return 1+1", new HashMap<>(), QLOptions.builder().traceExpression(true).build()); + Assert.assertEquals("RETURN return 2\n" + " | OPERATOR + 2\n" + " | VALUE 1 1\n" + " | VALUE 1 1\n", + resultReturn.getExpressionTraces().get(0).toPrettyString(0)); + + QLResult resultEmptyStmt = + express4Runner.execute(";;;;", new HashMap<>(), QLOptions.builder().traceExpression(true).build()); + Assert.assertEquals(1, resultEmptyStmt.getExpressionTraces().size()); + Assert.assertEquals("STATEMENT ; \n", resultEmptyStmt.getExpressionTraces().get(0).toPrettyString(0)); + + QLResult resultEmptyStmtFilter = + express4Runner.execute("a=1;;;;", new HashMap<>(), QLOptions.builder().traceExpression(true).build()); + Assert.assertEquals(1, resultEmptyStmtFilter.getExpressionTraces().size()); + } + + @Test + public void getExpressionTracePointsTest() { + // tag::getExpressionTracePoints[] + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + TracePointTree tracePointTree = express4Runner.getExpressionTracePoints("1+3+5*ab+9").get(0); + Assert.assertEquals("OPERATOR +\n" + " | OPERATOR +\n" + " | OPERATOR +\n" + " | VALUE 1\n" + + " | VALUE 3\n" + " | OPERATOR *\n" + " | VALUE 5\n" + " | VARIABLE ab\n" + + " | VALUE 9\n", tracePointTree.toPrettyString(0)); + // end::getExpressionTracePoints[] + + TracePointTree tracePointTreeFunction = + express4Runner.getExpressionTracePoints("ab && (myTest(1,2) || false)").get(0); + Assert.assertEquals( + "OPERATOR &&\n" + " | VARIABLE ab\n" + " | OPERATOR ||\n" + " | FUNCTION myTest\n" + + " | VALUE 1\n" + " | VALUE 2\n" + " | VALUE false\n", + tracePointTreeFunction.toPrettyString(0)); + + TracePointTree tracePointIn = express4Runner.getExpressionTracePoints("'ab' in ['cc', 'dd', 'ff']").get(0); + Assert.assertEquals("OPERATOR in\n" + " | VALUE 'ab'\n" + " | LIST [\n" + " | VALUE 'cc'\n" + + " | VALUE 'dd'\n" + " | VALUE 'ff'\n", tracePointIn.toPrettyString(0)); + } + + @Test + public void checkSyntaxTest() { + // tag::checkSyntax[] + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + try { + express4Runner.check("a+b;\n(a+b"); + fail(); + } + catch (QLSyntaxException e) { + assertEquals(2, e.getLineNo()); + assertEquals(4, e.getColNo()); + assertEquals("SYNTAX_ERROR", e.getErrorCode()); + // represents the end of script + assertEquals( + "[Error SYNTAX_ERROR: mismatched input '' expecting ')']\n" + "[Near: a+b; (a+b]\n" + + " ^^^^^\n" + "[Line: 2, Column: 4]", + e.getMessage()); + } + // end::checkSyntax[] + + try { + express4Runner.check("sellerId in [1001] || (sellerId not in [1001])"); + fail(); + } + catch (QLSyntaxException e) { + assertEquals( + "[Error SYNTAX_ERROR: mismatched input 'not' expecting ')']\n" + + "[Near: ...[1001] || (sellerId not in [1001])]\n" + " ^^^\n" + + "[Line: 1, Column: 32]", + e.getMessage()); + } + } + + @Test + public void addAliasTest() { + // tag::addAlias[] + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + // add custom function zero + express4Runner.addFunction("zero", (String ignore) -> 0); + + // keyword alias + assertTrue(express4Runner.addAlias("如果", "if")); + assertTrue(express4Runner.addAlias("则", "then")); + assertTrue(express4Runner.addAlias("否则", "else")); + assertTrue(express4Runner.addAlias("返回", "return")); + // operator alias + assertTrue(express4Runner.addAlias("大于", ">")); + // function alias + assertTrue(express4Runner.addAlias("零", "zero")); + + Map context = new HashMap<>(); + context.put("语文", 90); + context.put("数学", 90); + context.put("英语", 90); + + Object result = express4Runner + .execute("如果 (语文 + 数学 + 英语 大于 270) 则 返回 1 否则 返回 零()", context, QLOptions.DEFAULT_OPTIONS) + .getResult(); + assertEquals(0, result); + // end::addAlias[] + } + + @Test + public void inTest() { + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + express4Runner.addAlias("属于", "in"); + assertTrue( + (Boolean)express4Runner.execute("1 属于 [1,2]", new HashMap<>(), QLOptions.DEFAULT_OPTIONS).getResult()); + assertFalse( + (Boolean)express4Runner.execute("1 属于 [3,2]", new HashMap<>(), QLOptions.DEFAULT_OPTIONS).getResult()); + } + + @Test + public void cacheDocTest() { + // tag::cacheSwitch[] + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + // open cache switch + express4Runner.execute("1+2", new HashMap<>(), QLOptions.builder().cache(true).build()); + // end::cacheSwitch[] + } + + @Test + public void docQuickStartTest() { + // tag::firstQl[] + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + Map context = new HashMap<>(); + context.put("a", 1); + context.put("b", 2); + context.put("c", 3); + Object result = express4Runner.execute("a + b * c", context, QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals(7, result); + // end::firstQl[] + } + + @Test + public void docAddFunctionAndOperatorTest() { + // tag::addFunctionAndOperator[] + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + // custom function + express4Runner.addVarArgsFunction("join", + params -> Arrays.stream(params).map(Object::toString).collect(Collectors.joining(","))); + Object resultFunction = + express4Runner.execute("join(1,2,3)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals("1,2,3", resultFunction); + + // custom operator + express4Runner.addOperatorBiFunction("join", (left, right) -> left + "," + right); + Object resultOperator = + express4Runner.execute("1 join 2 join 3", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals("1,2,3", resultOperator); + // end::addFunctionAndOperator[] + } + + @Test + public void docImportJavaTest() { + // tag::importJavaCls[] + Express4Runner express4Runner = new Express4Runner(InitOptions.builder() + // open security strategy, which allows access to all Java classes within the application. + .securityStrategy(QLSecurityStrategy.open()) + .build()); + // Import Java classes using the import statement. + Map params = new HashMap<>(); + params.put("a", 1); + params.put("b", 2); + Object result = + express4Runner + .execute("import com.alibaba.qlexpress4.QLImportTester;" + "QLImportTester.add(a,b)", + params, + QLOptions.DEFAULT_OPTIONS) + .getResult(); + Assert.assertEquals(3, result); + // end::importJavaCls[] + } + + @Test + public void docDefaultImportJavaTest() { + // tag::defaultImport[] + Express4Runner express4Runner = new Express4Runner(InitOptions.builder() + .addDefaultImport( + Collections.singletonList(ImportManager.importCls("com.alibaba.qlexpress4.QLImportTester"))) + .securityStrategy(QLSecurityStrategy.open()) + .build()); + Object result = + express4Runner.execute("QLImportTester.add(1,2)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS) + .getResult(); + Assert.assertEquals(3, result); + // end::defaultImport[] + } + + @Test + public void notStrictNewLinesTest() { + // tag::notStrictNewLinesTest[] + Express4Runner express4Runner = new Express4Runner(InitOptions.builder().strictNewLines(false).build()); + String script = "商家应收=\n 价格\n - 饭卡商家承担\n + 平台补贴"; + Map context = new HashMap<>(); + context.put("价格", 10); + context.put("饭卡商家承担", 3); + context.put("平台补贴", 5); + QLResult result = express4Runner.execute(script, context, QLOptions.DEFAULT_OPTIONS); + Assert.assertEquals(12, ((Number)result.getResult()).intValue()); + // end::notStrictNewLinesTest[] + } + + @Test + public void invokeDefaultMethodTest() { + Express4Runner express4Runner = new Express4Runner(InitOptions.builder() + .addDefaultImport(Collections.singletonList(ImportManager.importPack("com.alibaba.qlexpress4.inport"))) + .securityStrategy(QLSecurityStrategy.open()) + .build()); + Object result = express4Runner + .execute("a = new InterWithDefaultImplChild();a.haha()", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS) + .getResult(); + Assert.assertEquals("haha", result); + + Object result1 = + express4Runner + .execute("a = new InterWithDefaultImplGrandPaChild();a.haha()", + Collections.emptyMap(), + QLOptions.DEFAULT_OPTIONS) + .getResult(); + Assert.assertEquals("grandPa", result1); + + Map map = new HashMap<>(); + map.put("a", "123"); + map.put("b", "456"); + map.put("c", "789"); + + Map context = new HashMap<>(); + context.put("map", map); + map.entrySet().parallelStream().map(en -> en.getKey() + ":" + en.getValue()).collect(Collectors.toList()); + Object result2 = express4Runner.execute( + "map.entrySet()" + + ".parallelStream().map(en -> en.getKey() + \":\" + en.getValue()).collect(Collectors.toList())", + context, + QLOptions.DEFAULT_OPTIONS).getResult(); + Assert.assertEquals(Arrays.asList("a:123", "b:456", "c:789"), result2); + } + + @Test + public void concurrentCacheTest() + throws InterruptedException { + int threadCount = 5; + ExecutorService pool = Executors.newFixedThreadPool(threadCount); + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + String expression = "a+b*c"; + + for (int i = 0; i < threadCount; i++) { + pool.submit(() -> { + Map context = new HashMap<>(); + context.put("a", 1); + context.put("b", 2); + context.put("c", 3); + + express4Runner.execute(expression, context, QLOptions.builder().cache(true).build()).getResult(); + }); + } + + pool.shutdown(); + long start = System.currentTimeMillis(); + pool.awaitTermination(10, TimeUnit.SECONDS); + + long cost = System.currentTimeMillis() - start; + Assert.assertTrue(cost < 5000); + } + + @Test + public void dollarVariableTest() { + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + Object result = + express4Runner.execute("$a = 10; $a", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); + Assert.assertEquals(10, result); + } + + @Test + public void docTryCatchTest() { + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + Object result = express4Runner + .execute("1 + try {\n" + " 100 + 1/0\n" + "} catch(e) {\n" + " // Throw a zero-division exception\n" + + " 11\n" + "}", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS) + .getResult(); + Assert.assertEquals(12, result); + } + + @Test + public void docPreciseTest() { + // tag::bigDecimalForPrecise[] + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + Object result = express4Runner.execute("0.1", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertTrue(result instanceof BigDecimal); + // end::bigDecimalForPrecise[] + + // tag::preciseComparisonWithJava[] + assertNotEquals(0.3, 0.1 + 0.2, 0.0); + assertTrue((Boolean)express4Runner.execute("0.3==0.1+0.2", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS) + .getResult()); + // end::preciseComparisonWithJava[] + + // tag::preciseSwitch[] + Map context = new HashMap<>(); + context.put("a", 0.1); + context.put("b", 0.2); + assertFalse((Boolean)express4Runner.execute("0.3==a+b", context, QLOptions.DEFAULT_OPTIONS).getResult()); + // open precise switch + assertTrue((Boolean)express4Runner.execute("0.3==a+b", context, QLOptions.builder().precise(true).build()) + .getResult()); + // end::preciseSwitch[] + } + + @Test + public void securityStrategyTest() + throws NoSuchMethodException, SecurityException, NoSuchFieldException { + // tag::securityStrategyContextSetup[] + MyDesk desk = new MyDesk(); + desk.setBook1("Thinking in Java"); + desk.setBook2("Effective Java"); + Map context = Collections.singletonMap("desk", desk); + // end::securityStrategyContextSetup[] + + // tag::securityStrategyIsolation[] + // default isolation strategy, no field or method can be found + Express4Runner express4RunnerIsolation = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + assertErrorCode(express4RunnerIsolation, context, "desk.book1", "FIELD_NOT_FOUND"); + assertErrorCode(express4RunnerIsolation, context, "desk.getBook2()", "METHOD_NOT_FOUND"); + // end::securityStrategyIsolation[] + + // tag::securityStrategyBlackList[] + // black list security strategy + Set memberList = new HashSet<>(); + memberList.add(MyDesk.class.getMethod("getBook2")); + Express4Runner express4RunnerBlackList = new Express4Runner( + InitOptions.builder().securityStrategy(QLSecurityStrategy.blackList(memberList)).build()); + assertErrorCode(express4RunnerBlackList, context, "desk.book2", "FIELD_NOT_FOUND"); + Object resultBlack = + express4RunnerBlackList.execute("desk.book1", context, QLOptions.DEFAULT_OPTIONS).getResult(); + Assert.assertEquals("Thinking in Java", resultBlack); + // end::securityStrategyBlackList[] + + // tag::securityStrategyWhiteList[] + // white list security strategy + Express4Runner express4RunnerWhiteList = new Express4Runner( + InitOptions.builder().securityStrategy(QLSecurityStrategy.whiteList(memberList)).build()); + Object resultWhite = + express4RunnerWhiteList.execute("desk.getBook2()", context, QLOptions.DEFAULT_OPTIONS).getResult(); + Assert.assertEquals("Effective Java", resultWhite); + assertErrorCode(express4RunnerWhiteList, context, "desk.getBook1()", "METHOD_NOT_FOUND"); + // end::securityStrategyWhiteList[] + + // tag::securityStrategyOpen[] + // open security strategy + Express4Runner express4RunnerOpen = + new Express4Runner(InitOptions.builder().securityStrategy(QLSecurityStrategy.open()).build()); + Assert.assertEquals("Thinking in Java", + express4RunnerOpen.execute("desk.book1", context, QLOptions.DEFAULT_OPTIONS).getResult()); + Assert.assertEquals("Effective Java", + express4RunnerOpen.execute("desk.getBook2()", context, QLOptions.DEFAULT_OPTIONS).getResult()); + // end::securityStrategyOpen[] + } + + @Test + public void mapSetGetTest() { + String script = "a = new HashMap<>();" + "a['aaa'] = 'bbb';" + "a"; + Express4Runner express4Runner = + new Express4Runner(InitOptions.builder().securityStrategy(QLSecurityStrategy.open()).build()); + Object result = express4Runner.execute(script, Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertTrue(result instanceof HashMap); + assertEquals("bbb", ((HashMap)result).get("aaa")); + } + + @Test + public void shortCircuitTest() { + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + assertTrue( + (Boolean)express4Runner.execute("true && true && true", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS) + .getResult()); + assertFalse( + (Boolean)express4Runner.execute("true && false && (1/0)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS) + .getResult()); + assertTrue( + (Boolean)express4Runner + .execute("a = 1+1+1+1+1+1+1+1+1;" + "true && true && true", + Collections.emptyMap(), + QLOptions.DEFAULT_OPTIONS) + .getResult()); + + assertFalse((Boolean)express4Runner + .execute("false || false || false", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS) + .getResult()); + assertTrue( + (Boolean)express4Runner.execute("false || true || (1/0)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS) + .getResult()); + assertTrue((Boolean)express4Runner + .execute("(false && (1/0)) || true || (1/0)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS) + .getResult()); + + assertErrorCode(express4Runner, "true && (1/0)", "INVALID_ARITHMETIC"); + + // disable short circuit test + QLOptions disableShortCircuitOp = QLOptions.builder().shortCircuitDisable(true).build(); + assertTrue( + (Boolean)express4Runner.execute("false || false || true", Collections.emptyMap(), disableShortCircuitOp) + .getResult()); + assertFalse( + (Boolean)express4Runner.execute("(true && false) || false", Collections.emptyMap(), disableShortCircuitOp) + .getResult()); + } + + @Test + public void disableShortCircuitTest() { + // tag::disableShortCircuit[] + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + // execute when enable short circuit (default) + // `1/0` is short-circuited by the preceding `false`, so it won't throw an error. + assertFalse((Boolean)express4Runner.execute("false && (1/0)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS) + .getResult()); + try { + // execute when disable short circuit + express4Runner.execute("false && (1/0)", + Collections.emptyMap(), + QLOptions.builder().shortCircuitDisable(true).build()); + fail(); + } + catch (QLException e) { + Assert.assertEquals("INVALID_ARITHMETIC", e.getErrorCode()); + Assert.assertEquals("Division by zero", e.getReason()); + } + // end::disableShortCircuit[] + } + + @Test + public void assignTest() { + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + assertErrorCode(express4Runner, "1 = 0", "SYNTAX_ERROR"); + } + + @Test + public void ifTest() { + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + QLOptions debugOptions = QLOptions.builder().build(); + Object result = + express4Runner.execute("if (2==3) {if (2==2) 10} else 4", Collections.emptyMap(), debugOptions).getResult(); + assertEquals(4, result); + } + + @Test + public void debugExample() { + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + QLOptions debugOptions = QLOptions.builder().build(); + Object result = express4Runner.execute("1+1", Collections.emptyMap(), debugOptions).getResult(); + assertEquals(2, result); + + Object result1 = + express4Runner.execute("false || true || (1/0)", Collections.emptyMap(), debugOptions).getResult(); + assertTrue((Boolean)result1); + } + + @Test + public void populateTest() { + // tag::polluteUserContext[] + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + QLOptions populateOption = QLOptions.builder().polluteUserContext(true).build(); + Map populatedMap = new HashMap<>(); + populatedMap.put("b", 10); + express4Runner.execute("a = 11;b = a", populatedMap, populateOption); + assertEquals(11, populatedMap.get("a")); + assertEquals(11, populatedMap.get("b")); + + // no population + Map noPopulatedMap1 = new HashMap<>(); + express4Runner.execute("a = 11", noPopulatedMap1, QLOptions.DEFAULT_OPTIONS); + assertFalse(noPopulatedMap1.containsKey("a")); + + Map noPopulatedMap2 = new HashMap<>(); + noPopulatedMap2.put("a", 10); + assertEquals(19, express4Runner.execute("a = 19;a", noPopulatedMap2, QLOptions.DEFAULT_OPTIONS).getResult()); + assertEquals(10, noPopulatedMap2.get("a")); + // end::polluteUserContext[] + } + + @SuppressWarnings("unchecked") + @Test + public void mapLiteralTest() { + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + Map result = (Map)express4Runner + .execute("{a:123,'b':'test'}", new HashMap<>(), QLOptions.DEFAULT_OPTIONS) + .getResult(); + assertEquals(123, result.get("a")); + assertEquals("test", result.get("b")); + } + + @Test + public void classFieldTest() { + Express4Runner express4Runner = + new Express4Runner(InitOptions.builder().securityStrategy(QLSecurityStrategy.open()).build()); + assertEquals(List.class, + express4Runner.execute("List.class", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult()); + assertEquals(List.class, + express4Runner.execute("java.util.List.class", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS) + .getResult()); + } + + @Test + public void invalidOperatorTest() { + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + assertErrorCode(express4Runner, "a abcd bb", "SYNTAX_ERROR"); + assertErrorCode(express4Runner, "import a.b v = 1", "SYNTAX_ERROR"); + assertErrorCode(express4Runner, "a.*bbb", "SYNTAX_ERROR"); + } + + @Test + public void importNotAtBeginningTest() { + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + try { + express4Runner + .execute("a = 10;\n" + "import a.b.c;", new HashMap<>(), QLOptions.builder().cache(false).build()); + fail(); + } + catch (QLSyntaxException e) { + assertEquals("SYNTAX_ERROR", e.getErrorCode()); + assertEquals("Import statement is not at the beginning of the file.", e.getReason()); + } + } + + @Test + public void extensionFunctionTest() { + // tag::extensionFunction[] + // tag::defineExtensionFunctionByExtensionFunction[] + ExtensionFunction helloFunction = new ExtensionFunction() { + @Override + public Class[] getParameterTypes() { + return new Class[0]; + } + + @Override + public String getName() { + return "hello"; + } + + @Override + public Class getDeclaringClass() { + return String.class; + } + + @Override + public Object invoke(Object obj, Object[] args) { + String originStr = (String)obj; + return "Hello," + originStr; + } + }; + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + express4Runner.addExtendFunction(helloFunction); + Object result = + express4Runner.execute("'jack'.hello()", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals("Hello,jack", result); + // end::defineExtensionFunctionByExtensionFunction[] + + // tag::defineExtensionFunctionByQLFunctionalVarargs[] + // simpler way to define extension function + express4Runner.addExtendFunction("add", + Number.class, + params -> ((Number)params[0]).intValue() + ((Number)params[1]).intValue()); + QLResult resultAdd = express4Runner.execute("1.add(2)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS); + assertEquals(3, resultAdd.getResult()); + // end::defineExtensionFunctionByQLFunctionalVarargs[] + + // end::extensionFunction[] + + express4Runner.addExtendFunction("add2", + Number.class, + params -> ((Number)params[0]).intValue() + ((Number)params[1]).intValue() + ((Number)params[2]).intValue()); + QLResult resultAdd2 = express4Runner.execute("1.add2(2,3)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS); + assertEquals(6, resultAdd2.getResult()); + } + + @Test + public void scripTimeoutTest() { + // tag::scripTimeout[] + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + try { + express4Runner.execute("while (true) {\n" + " 1+1\n" + "}", + Collections.emptyMap(), + QLOptions.builder().timeoutMillis(10L).build()); + fail("should timeout"); + } + catch (QLTimeoutException e) { + assertEquals(QLErrorCodes.SCRIPT_TIME_OUT.name(), e.getErrorCode()); + } + // end::scripTimeout[] + + try { + express4Runner.execute("while (2) {\n" + " 1+1\n" + "}", + Collections.emptyMap(), + QLOptions.builder().timeoutMillis(10L).build()); + fail("should exception"); + } + catch (QLTimeoutException e) { + fail(); + } + catch (QLRuntimeException e) { + assertEquals(QLErrorCodes.WHILE_CONDITION_BOOL_REQUIRED.name(), e.getErrorCode()); + } + } + + @Test + public void interpolationTest() { + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + Map context = new HashMap<>(); + context.put("a", 1); + QLResult result = express4Runner.execute("\"Hello,${a+1}\"", context, QLOptions.DEFAULT_OPTIONS); + Assert.assertEquals("Hello,2", result.getResult()); + + // tag::disableInterpolation[] + Express4Runner express4RunnerDisable = new Express4Runner( + // disable string interpolation + InitOptions.builder().interpolationMode(InterpolationMode.DISABLE).build()); + Assert.assertEquals("Hello,${ a + 1 }", + express4RunnerDisable.execute("\"Hello,${ a + 1 }\"", context, QLOptions.DEFAULT_OPTIONS).getResult()); + Assert.assertEquals("Hello,${lll", + express4RunnerDisable.execute("\"Hello,${lll\"", context, QLOptions.DEFAULT_OPTIONS).getResult()); + Assert.assertEquals("Hello,aaa $ lll\"\n\b", + express4RunnerDisable.execute("\"Hello,aaa $ lll\\\"\n\b\"", context, QLOptions.DEFAULT_OPTIONS) + .getResult()); + // end::disableInterpolation[] + } + + @Test + public void templateEngineTest() { + // tag::templateEngine[] + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + Map context = new HashMap<>(); + context.put("a", 1); + context.put("b", 2); + context.put("c", "test"); + QLResult simpleTemplate = express4Runner.executeTemplate("a ${a};b ${b+2}", context, QLOptions.DEFAULT_OPTIONS); + Assert.assertEquals("a 1;b 4", simpleTemplate.getResult()); + QLResult conditionTemplate = + express4Runner.executeTemplate("m xx ${\n" + " if (c like 't%') {\n" + " 'YYY'\n" + " }\n" + "}", + context, + QLOptions.DEFAULT_OPTIONS); + Assert.assertEquals("m xx YYY", conditionTemplate.getResult()); + QLResult multiLineTemplate = express4Runner.executeTemplate("m\n ${a}\n c", context, QLOptions.DEFAULT_OPTIONS); + Assert.assertEquals("m\n 1\n c", multiLineTemplate.getResult()); + QLResult escapeStringTemplate = + express4Runner.executeTemplate("m \n\"haha\" d\"", context, QLOptions.DEFAULT_OPTIONS); + Assert.assertEquals("m \n\"haha\" d\"", escapeStringTemplate.getResult()); + // end::templateEngine[] + } + + @Test + public void logicAndTest() { + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + Object result = + express4Runner.execute("null && true", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertFalse((Boolean)result); + } + + @Test + public void numberTest() { + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + Object[][] scriptAndExpects = + new Object[][] {{"12323", 12323}, {"2147483647", 2147483647}, {"9223372036854775807", 9223372036854775807L}, + {"18446744073709552000", new BigInteger("18446744073709552000")}, {"1.1", new BigDecimal("1.1")}, + {"1.25", 1.25d}, {"1.", 1.0}, {".1", new BigDecimal("0.1")}, {"0xfff", 4095}, {"0b11", 3}, {"072", 58}, + {"12e1", 120.0}, {"12.1E2", 1210.0}, {"10l", 10L}, {"10L", 10L}, {"10d", 10.0}, {"10.313D", 10.313d}, + {"10.2f", 10.2f}, {"10.2F", 10.2f}}; + + for (Object[] scriptAndExpect : scriptAndExpects) { + assertResultEquals(express4Runner, (String)scriptAndExpect[0], scriptAndExpect[1]); + } + } + + @Test + public void numberAmbiguousValueTest() { + Express4Runner express4Runner = + new Express4Runner(InitOptions.builder().securityStrategy(QLSecurityStrategy.open()).build()); + Object result = + express4Runner.execute("1.doubleValue()", new HashMap<>(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals(1d, result); + } + + @Test + public void errorReportColNumTest() { + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + try { + express4Runner.execute("1+1;\n2+2;\n1+cc()", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS); + } + catch (QLRuntimeException e) { + assertEquals(2, e.getColNo()); + assertEquals(3, e.getLineNo()); + } + + try { + express4Runner.execute("1/0", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS); + } + catch (QLRuntimeException e) { + assertEquals(1, e.getColNo()); + assertEquals(1, e.getLineNo()); + } + + try { + express4Runner.execute("a[]", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS); + } + catch (QLSyntaxException e) { + assertEquals(1, e.getLineNo()); + assertEquals(2, e.getColNo()); + } + } + + // tag::annoObj[] + public static class MyFunctionUtil { + @QLFunction({"myAdd", "iAdd"}) + public int add(int a, int b) { + return a + b; + } + + @QLFunction("arr3") + public static int[] array3(int a, int b, int c) { + return new int[] {a, b, c}; + } + + @QLFunction("concat") + public String concat(String a, String b) { + return a + b; + } + } + // end::annoObj[] + + @Test + public void addFunctionByAnnotationTest() { + // tag::addFunctionByAnnotationObject[] + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + BatchAddFunctionResult addResult = express4Runner.addObjFunction(new MyFunctionUtil()); + assertEquals(4, addResult.getSucc().size()); + Object result = + express4Runner.execute("myAdd(1,2) + iAdd(5,6)", new HashMap<>(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals(14, result); + express4Runner.addStaticFunction(MyFunctionUtil.class); + Object result1 = + express4Runner.execute("arr3(5,9,10)[2]", new HashMap<>(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals(10, result1); + + Object result2 = + express4Runner.execute("concat('aa', null)", new HashMap<>(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals("aanull", result2); + // end::addFunctionByAnnotationObject[] + } + + public static class OverloadService { + public String format(String value) { + return "S:" + value; + } + + public String format(Integer left, int right) { + return "I:" + left + "," + right; + } + } + + @Test + public void addFunctionOfServiceMethodBasicTest() { + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + MyFunctionUtil service = new MyFunctionUtil(); + + boolean added = + express4Runner.addFunctionOfServiceMethod("svcAdd", service, "add", new Class[] {int.class, int.class}); + assertTrue(added); + + Object result = express4Runner.execute("svcAdd(1,2)", new HashMap<>(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals(3, result); + } + + @Test + public void addFunctionOfServiceMethodOverloadTest() { + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + OverloadService service = new OverloadService(); + + boolean addedFormatString = + express4Runner.addFunctionOfServiceMethod("fmtStr", service, "format", new Class[] {String.class}); + assertTrue(addedFormatString); + Object resultStr = + express4Runner.execute("fmtStr('x')", new HashMap<>(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals("S:x", resultStr); + + boolean addedFormatInt = express4Runner + .addFunctionOfServiceMethod("fmtInt", service, "format", new Class[] {Integer.class, int.class}); + assertTrue(addedFormatInt); + Object resultInt = + express4Runner.execute("fmtInt(null,2)", new HashMap<>(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals("I:null,2", resultInt); + } + + @Test + public void variableStartsWithWellNumber() { + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + HashMap context = new HashMap<>(); + context.put("#cost", 10); + Object result = express4Runner.execute("#cost + 1", context, QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals(11, result); + } + + @Test + public void customExpressKeyValue() { + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + + Map attachments = new HashMap<>(); + + Map subAttachA = new HashMap<>(); + subAttachA.put("aa", 123); + + Map subAttachB = new HashMap<>(); + subAttachB.put("bb", 12); + + attachments.put("a", subAttachA); + attachments.put("b", subAttachB); + + QLOptions qlOptions = QLOptions.builder().attachments(attachments).build(); + Object result = express4Runner.execute("${/a/aa} + ${/b/bb}", new ExpressContext() { + @Override + public Value get(Map attachments, String variableName) { + String[] split = variableName.split("/"); + Map subMap = (Map)attachments.get(split[1]); + return new DataValue(subMap.get(split[2])); + } + }, qlOptions).getResult(); + assertEquals(135, result); + } + + @Test + public void getOutVarNamesTest() { + // tag::getOutVarNames[] + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + Set outVarNames = + express4Runner.getOutVarNames("int a = 1, b = 10;\n" + "c = 11\n" + "e = a + b + c + d\n" + "f+e"); + Set expectSet = new HashSet<>(); + expectSet.add("d"); + expectSet.add("f"); + assertEquals(expectSet, outVarNames); + // end::getOutVarNames[] + + Set outVarNames2 = express4Runner.getOutVarNames("if (true) {a = 10} else {a}"); + Set expectSet2 = new HashSet<>(); + expectSet2.add("a"); + assertEquals(expectSet2, outVarNames2); + + Set outVarNames3 = express4Runner.getOutVarNames("while (a>2) {a++;b=100} a+b"); + Set expectSet3 = new HashSet<>(); + expectSet3.add("a"); + assertEquals(expectSet3, outVarNames3); + + express4Runner.addFunction("dd", () -> { + }); + Set outVarNames4 = express4Runner.getOutVarNames("cc(a,bc(2,m,1))\ndd(c)"); + Set expectSet4 = new HashSet<>(); + expectSet4.add("a"); + expectSet4.add("m"); + expectSet4.add("c"); + Assert.assertEquals(expectSet4, outVarNames4); + + Set outVarNames5 = express4Runner.getOutVarNames("resultSet = ''; " + "if (a == 11)" + "true"); + Set expectSet5 = new HashSet<>(); + expectSet5.add("a"); + Assert.assertEquals(expectSet5, outVarNames5); + + Set outVarNamesWithSelector = express4Runner.getOutVarNames("${0} + ${1}"); + Set expectSetWithSelector = new HashSet<>(); + expectSetWithSelector.add("0"); + expectSetWithSelector.add("1"); + Assert.assertEquals(expectSetWithSelector, outVarNamesWithSelector); + + Set outVarNamesWithDyString = express4Runner.getOutVarNames("\"Hello ${a+b}\""); + Set expectSetWithDyString = new HashSet<>(); + expectSetWithDyString.add("a"); + expectSetWithDyString.add("b"); + Assert.assertEquals(expectSetWithDyString, outVarNamesWithDyString); + + Set outVarNamesWithClsName = express4Runner.getOutVarNames("Math.abs(1)"); + Assert.assertEquals(Collections.emptySet(), outVarNamesWithClsName); + + Set outVarNamesWithQualified = express4Runner.getOutVarNames("java.lang.Math.abs(1)"); + Assert.assertEquals(Collections.emptySet(), outVarNamesWithQualified); + + Set outVarNamesWithClsMethodCall = express4Runner.getOutVarNames("Arrays.asList(a,b)"); + Set expectOutVarNamesWithClsMethodCall = new HashSet<>(); + expectOutVarNamesWithClsMethodCall.add("a"); + expectOutVarNamesWithClsMethodCall.add("b"); + Assert.assertEquals(expectOutVarNamesWithClsMethodCall, outVarNamesWithClsMethodCall); + } + + @Test + public void getOutVarAttrs() { + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + Assert.assertEquals(Arrays.asList("a.b.c", "a.b.d", "c.m"), + flatOutVarAttrs(express4Runner.getOutVarAttrs("a.b.c+a.b.c-a.b.d*c.m"))); + Assert.assertEquals(Collections.singletonList("c.m"), + flatOutVarAttrs(express4Runner.getOutVarAttrs("a=2;test(a.b.c,c.m)"))); + Assert.assertEquals(Arrays.asList("a.b", "c.m"), + flatOutVarAttrs(express4Runner.getOutVarAttrs("a.b=2;test(c.m)"))); + Assert.assertEquals(Collections.singletonList("c"), + flatOutVarAttrs(express4Runner.getOutVarAttrs("java.lang.Math.abs(c)"))); + } + + private List flatOutVarAttrs(Set> outVarAttrs) { + return outVarAttrs.stream().map(l -> String.join(".", l)).sorted().collect(Collectors.toList()); + } + + @Test + public void getOutFunctions() { + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + Set outFuncNames = express4Runner.getOutFunctions("time('2025-09-8')+sum(1,sub(3,2))"); + Set expectOutFuncNames = new HashSet<>(); + expectOutFuncNames.add("time"); + expectOutFuncNames.add("sum"); + expectOutFuncNames.add("sub"); + Assert.assertEquals(expectOutFuncNames, outFuncNames); + + Set outFuncNamesExcludeDefined = + express4Runner.getOutFunctions("function add(a,b) {a+b}\n add(1,2)+sub(3,1)"); + Assert.assertEquals(Collections.singleton("sub"), outFuncNamesExcludeDefined); + + Set outFuncNamesMultiScope = + express4Runner.getOutFunctions("function add(a,b) {\n function sub(a,b) { a-b }\n add(1,2)+sub(3,1) \n}\n"); + Assert.assertEquals(Collections.emptySet(), outFuncNamesMultiScope); + + Set outFuncNamesMultiScope1 = express4Runner + .getOutFunctions("function add(a,b) {\n function sub(a,b) { a-b }\n add(1,2)+sub(3,1) \n}\nsub(3,1)"); + Assert.assertEquals(Collections.singleton("sub"), outFuncNamesMultiScope1); + + Set outFuncNamesRecur = + express4Runner.getOutFunctions("function recur(a,b) {\n recur(1,2) \n}\nrecur(3,1)"); + Assert.assertEquals(Collections.emptySet(), outFuncNamesRecur); + + Set outFuncNamesReorder = express4Runner.getOutFunctions("add(1,2); function add(a,b) {\n a+b \n}"); + Assert.assertEquals(Collections.emptySet(), outFuncNamesReorder); + } + + @Test + public void addOperatorTest() { + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + Object result = express4Runner.execute("'1.2'+'2.3'", new HashMap<>(), QLOptions.builder().cache(false).build()) + .getResult(); + assertEquals("1.22.3", result); + boolean replaceResult = express4Runner.replaceDefaultOperator("+", + (left, right) -> Double.parseDouble(left.get().toString()) + Double.parseDouble(right.get().toString())); + assertTrue(replaceResult); + Object result1 = + express4Runner.execute("'1.2'+'2.3'", new HashMap<>(), QLOptions.builder().cache(false).build()) + .getResult(); + assertEquals(3.5d, result1); + express4Runner.addOperator("join", (left, right) -> left.get().toString() + right.get().toString()); + Object result2 = + express4Runner.execute("1.2 join 2", new HashMap<>(), QLOptions.builder().cache(false).build()).getResult(); + assertEquals("1.22", result2); + + express4Runner.addOperator(".*", (left, right) -> { + String fieldName = (String)right.get(); + return ((List>)left.get()).stream().map(m -> m.get(fieldName)).collect(Collectors.toList()); + }, QLPrecedences.GROUP); + Object result3 = + express4Runner.execute("[{a:1}, {a:5}].*a", new HashMap<>(), QLOptions.builder().cache(false).build()) + .getResult(); + List expect = new ArrayList<>(); + expect.add(1); + expect.add(5); + assertEquals(expect, result3); + + Object result4 = express4Runner + .execute("[{a:1}, {a:5}, {a:10}, {a:20}].*a[1:-1]", new HashMap<>(), QLOptions.DEFAULT_OPTIONS) + .getResult(); + assertEquals(Arrays.asList(5, 10), result4); + + try { + express4Runner.execute("[{a:1}, {a:5}].*'abc'", new HashMap<>(), QLOptions.builder().cache(false).build()); + } + catch (QLRuntimeException e) { + assertEquals("INVALID_ARGUMENT", e.getErrorCode()); + assertEquals("custom e test", e.getReason()); + } + } + + @Test + public void methodInvokeCauseTest() { + Express4Runner express4Runner = + new Express4Runner(InitOptions.builder().securityStrategy(QLSecurityStrategy.open()).build()); + try { + express4Runner.execute("l = [];l.get(3)", new HashMap<>(), QLOptions.builder().cache(false).build()); + } + catch (QLRuntimeException e) { + assertTrue(e.getCause() instanceof IndexOutOfBoundsException); + } + } + + @Test + public void emptyListCacheTest() { + Express4Runner express4Runner = + new Express4Runner(InitOptions.builder().securityStrategy(QLSecurityStrategy.open()).build()); + String content = "arr = []; arr.add(1); return arr;"; + for (int i = 0; i < 10; i++) { + Object result = + express4Runner.execute(content, new HashMap<>(), QLOptions.builder().cache(true).build()).getResult(); + assertEquals(1, ((List)result).size()); + } + } + + @Test + public void emptyMapCacheTest() { + Express4Runner express4Runner = + new Express4Runner(InitOptions.builder().securityStrategy(QLSecurityStrategy.open()).build()); + for (int i = 0; i < 10; i++) { + String content = "m = {:}; m.put(k,'b'); return m;"; + Object result = express4Runner + .execute(content, Collections.singletonMap("k", "k" + i), QLOptions.builder().cache(true).build()) + .getResult(); + assertEquals(1, ((Map)result).size()); + } + } + + @Test + public void innerFunctionExceptionTest() { + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + express4Runner.addFunction("testExp", () -> { + throw new RuntimeException("inner test"); + }); + assertNotNull(express4Runner.getFunction("testExp")); + try { + express4Runner.execute("1+testExp()+10", new HashMap<>(), QLOptions.DEFAULT_OPTIONS); + } + catch (QLException e) { + assertEquals("inner test", e.getCause().getMessage()); + assertEquals( + "[Error INVOKE_FUNCTION_INNER_ERROR: exception from inner when invoking function 'testExp', error message: inner test]\n" + + "[Near: 1+testExp()+10]\n" + " ^^^^^^^\n" + "[Line: 1, Column: 2]", + e.getMessage()); + assertEquals(2, e.getPos()); + } + } + + @Test + public void avoidNullPointerTest() { + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + Object result = express4Runner + .execute("'a '+${a}+aa('xxx')", new HashMap<>(), QLOptions.builder().avoidNullPointer(true).build()) + .getResult(); + assertEquals("a nullnull", result); + } + + @Test + public void atFunctionTest() { + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + express4Runner.addFunction("@", (String s) -> s + "," + s); + Object result = express4Runner.execute("@('a')", new HashMap<>(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals("a,a", result); + } + + @Test + public void multilineStrNotCloseTest() { + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + try { + express4Runner.execute("a=1;'aaa \n \n cccc", new HashMap<>(), QLOptions.DEFAULT_OPTIONS); + fail("should throw"); + } + catch (QLException e) { + assertEquals("unterminated string literal", e.getReason()); + } + + try { + express4Runner.execute("\"aaa \n cccc", new HashMap<>(), QLOptions.DEFAULT_OPTIONS); + fail("should throw"); + } + catch (QLException e) { + assertEquals("unterminated string literal", e.getReason()); + } + } + + @Test + public void chineseCommaPropertyTest() { + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + Object result = + express4Runner.execute("{'销售方地址、电话':'test'}.销售方地址、电话", new HashMap<>(), QLOptions.DEFAULT_OPTIONS) + .getResult(); + assertEquals("test", result); + } + + @Test + public void stringEscapeTest() { + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + // "'\na + Object result = + express4Runner.execute("\"\\\"\"+'\\'\na'", new HashMap<>(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals("\"'\na", result); + } + + @Test + public void addMacroTest() { + // tag::addMacro[] + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + express4Runner.addMacro("rename", "name='haha-'+name"); + Map context = Collections.singletonMap("name", "wuli"); + Object result = express4Runner.execute("rename", context, QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals("haha-wuli", result); + + // replace macro + express4Runner.addOrReplaceMacro("rename", "name='huhu-'+name"); + Object result1 = express4Runner.execute("rename", context, QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals("huhu-wuli", result1); + // end::addMacro[] + } + + @Test + public void dynamicVariableComplexTest() { + // tag::dynamicVar[] + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + + Map staticContext = new HashMap<>(); + staticContext.put("语文", 88); + staticContext.put("数学", 99); + staticContext.put("英语", 95); + + QLOptions defaultOptions = QLOptions.DEFAULT_OPTIONS; + DynamicVariableContext dynamicContext = + new DynamicVariableContext(express4Runner, staticContext, defaultOptions); + dynamicContext.put("平均成绩", "(语文+数学+英语)/3.0"); + dynamicContext.put("是否优秀", "平均成绩>90"); + + // dynamic var + assertTrue((Boolean)express4Runner.execute("是否优秀", dynamicContext, defaultOptions).getResult()); + assertEquals(94, + ((Number)express4Runner.execute("平均成绩", dynamicContext, defaultOptions).getResult()).intValue()); + // static var + assertEquals(187, + ((Number)express4Runner.execute("语文+数学", dynamicContext, defaultOptions).getResult()).intValue()); + // end::dynamicVar[] + } + + public static class MyObj { + public int a; + + public String b; + } + + @Test + public void executeWithObjContextTest() { + // tag::executeWithObject[] + MyObj myObj = new MyObj(); + myObj.a = 1; + myObj.b = "test"; + + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + Object result = express4Runner.execute("a+b", myObj, QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals("1test", result); + // end::executeWithObject[] + } + + @Test + public void qlAliasTest() { + Express4Runner express4Runner = + new Express4Runner(InitOptions.builder().securityStrategy(QLSecurityStrategy.open()).build()); + String[] exps = new String[] {"患者.birth", "1987-02-23", "患者.生日()", "1987-02-23", "患者.患者姓名", "老王", "患者.姓名", "老王", + "患者.getBirth()==患者.出生年月()", "true", //方法注解 + "患者.生日()==患者.生日", "true", //get方法和字段名字一样是不冲突的 + "患者.患者姓名 + ' 今年 '+ 患者.获取年龄() +' 岁'", "老王 今年 34 岁", //任意方法的注解 + "患者.级别='低风险';return 患者.级别;", "低风险",}; + Person person = new Patient(); + person.setName("老王"); + person.setSex("男"); + person.setBirth("1987-02-23"); + for (int i = 0; i < exps.length; i += 2) { + Object result = + express4Runner.executeWithAliasObjects(exps[i], QLOptions.DEFAULT_OPTIONS, person).getResult(); + assertEquals(result.toString(), exps[i + 1]); + } + } + + @Test + public void qlAliasDocTest() { + // tag::qlAlias[] + Order order = new Order(); + order.setOrderNum("OR123455"); + order.setAmount(100); + + User user = new User(); + user.setName("jack"); + user.setVip(true); + + // Calculate the Final Order Amount + Express4Runner express4Runner = + new Express4Runner(InitOptions.builder().securityStrategy(QLSecurityStrategy.open()).build()); + Number result = (Number)express4Runner + .executeWithAliasObjects("用户.是vip? 订单.金额 * 0.8 : 订单.金额", QLOptions.DEFAULT_OPTIONS, order, user) + .getResult(); + assertEquals(80, result.intValue()); + // end::qlAlias[] + } + + @Test + public void customComplexFunctionDocTest() { + // tag::customComplexFunction[] + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + express4Runner.addFunction("hello", new HelloFunction()); + String resultJack = (String)express4Runner.execute("hello()", + Collections.emptyMap(), + // Additional information(tenant for example) can be brought into the custom function from outside via attachments + QLOptions.builder().attachments(Collections.singletonMap("tenant", "jack")).build()).getResult(); + assertEquals("hello,jack", resultJack); + String resultLucy = + (String)express4Runner + .execute("hello()", + Collections.emptyMap(), + QLOptions.builder().attachments(Collections.singletonMap("tenant", "lucy")).build()) + .getResult(); + assertEquals("hello,lucy", resultLucy); + // end::customComplexFunction[] + } + + @Test + public void customSelectorTest() { + // tag::customSelector[] + Express4Runner express4Runner = + new Express4Runner(InitOptions.builder().selectorStart("#[").selectorEnd("]").build()); + + Map context = new HashMap<>(); + context.put("0", "World"); + + QLResult result = express4Runner.execute("'Hello ' + #[0]", context, QLOptions.DEFAULT_OPTIONS); + assertEquals("Hello World", result.getResult()); + // end::customSelector[] + } + + @Test + public void customSelectorWhenNoCloseTest() { + Express4Runner express4Runner = + new Express4Runner(InitOptions.builder().selectorStart("#[").selectorEnd("]").build()); + + assertErrorCode(express4Runner, "'Hello ' + #[0grg", QLErrorCodes.SYNTAX_ERROR.name()); + assertErrorCode(express4Runner, "'Hello ' + ${pl}", QLErrorCodes.SYNTAX_ERROR.name()); + } + + @Test + public void listGetWhenPreciseTest() { + InitOptions initOptions = InitOptions.builder().securityStrategy(QLSecurityStrategy.open()).build(); + Express4Runner runner = new Express4Runner(initOptions); + QLOptions qlOptions = QLOptions.builder().precise(true).cache(true).build(); + Map context = new HashMap<>(); + ArrayList list = new ArrayList<>(); + list.add("a"); + list.add("b"); + context.put("list", list); + QLResult result = runner.execute("list.get(list.size()-1);", context, qlOptions); + assertEquals("b", result.getResult()); + } + + private void assertResultEquals(Express4Runner express4Runner, String script, Object expect) { + assertResultPredicate(express4Runner, script, result -> Objects.equals(expect, result)); + } + + private void assertResultPredicate(Express4Runner express4Runner, String script, Predicate predicate) { + Object result = express4Runner.execute(script, Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertTrue(script, predicate.test(result)); + } + + private void assertErrorCode(Express4Runner express4Runner, String script, String errCode) { + try { + express4Runner.execute(script, Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS); + } + catch (QLException e) { + assertEquals(errCode, e.getErrorCode()); + } + } + + private void assertErrorCode(Express4Runner express4Runner, Map existMap, String script, + String errCode) { + try { + express4Runner.execute(script, existMap, QLOptions.DEFAULT_OPTIONS); + fail("no errCode:" + errCode); + } + catch (QLException e) { + assertEquals(errCode, e.getErrorCode()); + } + } + + /** + * Tests for function call disable feature + */ + @Test + public void testDefaultAllowFunctionCall() + throws Exception { + // Create a runner + Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + + // Use default options (disableFunctionCalls = false) + String scriptWithFunctionCall = "Math.max(1, 2)"; + + // Should pass validation + runner.check(scriptWithFunctionCall, CheckOptions.DEFAULT_OPTIONS); + } + + @Test + public void testDisableFunctionCalls() { + // tag::disableFunctionCallsExample[] + // Create a runner + Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + + // Create options with function calls disabled + CheckOptions options = CheckOptions.builder().disableFunctionCalls(true).build(); + + // Script with function call + String scriptWithFunctionCall = "Math.max(1, 2)"; + + // Use custom options to check script + try { + runner.check(scriptWithFunctionCall, options); + } + catch (QLSyntaxException e) { + // Will throw exception as function calls are disabled + } + // end::disableFunctionCallsExample[] + + // Should throw QLSyntaxException + QLSyntaxException exception = Assert.assertThrows(QLSyntaxException.class, () -> { + runner.check(scriptWithFunctionCall, options); + }); + + // Verify exception message contains relevant information + Assert.assertTrue(exception.getMessage().contains("Function calls are not allowed")); + } + + @Test + public void testDisableDifferentFunctionCallStyles() { + // Create a runner + Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + + // Create options with function calls disabled + CheckOptions options = CheckOptions.builder().disableFunctionCalls(true).build(); + + // Test basic function call styles first + String[] basicFunctionCallScripts = {"func()", // Basic function call + "obj.method()" // Method call + }; + + // All should throw exception (don't check exact message for now) + for (String script : basicFunctionCallScripts) { + Assert.assertThrows(Exception.class, () -> { + runner.check(script, options); + }); + } + } + + @Test + public void testDisableFunctionCallsAllowOtherSyntax() + throws Exception { + // Create a runner + Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + + // Create options with function calls disabled + CheckOptions options = CheckOptions.builder().disableFunctionCalls(true).build(); + + // Scripts without function calls should still be allowed + String[] validScripts = {"1 + 2", // Arithmetic expression + "x = 5", // Assignment + "x > 3 ? 'yes' : 'no'", // Ternary operator + "{a: 1, b: 2}" // Map literal + }; + + // All should pass validation + for (String script : validScripts) { + runner.check(script, options); + } + } +} diff --git a/src/test/java/com/alibaba/qlexpress4/OperatorLimitTest.java b/src/test/java/com/alibaba/qlexpress4/OperatorLimitTest.java new file mode 100644 index 0000000..7f365bc --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/OperatorLimitTest.java @@ -0,0 +1,325 @@ +package com.alibaba.qlexpress4; + +import com.alibaba.qlexpress4.exception.QLSyntaxException; +import com.alibaba.qlexpress4.operator.OperatorCheckStrategy; +import org.junit.Test; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +public class OperatorLimitTest { + + @Test + public void testCheckWithAllowedOperators() + throws QLSyntaxException { + // tag::operatorCheckStrategyExample[] + // Create a whitelist of allowed operators + Set allowedOps = new HashSet<>(Arrays.asList("+", "*")); + + // Configure check options with operator whitelist + CheckOptions checkOptions = + CheckOptions.builder().operatorCheckStrategy(OperatorCheckStrategy.whitelist(allowedOps)).build(); + + // Create runner and check script with custom options + Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + runner.check("a + b * c", checkOptions); // This will pass as + and * are allowed + // end::operatorCheckStrategyExample[] + } + + @Test + public void testCheckWithDisallowedOperators() { + Set allowedOps = new HashSet<>(Arrays.asList("+", "*")); + CheckOptions checkOptions = + CheckOptions.builder().operatorCheckStrategy(OperatorCheckStrategy.whitelist(allowedOps)).build(); + Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + try { + runner.check("a = b + c", checkOptions); + fail("Should throw QLSyntaxException"); + } + catch (QLSyntaxException e) { + // Verify error code + assertEquals("OPERATOR_NOT_ALLOWED", e.getErrorCode()); + + // Verify position information - should be precise now + assertEquals(2, e.getPos()); // Position of '=' in "a = b + c" + assertEquals(1, e.getLineNo()); // First line + assertEquals(2, e.getColNo()); // Column of '=' + + // Verify error lexeme + assertEquals("=", e.getErrLexeme()); + + // Verify error reason + assertTrue(e.getReason().contains("Script uses disallowed operator")); + assertTrue(e.getReason().contains("=")); + + // Verify complete message format + assertTrue("Message should contain error code", e.getMessage().contains("OPERATOR_NOT_ALLOWED")); + assertTrue("Message should contain line info", e.getMessage().contains("Line: 1")); + assertTrue("Message should contain column info", e.getMessage().contains("Column: 2")); + } + } + + @Test + public void testCheckWithForbiddenOperators() + throws QLSyntaxException { + Set forbiddenOps = new HashSet<>(Arrays.asList("=")); + CheckOptions checkOptions = + CheckOptions.builder().operatorCheckStrategy(OperatorCheckStrategy.blacklist(forbiddenOps)).build(); + Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + runner.check("a + b * c - d / e", checkOptions); + } + + @Test + public void testCheckWithForbiddenOperatorUsed() { + Set forbiddenOps = new HashSet<>(Arrays.asList("=")); + CheckOptions checkOptions = + CheckOptions.builder().operatorCheckStrategy(OperatorCheckStrategy.blacklist(forbiddenOps)).build(); + Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + try { + runner.check("a = b + c", checkOptions); + fail("Should throw QLSyntaxException"); + } + catch (QLSyntaxException e) { + // Verify error code + assertEquals("OPERATOR_NOT_ALLOWED", e.getErrorCode()); + + // Verify position information - should be precise now + assertEquals(2, e.getPos()); // Position of '=' in "a = b + c" + assertEquals(1, e.getLineNo()); // First line + assertEquals(2, e.getColNo()); // Column of '=' + + // Verify error lexeme + assertEquals("=", e.getErrLexeme()); + + // Verify error reason + assertTrue(e.getReason().contains("Script uses disallowed operator")); + assertTrue(e.getReason().contains("=")); + + // Verify complete message format + assertTrue("Message should contain error code", e.getMessage().contains("OPERATOR_NOT_ALLOWED")); + assertTrue("Message should contain line info", e.getMessage().contains("Line: 1")); + assertTrue("Message should contain column info", e.getMessage().contains("Column: 2")); + } + } + + @Test + public void testWhitelistWithPrefixOperator() { + Set allowedOps = new HashSet<>(Arrays.asList("+", "++")); + CheckOptions checkOptions = + CheckOptions.builder().operatorCheckStrategy(OperatorCheckStrategy.whitelist(allowedOps)).build(); + Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + + // Should allow prefix increment + runner.check("++a + b", checkOptions); + + // Should disallow prefix decrement + try { + runner.check("--a + b", checkOptions); + fail("Should throw QLSyntaxException"); + } + catch (QLSyntaxException e) { + assertEquals("OPERATOR_NOT_ALLOWED", e.getErrorCode()); + assertEquals("--", e.getErrLexeme()); + assertEquals(1, e.getLineNo()); + assertEquals(0, e.getColNo()); // Position of '--' + } + } + + @Test + public void testWhitelistWithSuffixOperator() { + Set allowedOps = new HashSet<>(Arrays.asList("+", "++")); + CheckOptions checkOptions = + CheckOptions.builder().operatorCheckStrategy(OperatorCheckStrategy.whitelist(allowedOps)).build(); + Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + + // Should allow suffix increment + runner.check("a++ + b", checkOptions); + + // Should disallow suffix decrement + try { + runner.check("a-- + b", checkOptions); + fail("Should throw QLSyntaxException"); + } + catch (QLSyntaxException e) { + assertEquals("OPERATOR_NOT_ALLOWED", e.getErrorCode()); + assertEquals("--", e.getErrLexeme()); + assertEquals(1, e.getLineNo()); + assertEquals(1, e.getColNo()); // Position of '--' in "a--" + } + } + + @Test + public void testBlacklistWithMultipleOperators() { + Set forbiddenOps = new HashSet<>(Arrays.asList("=", "*")); + CheckOptions checkOptions = + CheckOptions.builder().operatorCheckStrategy(OperatorCheckStrategy.blacklist(forbiddenOps)).build(); + Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + + // Should allow addition and subtraction + runner.check("a + b - c", checkOptions); + + // Should disallow assignment + try { + runner.check("a = b", checkOptions); + fail("Should throw QLSyntaxException"); + } + catch (QLSyntaxException e) { + assertEquals("OPERATOR_NOT_ALLOWED", e.getErrorCode()); + assertEquals("=", e.getErrLexeme()); + } + + // Should disallow multiplication + try { + runner.check("a * b", checkOptions); + fail("Should throw QLSyntaxException"); + } + catch (QLSyntaxException e) { + assertEquals("OPERATOR_NOT_ALLOWED", e.getErrorCode()); + assertEquals("*", e.getErrLexeme()); + } + } + + @Test + public void testPreciseErrorPositionInMultiLineScript() { + Set allowedOps = new HashSet<>(Arrays.asList("+")); + CheckOptions checkOptions = + CheckOptions.builder().operatorCheckStrategy(OperatorCheckStrategy.whitelist(allowedOps)).build(); + Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + + String multiLineScript = "a + b\n" + "c = d\n" + "e + f"; + + try { + runner.check(multiLineScript, checkOptions); + fail("Should throw QLSyntaxException"); + } + catch (QLSyntaxException e) { + assertEquals("OPERATOR_NOT_ALLOWED", e.getErrorCode()); + assertEquals("=", e.getErrLexeme()); + assertEquals(2, e.getLineNo()); // Second line where '=' appears + assertEquals(2, e.getColNo()); // Column of '=' in second line + } + } + + @Test + public void testEmptyWhitelistValidation() + throws QLSyntaxException { + // Empty whitelist means no operators are allowed + CheckOptions checkOptions = + CheckOptions.builder().operatorCheckStrategy(OperatorCheckStrategy.whitelist(new HashSet<>())).build(); + Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + + // Any operator should be disallowed + try { + runner.check("a + b", checkOptions); + fail("Should throw QLSyntaxException"); + } + catch (QLSyntaxException e) { + assertEquals("OPERATOR_NOT_ALLOWED", e.getErrorCode()); + assertEquals("+", e.getErrLexeme()); + } + } + + @Test + public void testEmptyBlacklistValidation() + throws QLSyntaxException { + // Empty blacklist means all operators are allowed + CheckOptions checkOptions = + CheckOptions.builder().operatorCheckStrategy(OperatorCheckStrategy.blacklist(new HashSet<>())).build(); + Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + + // All operators should be allowed + runner.check("a = b + c * d / e % f", checkOptions); + runner.check("++a--", checkOptions); + } + + @Test + public void testNullWhitelistValidation() + throws QLSyntaxException { + // Null whitelist is treated as empty set, meaning no operators are allowed + CheckOptions checkOptions = + CheckOptions.builder().operatorCheckStrategy(OperatorCheckStrategy.whitelist(null)).build(); + Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + + // Any operator should be disallowed + try { + runner.check("a + b", checkOptions); + fail("Should throw QLSyntaxException"); + } + catch (QLSyntaxException e) { + assertEquals("OPERATOR_NOT_ALLOWED", e.getErrorCode()); + assertEquals("+", e.getErrLexeme()); + } + } + + @Test + public void testNullBlacklistValidation() + throws QLSyntaxException { + // Null blacklist is treated as empty set, meaning all operators are allowed + CheckOptions checkOptions = + CheckOptions.builder().operatorCheckStrategy(OperatorCheckStrategy.blacklist(null)).build(); + Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + + // All operators should be allowed + runner.check("a = b + c * d / e % f", checkOptions); + runner.check("++a--", checkOptions); + } + + @Test + public void testComplexExpressionWithWhitelist() + throws QLSyntaxException { + Set allowedOps = new HashSet<>(Arrays.asList("+", "*")); + CheckOptions checkOptions = + CheckOptions.builder().operatorCheckStrategy(OperatorCheckStrategy.whitelist(allowedOps)).build(); + Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + + // Should allow complex arithmetic with allowed operators + runner.check("(a + b) * (c + d)", checkOptions); + + // Should disallow division operator (not in whitelist) + try { + runner.check("(a + b) * (c + d) / e", checkOptions); + fail("Should throw QLSyntaxException"); + } + catch (QLSyntaxException e) { + assertEquals("OPERATOR_NOT_ALLOWED", e.getErrorCode()); + assertEquals("/", e.getErrLexeme()); + } + + // Should disallow assignment in complex expression + try { + runner.check("(a + b) * (c = d)", checkOptions); + fail("Should throw QLSyntaxException"); + } + catch (QLSyntaxException e) { + assertEquals("OPERATOR_NOT_ALLOWED", e.getErrorCode()); + assertEquals("=", e.getErrLexeme()); + // Position should be accurate within the complex expression + assertTrue(e.getPos() > 10); // Should be after "(a + b) * (c " + } + } + + @Test + public void testErrorMessageContainsOperatorSet() { + Set allowedOps = new HashSet<>(Arrays.asList("+")); + CheckOptions checkOptions = + CheckOptions.builder().operatorCheckStrategy(OperatorCheckStrategy.whitelist(allowedOps)).build(); + Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + + try { + runner.check("a * b", checkOptions); + fail("Should throw QLSyntaxException"); + } + catch (QLSyntaxException e) { + String reason = e.getReason(); + assertTrue(reason.contains("Script uses disallowed operator")); + assertTrue(reason.contains("*")); + // Verify that the error message contains information about the operator + assertTrue(reason.contains("+") || reason.contains("allowed")); + } + } +} diff --git a/src/test/java/com/alibaba/qlexpress4/QLImportTester.java b/src/test/java/com/alibaba/qlexpress4/QLImportTester.java new file mode 100644 index 0000000..73e3290 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/QLImportTester.java @@ -0,0 +1,9 @@ +package com.alibaba.qlexpress4; + +public class QLImportTester { + + public static int add(int a, int b) { + return a + b; + } + +} diff --git a/src/test/java/com/alibaba/qlexpress4/TestSuiteRunner.java b/src/test/java/com/alibaba/qlexpress4/TestSuiteRunner.java new file mode 100644 index 0000000..cba99c3 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/TestSuiteRunner.java @@ -0,0 +1,295 @@ +package com.alibaba.qlexpress4; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; + +import com.alibaba.fastjson2.JSONException; +import com.alibaba.qlexpress4.aparser.ImportManager; +import com.alibaba.qlexpress4.exception.QLException; +import com.alibaba.qlexpress4.exception.QLRuntimeException; +import com.alibaba.qlexpress4.exception.UserDefineException; +import com.alibaba.qlexpress4.runtime.*; +import com.alibaba.qlexpress4.runtime.function.CustomFunction; + +import com.alibaba.qlexpress4.security.QLSecurityStrategy; + +import org.junit.Assert; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * Author: DQinYuan + */ +public class TestSuiteRunner { + private static final String ASSERT_FUNCTION_NAME = "assert"; + + private static final String ASSERT_FALSE_FUNCTION_NAME = "assertFalse"; + + private static final String ASSERT_ERROR_CODE_FUNCTION_NAME = "assertErrorCode"; + + private static final String PRINT_FUNCTION_NAME = "println"; + + private static final String TEST_PATH_ATT = "TEST_PATH"; + + private static final Express4Runner CONFIG_RUNNER = new Express4Runner(InitOptions.builder() + .securityStrategy(QLSecurityStrategy.open()) + .addDefaultImport(Arrays.asList(ImportManager.importCls("com.alibaba.qlexpress4.QLOptions"), + ImportManager.importCls("com.alibaba.qlexpress4.InitOptions"))) + .build()); + + private Express4Runner prepareRunner(InitOptions initOptions) { + Express4Runner testRunner = new Express4Runner(initOptions); + testRunner.addFunction(ASSERT_FUNCTION_NAME, new AssertFunction()); + testRunner.addFunction(ASSERT_FALSE_FUNCTION_NAME, new AssertFalseFunction()); + testRunner.addFunction(ASSERT_ERROR_CODE_FUNCTION_NAME, new AssertErrorCodeFunction()); + testRunner.addFunction(PRINT_FUNCTION_NAME, new PrintFunction()); + return testRunner; + } + + @Test + public void suiteTest() + throws URISyntaxException, IOException { + Path testSuiteRoot = getTestSuiteRoot(); + handleDirectory(testSuiteRoot, ""); + } + + @Test + public void featureDebug() + throws URISyntaxException, IOException { + Path filePath = getTestSuiteRoot().resolve("independent/function/nested_function_calls.ql"); + handleFile(filePath, filePath.toString(), true); + } + + private void handleDirectory(Path dir, String pathPrefix) + throws IOException { + Files.list(dir).forEach(path -> { + try { + String newPrefix = pathPrefix + "/" + path.getFileName(); + if (Files.isDirectory(path)) { + handleDirectory(path, newPrefix); + } + else { + handleFile(path, newPrefix, false); + } + } + catch (IOException e) { + throw new RuntimeException(e); + } + }); + } + + public void handleFile(Path qlFile, String path, boolean debug) + throws IOException { + printRunning(path); + Map attachments = new HashMap<>(); + attachments.put(TEST_PATH_ATT, path); + + String qlScript = new String(Files.readAllBytes(qlFile)); + // parse testsuite option first + Optional> scriptOptionOp = parseOption(qlScript); + Optional errCodeOp = scriptOptionOp.map(scriptOption -> (String)scriptOption.get("errCode")); + Optional initOptionsBuilder = + scriptOptionOp.map(scriptOption -> (InitOptions.Builder)scriptOption.get("initOptions")); + InitOptions initOptions = initOptionsBuilder.isPresent() + ? initOptionsBuilder.get().securityStrategy(QLSecurityStrategy.open()).debug(debug).build() + : InitOptions.builder().securityStrategy(QLSecurityStrategy.open()).debug(debug).build(); + Express4Runner express4Runner = prepareRunner(initOptions); + Optional optionsBuilder = + scriptOptionOp.map(scriptOption -> (QLOptions.Builder)scriptOption.get("qlOptions")); + QLOptions qlOptions = optionsBuilder.isPresent() ? optionsBuilder.get().attachments(attachments).build() + : QLOptions.builder().attachments(attachments).build(); + if (errCodeOp.isPresent()) { + long start = System.currentTimeMillis(); + assertErrCode(express4Runner, path, qlScript, qlOptions, errCodeOp.get(), debug); + printOk(path, System.currentTimeMillis() - start); + return; + } + + try { + long start = System.currentTimeMillis(); + express4Runner.execute(qlScript, Collections.emptyMap(), qlOptions); + printOk(path, System.currentTimeMillis() - start); + } + catch (Exception e) { + System.out.printf("%1$-95s %2$s\n", path, "error"); + throw e; + } + } + + private void printRunning(String path) { + System.out.printf("%1$-98s %2$s\n", path, "running"); + } + + private void printOk(String path, long consumeTime) { + System.out.printf("%1$-98s %2$s consume %3$dms\n", path, "ok", consumeTime); + } + + private void assertErrCode(Express4Runner runner, String path, String qlScript, QLOptions qlOptions, + String expectErrCode, boolean printE) { + try { + runner.execute(qlScript, Collections.emptyMap(), qlOptions); + Assert.fail("expect error codes:" + expectErrCode + ", but end normally"); + } + catch (QLException qlException) { + if (printE) { + qlException.printStackTrace(); + } + assertEquals(path + " error code assert fail", expectErrCode, qlException.getErrorCode()); + } + catch (Exception e) { + throw new RuntimeException(path + " unknown error", e); + } + } + + private Optional> parseOption(String qlScript) { + if (!qlScript.startsWith("/*")) { + return Optional.empty(); + } + int endIndex = qlScript.indexOf("*/"); + if (endIndex == -1) { + return Optional.empty(); + } + String configJson = qlScript.substring(2, endIndex); + try { + Map scriptOptions = (Map)CONFIG_RUNNER + .execute(configJson, Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS) + .getResult(); + return Optional.of(scriptOptions); + } + catch (JSONException e) { + return Optional.empty(); + } + } + + @Test + public void assertTest() { + Map attachment = new HashMap<>(); + attachment.put(TEST_PATH_ATT, "a/b.ql"); + + QLOptions attachOptions = QLOptions.builder().attachments(attachment).build(); + Express4Runner express4Runner = prepareRunner(InitOptions.DEFAULT_OPTIONS); + express4Runner.execute("assert(true)", Collections.emptyMap(), attachOptions); + assertErrCodeAndReason(express4Runner, "assert(false)", attachOptions, "BIZ_EXCEPTION", "a/b.ql: assert fail"); + assertErrCodeAndReason(express4Runner, + "assert(false, 'my test')", + attachOptions, + "BIZ_EXCEPTION", + "a/b.ql: my test"); + // variable can be the same name with function + express4Runner.execute("assert = 4;assert(assert == 4)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS); + } + + private Path getTestSuiteRoot() + throws URISyntaxException { + return Paths.get(getClass().getClassLoader().getResource("testsuite").toURI()); + } + + private void assertErrCodeAndReason(Express4Runner express4Runner, String script, QLOptions qlOptions, + String errCode, String reason) { + try { + express4Runner.execute(script, Collections.emptyMap(), qlOptions); + } + catch (QLException e) { + assertEquals(errCode, e.getErrorCode()); + assertEquals(reason, e.getReason()); + } + } + + private static class PrintFunction implements CustomFunction { + @Override + public Object call(QContext qContext, Parameters parameters) + throws Exception { + for (int i = 0; i < parameters.size(); i++) { + System.out.println(parameters.get(i).get()); + } + return null; + } + } + + private static class AssertFunction implements CustomFunction { + @Override + public Object call(QContext qContext, Parameters parameters) + throws Exception { + int pSize = parameters.size(); + switch (pSize) { + case 1: + Boolean b = (Boolean)parameters.getValue(0); + if (b == null || !b) { + throw new UserDefineException(wrap(qContext.attachment(), "assert fail")); + } + return null; + case 2: + Boolean b0 = (Boolean)parameters.getValue(0); + if (b0 == null || !b0) { + throw new UserDefineException(wrap(qContext.attachment(), (String)parameters.getValue(1))); + } + return null; + default: + throw new UserDefineException("invalid parameter size"); + } + } + + private String wrap(Map attachments, String originErrInfo) { + return attachments.get(TEST_PATH_ATT) + ": " + originErrInfo; + } + } + + private static class AssertFalseFunction implements CustomFunction { + @Override + public Object call(QContext qContext, Parameters parameters) + throws Exception { + int pSize = parameters.size(); + switch (pSize) { + case 1: + Boolean b = (Boolean)parameters.getValue(0); + if (b == null || b) { + throw new UserDefineException(wrap(qContext.attachment(), "assert fail")); + } + return null; + case 2: + Boolean b0 = (Boolean)parameters.getValue(0); + if (b0 == null || b0) { + throw new UserDefineException(wrap(qContext.attachment(), (String)parameters.getValue(1))); + } + return null; + default: + throw new UserDefineException("invalid parameter size"); + } + } + + private String wrap(Map attachments, String originErrInfo) { + return attachments.get(TEST_PATH_ATT) + ": " + originErrInfo; + } + } + + private static class AssertErrorCodeFunction implements CustomFunction { + @Override + public Object call(QContext qContext, Parameters parameters) + throws Throwable { + int pSize = parameters.size(); + if (pSize != 2) { + throw new UserDefineException(String.format("invalid pSize:%s, expected 2 parameters", pSize)); + } + + Value value = parameters.get(0); + Value value1 = parameters.get(1); + QLambda qLambda = (QLambda)value.get(); + try { + qLambda.run(); + throw new UserDefineException(String.format("expectedErrorCode:%s, but no error", value1.get())); + } + catch (QLRuntimeException e) { + if (!Objects.equals(value1.get(), e.getErrorCode())) { + throw new UserDefineException( + String.format("expectedErrorCode:%s, actualErrorCode:%s", value1.get(), e.getErrorCode())); + } + return null; + } + } + } +} diff --git a/src/test/java/com/alibaba/qlexpress4/aparser/CompileTimeFunctionTest.java b/src/test/java/com/alibaba/qlexpress4/aparser/CompileTimeFunctionTest.java new file mode 100644 index 0000000..bd7c6b1 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/aparser/CompileTimeFunctionTest.java @@ -0,0 +1,99 @@ +package com.alibaba.qlexpress4.aparser; + +import com.alibaba.qlexpress4.Express4Runner; +import com.alibaba.qlexpress4.InitOptions; +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.aparser.compiletimefunction.CodeGenerator; +import com.alibaba.qlexpress4.aparser.compiletimefunction.CompileTimeFunction; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.runtime.*; +import com.alibaba.qlexpress4.runtime.context.MapExpressContext; +import com.alibaba.qlexpress4.runtime.data.DataValue; +import com.alibaba.qlexpress4.runtime.instruction.*; +import com.alibaba.qlexpress4.security.QLSecurityStrategy; +import org.junit.Test; + +import java.util.*; + +import static org.junit.Assert.*; + +/** + * Author: DQinYuan + */ +public class CompileTimeFunctionTest { + + public static class ForEachFunction implements CompileTimeFunction { + + private static final String SCOPE_NAME = "FOR_EACH_FUNCTION"; + + // load iterator + // load lambda 第二个参数编译而来 + // load lambda 自定义, 循环 iterator, 调用 lambda 返回结果 list + // call + @Override + public void createFunctionInstruction(String functionName, List arguments, + OperatorFactory operatorFactory, CodeGenerator codeGenerator) { + if (arguments.size() != 2) { + throw codeGenerator.reportParseErr("INVALID_ARGUMENTS", + "FOREACH must hava 2 params, but accept " + arguments.size()); + } + + ErrorReporter functionErrReporter = codeGenerator.getErrorReporter(); + codeGenerator.addInstruction(new NewScopeInstruction(functionErrReporter, SCOPE_NAME)); + + // load iterator + QLParser.ExpressionContext arg0 = arguments.get(0); + codeGenerator.addInstructionsByTree(arg0); + codeGenerator.addInstruction( + new MethodInvokeInstruction(codeGenerator.newReporterWithToken(arg0.getStart()), "iterator", 0, false)); + + // load lambda + QLParser.ExpressionContext arg1 = arguments.get(1); + QLambdaDefinition bodyDefinition = codeGenerator.generateLambdaDefinition(arg1, + Collections.singletonList(new QLambdaDefinitionInner.Param("_", Object.class))); + codeGenerator.addInstruction(new LoadLambdaInstruction(functionErrReporter, bodyDefinition)); + + // custom lambda + codeGenerator.addInstruction(new CallConstInstruction(functionErrReporter, params -> { + Iterator iterator = (Iterator)params[0]; + QLambda body = (QLambda)params[1]; + List result = new ArrayList<>(); + while (iterator.hasNext()) { + Object next = iterator.next(); + result.add(body.call(next).getResult().get()); + } + return new QResult(new DataValue(result), QResult.ResultType.RETURN); + }, 2, "FOR_EACH")); + } + } + + @Test + public void forEachCompileFunctionTest() { + Express4Runner express4Runner = + new Express4Runner(InitOptions.builder().securityStrategy(QLSecurityStrategy.open()).build()); + express4Runner.addCompileTimeFunction("FOR_EACH", new ForEachFunction()); + assertNotNull(express4Runner.getCompileTimeFunction("FOR_EACH")); + Object result = + express4Runner.execute("FOR_EACH([1,2,3,4], _+1)", new HashMap<>(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals(Arrays.asList(2, 3, 4, 5), result); + } + + public static class GenInstructionNumFunction implements CompileTimeFunction { + @Override + public void createFunctionInstruction(String functionName, List arguments, + OperatorFactory operatorFactory, CodeGenerator codeGenerator) { + QLambdaDefinition lambdaDefinition = + codeGenerator.generateLambdaDefinition(arguments.get(0), Collections.emptyList()); + assertEquals(2, ((QLambdaDefinitionInner)lambdaDefinition).getInstructions().length); + } + } + + @Test + public void genInstructionNumTest() { + Express4Runner express4Runner = + new Express4Runner(InitOptions.builder().securityStrategy(QLSecurityStrategy.open()).build()); + express4Runner.addCompileTimeFunction("GEN_INST_NUM", new GenInstructionNumFunction()); + express4Runner + .parseToLambda("GEN_INST_NUM(1)", new MapExpressContext(new HashMap<>()), QLOptions.DEFAULT_OPTIONS); + } +} \ No newline at end of file diff --git a/src/test/java/com/alibaba/qlexpress4/aparser/ImportManagerTest.java b/src/test/java/com/alibaba/qlexpress4/aparser/ImportManagerTest.java new file mode 100644 index 0000000..4ffff61 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/aparser/ImportManagerTest.java @@ -0,0 +1,74 @@ +package com.alibaba.qlexpress4.aparser; + +import com.alibaba.qlexpress4.DefaultClassSupplier; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.function.Function; + +import static org.junit.Assert.*; + +/** + * Author: DQinYuan + */ +public class ImportManagerTest { + + public static class TestImportInner { + public static class TestImportInner2 { + + } + } + + @Test + public void loadTest() { + ImportManager importManager = + new ImportManager(DefaultClassSupplier.getInstance(), new ArrayList<>(), new HashMap<>()); + ImportManager.LoadPartQualifiedResult result0 = importManager.loadPartQualified(Arrays.asList("Function")); + assertNull(result0.getCls()); + importManager.addImport(ImportManager.importPack("java.util.function")); + + ImportManager.LoadPartQualifiedResult result1 = importManager.loadPartQualified(Arrays.asList("Function")); + assertEquals(Function.class, result1.getCls()); + assertEquals(1, result1.getRestIndex()); + + ImportManager.LoadPartQualifiedResult result2 = + importManager.loadPartQualified(Arrays.asList("java", "util", "function", "Function", "a", "b")); + assertEquals(Function.class, result2.getCls()); + assertEquals(4, result2.getRestIndex()); + + ImportManager.LoadPartQualifiedResult result3 = importManager.loadPartQualified(Arrays.asList("com", + "alibaba", + "qlexpress4", + "aparser", + "ImportManagerTest", + "TestImportInner", + "TestImportInner2")); + assertEquals(TestImportInner.TestImportInner2.class, result3.getCls()); + + ImportManager.LoadPartQualifiedResult result4 = + importManager.loadPartQualified(Arrays.asList("Function", "value")); + assertEquals(Function.class, result4.getCls()); + assertEquals(1, result4.getRestIndex()); + + ImportManager.LoadPartQualifiedResult result5 = + importManager.loadPartQualified(Arrays.asList("Function", "TT", "v")); + assertEquals(1, result5.getRestIndex()); + } + + @Test + public void loadInnerTest() { + ImportManager importManager = + new ImportManager(DefaultClassSupplier.getInstance(), new ArrayList<>(), new HashMap<>()); + importManager.addImport(ImportManager.importInnerCls("com.alibaba.qlexpress4.aparser.ImportManagerTest")); + ImportManager.LoadPartQualifiedResult result = + importManager.loadPartQualified(Arrays.asList("TestImportInner", "TestImportInner2")); + assertEquals(TestImportInner.TestImportInner2.class, result.getCls()); + + ImportManager.LoadPartQualifiedResult result2 = + importManager.loadPartQualified(Arrays.asList("TestImportInner", "testImportInner2")); + assertEquals(TestImportInner.class, result2.getCls()); + assertEquals(1, result2.getRestIndex()); + } +} \ No newline at end of file diff --git a/src/test/java/com/alibaba/qlexpress4/aparser/MockOpM.java b/src/test/java/com/alibaba/qlexpress4/aparser/MockOpM.java new file mode 100644 index 0000000..4e88461 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/aparser/MockOpM.java @@ -0,0 +1,96 @@ +package com.alibaba.qlexpress4.aparser; + +import com.alibaba.qlexpress4.QLPrecedences; + +public class MockOpM implements ParserOperatorManager { + @Override + public boolean isOpType(String lexeme, OpType opType) { + switch (opType) { + case MIDDLE: + switch (lexeme) { + case "+": + case "-": + case "/": + case ".*": + case ">>": + case ">>>": + case "==": + case "=": + case "instanceof": + case "&": + case "|": + case "%": + case ">": + case "<": + case "*": + case "?.": + case "<<": + case "&&": + case "||": + return true; + } + return false; + case PREFIX: + switch (lexeme) { + case "++": + case "-": + case "+": + case "~": + return true; + + } + return false; + case SUFFIX: + switch (lexeme) { + case "++": + return true; + } + return false; + default: + return false; + } + } + + @Override + public Integer precedence(String lexeme) { + switch (lexeme) { + case "+": + case "-": + return QLPrecedences.ADD; + case "/": + case "%": + case "*": + return QLPrecedences.MULTI; + case ".*": + case "?.": + return QLPrecedences.GROUP; + case "==": + case "instanceof": + case ">": + case "<": + return QLPrecedences.COMPARE; + case ">>": + case ">>>": + case "<<": + return QLPrecedences.BIT_MOVE; + case "=": + return QLPrecedences.ASSIGN; + case "||": + return QLPrecedences.OR; + case "&&": + return QLPrecedences.AND; + case "&": + return QLPrecedences.BIT_AND; + case "|": + return QLPrecedences.BIT_OR; + default: + throw new IllegalStateException("unknown op"); + } + } + + @Override + public Integer getAlias(String lexeme) { + return null; + } + +} diff --git a/src/test/java/com/alibaba/qlexpress4/aparser/SyntaxTreeFactoryTest.java b/src/test/java/com/alibaba/qlexpress4/aparser/SyntaxTreeFactoryTest.java new file mode 100644 index 0000000..33ef669 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/aparser/SyntaxTreeFactoryTest.java @@ -0,0 +1,311 @@ +package com.alibaba.qlexpress4.aparser; + +import com.alibaba.qlexpress4.runtime.MetaClass; +import com.alibaba.qlexpress4.runtime.QLambdaDefinitionInner; +import com.alibaba.qlexpress4.runtime.instruction.*; +import com.alibaba.qlexpress4.runtime.operator.arithmetic.MultiplyOperator; +import com.alibaba.qlexpress4.runtime.operator.assign.AssignOperator; +import org.junit.Test; + +import java.util.List; +import java.util.function.Function; + +import static org.junit.Assert.*; + +/** + * Author: DQinYuan + */ +public class SyntaxTreeFactoryTest { + + @Test + public void visitPathExprTestWhenMix() { + String script = "java.util.function.Function.a.b.cc()"; + List instructions = getScriptInstructions(script, InterpolationMode.VARIABLE); + assertEquals(6, instructions.size()); + ConstInstruction constInstruction = (ConstInstruction)instructions.get(0); + MetaClass metaClass = (MetaClass)constInstruction.getConstObj(); + assertEquals(Function.class, metaClass.getClz()); + GetFieldInstruction getFieldInstruction1 = (GetFieldInstruction)instructions.get(1); + assertEquals("a", getFieldInstruction1.getFieldName()); + GetFieldInstruction getFieldInstruction2 = (GetFieldInstruction)instructions.get(2); + assertEquals("b", getFieldInstruction2.getFieldName()); + MethodInvokeInstruction methodInvokeInstruction = (MethodInvokeInstruction)instructions.get(3); + assertEquals("cc", methodInvokeInstruction.getMethodName()); + } + + @Test + public void visitPathExprTestWhenInnerCls() { + String script = "com.alibaba.qlexpress4.aparser.ImportManagerTest.TestImportInner.TestImportInner2.pp[m]"; + List instructions = getScriptInstructions(script, InterpolationMode.VARIABLE); + assertEquals(5, instructions.size()); + ConstInstruction constInstruction = (ConstInstruction)instructions.get(0); + MetaClass metaClass = (MetaClass)constInstruction.getConstObj(); + assertEquals(ImportManagerTest.TestImportInner.TestImportInner2.class, metaClass.getClz()); + GetFieldInstruction getFieldInstruction = (GetFieldInstruction)instructions.get(1); + assertEquals("pp", getFieldInstruction.getFieldName()); + LoadInstruction loadInstruction = (LoadInstruction)instructions.get(2); + assertEquals("m", loadInstruction.getName()); + assertTrue(instructions.get(3) instanceof IndexInstruction); + } + + @Test + public void visitCallTest() { + String script = "call(mm)"; + List instructions = getScriptInstructions(script, InterpolationMode.VARIABLE); + assertEquals(4, instructions.size()); + LoadInstruction loadInstruction = (LoadInstruction)instructions.get(0); + assertEquals("mm", loadInstruction.getName()); + } + + @Test + public void numberTest() { + String script = "10_0_0l"; + List instructions = getScriptInstructions(script, InterpolationMode.VARIABLE); + assertEquals(2, instructions.size()); + ConstInstruction constInstruction = (ConstInstruction)instructions.get(0); + assertEquals(1000L, constInstruction.getConstObj()); + } + + @Test + public void macroDefineTest() { + String script = "macro add {a+b} add;int c = 10;"; + List instructions = getScriptInstructions(script, InterpolationMode.VARIABLE); + assertEquals(7, instructions.size()); + } + + @Test + public void stringEscapeTest() { + // invalid escape \p will be ignored + String script = "\"\\r\\n\\p\""; + List instructions = getScriptInstructions(script, InterpolationMode.VARIABLE); + ConstInstruction qlInstruction = (ConstInstruction)instructions.get(0); + String constObj = (String)qlInstruction.getConstObj(); + assertEquals("\r\n", constObj); + } + + @Test + public void castTest() { + String script = "1+(int)3L"; + List instructions = getScriptInstructions(script, InterpolationMode.VARIABLE); + assertTrue(instructions.get(3) instanceof CastInstruction); + } + + @Test + public void cusOpTest() { + String script = "c.*d"; + getScriptInstructions(script, InterpolationMode.VARIABLE); + + String script1 = "c>>>d"; + getScriptInstructions(script1, InterpolationMode.VARIABLE); + } + + @Test + public void pathPartTest() { + String script = "assert((java.lang.Object) a == 1)"; + List instructions = getScriptInstructions(script, InterpolationMode.VARIABLE); + ConstInstruction constInstruction = (ConstInstruction)instructions.get(0); + assertEquals(Object.class, constInstruction.getConstObj()); + LoadInstruction loadInstruction = (LoadInstruction)instructions.get(1); + assertEquals("a", loadInstruction.getName()); + assertTrue(instructions.get(2) instanceof CastInstruction); + } + + @Test + public void fieldExpressionTest() { + String script = "\"null\".equals(b)"; + List instructions = getScriptInstructions(script, InterpolationMode.VARIABLE); + assertTrue(instructions.get(0) instanceof ConstInstruction); + } + + @Test + public void lambdaExprBodyTest() { + String script = "f = e -> try {\n" + " throw e;\n" + "} catch (java.lang.NullPointerException n) {\n" + + " 100\n" + "} catch (java.lang.Exception e) {\n" + " 10\n" + "};"; + List instructions = getScriptInstructions(script, InterpolationMode.VARIABLE); + LoadLambdaInstruction loadLambdaInstruction = (LoadLambdaInstruction)instructions.get(1); + QLambdaDefinitionInner lambdaDefinition = (QLambdaDefinitionInner)loadLambdaInstruction.getLambdaDefinition(); + assertEquals(2, lambdaDefinition.getInstructions().length); + } + + @Test + public void lambdaBlockBodyTest() { + String script = "f = e -> {10};"; + List instructions = getScriptInstructions(script, InterpolationMode.VARIABLE); + LoadLambdaInstruction loadLambdaInstruction = (LoadLambdaInstruction)instructions.get(1); + QLambdaDefinitionInner lambdaDefinition = (QLambdaDefinitionInner)loadLambdaInstruction.getLambdaDefinition(); + assertEquals(2, lambdaDefinition.getInstructions().length); + assertTrue(lambdaDefinition.getInstructions()[0] instanceof ConstInstruction); + } + + @Test + public void lambdaMapBodyTest() { + String script = "f = e -> {'test': 1234};"; + List instructions = getScriptInstructions(script, InterpolationMode.VARIABLE); + LoadLambdaInstruction loadLambdaInstruction = (LoadLambdaInstruction)instructions.get(1); + QLambdaDefinitionInner lambdaDefinition = (QLambdaDefinitionInner)loadLambdaInstruction.getLambdaDefinition(); + assertEquals(3, lambdaDefinition.getInstructions().length); + } + + @Test + public void newArrayTest() { + String script = "new int[][] {new int[] {1,2}, new int[] {3,4}}"; + List instructions = getScriptInstructions(script, InterpolationMode.VARIABLE); + NewArrayInstruction newArrayInstruction = (NewArrayInstruction)instructions.get(instructions.size() - 2); + assertEquals(Integer[].class, newArrayInstruction.getClz()); + } + + @Test + public void instanceOfTest() { + String script = "1 instanceof int"; + List instructions = getScriptInstructions(script, InterpolationMode.VARIABLE); + ConstInstruction constInstruction = (ConstInstruction)instructions.get(1); + MetaClass constMeta = (MetaClass)constInstruction.getConstObj(); + assertEquals(Integer.class, constMeta.getClz()); + } + + @Test + public void instanceOfStrArrTest() { + String script = "1 instanceof java.lang.String[][][]"; + List instructions = getScriptInstructions(script, InterpolationMode.VARIABLE); + ConstInstruction constInstruction = (ConstInstruction)instructions.get(1); + MetaClass constMeta = (MetaClass)constInstruction.getConstObj(); + assertEquals(String[][][].class, constMeta.getClz()); + } + + @Test + public void instanceOfIntArrTest() { + String script = "1 instanceof int[][][]"; + List instructions = getScriptInstructions(script, InterpolationMode.VARIABLE); + ConstInstruction constInstruction = (ConstInstruction)instructions.get(1); + MetaClass constMeta = (MetaClass)constInstruction.getConstObj(); + assertEquals(Integer[][][].class, constMeta.getClz()); + } + + @Test + public void bitOperatorTest() { + assertOperator("true & true", "&"); + assertOperator("true | true", "|"); + assertOperator("2 % 3", "%"); + } + + private void assertOperator(String script, String expectOp) { + List instructions = getScriptInstructions(script, InterpolationMode.VARIABLE); + OperatorInstruction operatorInstruction = (OperatorInstruction)instructions.get(2); + assertEquals(expectOp, operatorInstruction.getOperator().getOperator()); + } + + @Test + public void opPrecedencesTest() { + String script = "a = 1+2*3+10"; + List instructions = getScriptInstructions(script, InterpolationMode.VARIABLE); + OperatorInstruction operatorMulti = (OperatorInstruction)instructions.get(4); + assertTrue(operatorMulti.getOperator() instanceof MultiplyOperator); + OperatorInstruction operatorAssign = (OperatorInstruction)instructions.get(9); + assertTrue(operatorAssign.getOperator() instanceof AssignOperator); + } + + @Test + public void ternaryTest() { + String script = "l = (x) -> x > 10 ? 11 : 100"; + List instructions = getScriptInstructions(script, InterpolationMode.VARIABLE); + LoadInstruction loadInstruction = (LoadInstruction)instructions.get(0); + assertEquals("l", loadInstruction.getName()); + assertTrue(instructions.get(1) instanceof LoadLambdaInstruction); + OperatorInstruction operatorInstruction = (OperatorInstruction)instructions.get(2); + assertTrue(operatorInstruction.getOperator() instanceof AssignOperator); + } + + @Test + public void functionInterfaceTest() { + String script = "java.lang.Runnable r = () -> a = 8;"; + getScriptInstructions(script, InterpolationMode.VARIABLE); + } + + @Test + public void groupPriorityTest() { + String script = "a.*b.*c[2]+d.*e[1:2]"; + getScriptInstructions(script, InterpolationMode.VARIABLE); + } + + @Test + public void numberAmbiguousValueTest() { + String script = "1.doubleValue()"; + List instructions = getScriptInstructions(script, InterpolationMode.VARIABLE); + assertEquals("doubleValue", ((MethodInvokeInstruction)instructions.get(1)).getMethodName()); + } + + @Test + public void classifiedJsonTest() { + String script = "{'@class':'java.lang.Object', 'a': 'cccc'}"; + List instructions = getScriptInstructions(script, InterpolationMode.VARIABLE); + assertTrue(instructions.get(1) instanceof NewFilledInstanceInstruction); + } + + @Test + public void selectorTest() { + List instructions = + getScriptInstructions("${ TextField-AXXE } + ${v231}", InterpolationMode.SCRIPT); + LoadInstruction loadInstruction0 = (LoadInstruction)instructions.get(0); + assertEquals("TextField-AXXE", loadInstruction0.getName()); + LoadInstruction loadInstruction1 = (LoadInstruction)instructions.get(1); + assertEquals("v231", loadInstruction1.getName()); + + List instructions1 = + getScriptInstructions("${ TextField-A} + ${v2}", InterpolationMode.VARIABLE); + LoadInstruction loadInstruction10 = (LoadInstruction)instructions1.get(0); + assertEquals("TextField-A", loadInstruction10.getName()); + LoadInstruction loadInstruction11 = (LoadInstruction)instructions1.get(1); + assertEquals("v2", loadInstruction11.getName()); + } + + @Test + public void doubleQuoteStringScriptTest() { + List instructs = getScriptInstructions("\"a ${v-1}\"", InterpolationMode.SCRIPT); + ConstInstruction i0 = (ConstInstruction)instructs.get(0); + assertEquals("a ", i0.getConstObj()); + LoadInstruction i1 = (LoadInstruction)instructs.get(1); + assertEquals("v", i1.getName()); + ConstInstruction i2 = (ConstInstruction)instructs.get(2); + assertEquals(1, i2.getConstObj()); + OperatorInstruction i3 = (OperatorInstruction)instructs.get(3); + assertEquals("-", i3.getOperator().getOperator()); + StringJoinInstruction i4 = (StringJoinInstruction)instructs.get(4); + assertEquals(2, i4.getN()); + } + + @Test + public void doubleQuoteStringScriptTest2() { + getScriptInstructions("\"Hello ${a} ccc\"", InterpolationMode.SCRIPT); + } + + @Test + public void doubleQuoteStringVariableTest() { + List instructions = getScriptInstructions("\"a ${ v-1 } b\"", InterpolationMode.VARIABLE); + ConstInstruction i0 = (ConstInstruction)instructions.get(0); + assertEquals("a ", i0.getConstObj()); + LoadInstruction i1 = (LoadInstruction)instructions.get(1); + assertEquals("v-1", i1.getName()); + ConstInstruction i2 = (ConstInstruction)instructions.get(2); + assertEquals(" b", i2.getConstObj()); + StringJoinInstruction i3 = (StringJoinInstruction)instructions.get(3); + assertEquals(3, i3.getN()); + } + + @Test + public void ifTest() { + List instructions = getScriptInstructions( + "if (a > 0 && a < 5) {\n" + " true\n" + "} else if (a > 5 && a < 10) {\n" + " false\n" + + "} else if (a > 10 && a < 15) {\n" + " true\n" + "} == true", + InterpolationMode.VARIABLE); + OperatorInstruction equalInstruction = (OperatorInstruction)instructions.get(instructions.size() - 2); + assertEquals("==", equalInstruction.getOperator().getOperator()); + } + + private List getScriptInstructions(String script, InterpolationMode interpolationMode) { + QLParser.ProgramContext programContext = SyntaxTreeFactory + .buildTree(script, new MockOpM(), true, false, System.out::println, interpolationMode, "${", "}", true); + QvmInstructionVisitor visitor = new QvmInstructionVisitor(script); + programContext.accept(visitor); + return visitor.getInstructions(); + } +} \ No newline at end of file diff --git a/src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java b/src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java new file mode 100644 index 0000000..25e01dc --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/docs/CustomItemsDocTest.java @@ -0,0 +1,143 @@ +package com.alibaba.qlexpress4.docs; + +import com.alibaba.qlexpress4.Express4Runner; +import com.alibaba.qlexpress4.InitOptions; +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.QLPrecedences; +import com.alibaba.qlexpress4.api.QLFunctionalVarargs; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Examples used by docs/custom-item-source.adoc + */ +public class CustomItemsDocTest { + + @Test + public void addFunctionWithJavaFunctionalTest() { + // tag::addFunctionWithJavaFunctional[] + Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + // Function + runner.addFunction("inc", (Function)x -> x + 1); + // Predicate + runner.addFunction("isPos", (Predicate)x -> x > 0); + // Runnable + runner.addFunction("notify", () -> { + }); + // Consumer + runner.addFunction("print", (Consumer)System.out::println); + + Object r1 = runner.execute("inc(1)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); + Object r2 = runner.execute("isPos(1)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals(2, r1); + assertEquals(true, r2); + // end::addFunctionWithJavaFunctional[] + } + + // tag::joinFunctionVarargsObj[] + public static class JoinFunction implements QLFunctionalVarargs { + @Override + public Object call(Object... params) { + return Arrays.stream(params).map(Object::toString).collect(Collectors.joining(",")); + } + } + // end::joinFunctionVarargsObj[] + + @Test + public void addFunctionByVarargsTest() { + // tag::addFunctionByVarargs[] + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + express4Runner.addVarArgsFunction("join", new JoinFunction()); + Object resultFunction = + express4Runner.execute("join(1,2,3)", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals("1,2,3", resultFunction); + // end::addFunctionByVarargs[] + } + + @Test + public void addOperatorWithPrecedenceTest() { + // tag::addOperatorWithPrecedence[] + Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + runner.addOperator("?><", (left, right) -> left.get().toString() + right.get().toString(), QLPrecedences.ADD); + Object r = runner.execute("1 ?>< 2 * 3", new HashMap<>(), QLOptions.DEFAULT_OPTIONS).getResult(); + // precedence set to ADD, so multiply first, then custom operator: "1" + "6" => "16" + assertEquals("16", r); + // end::addOperatorWithPrecedence[] + } + + @Test + public void replaceDefaultOperatorTest() { + // tag::replaceDefaultOperator[] + Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + boolean ok = runner.replaceDefaultOperator("+", + (left, right) -> Double.parseDouble(left.get().toString()) + Double.parseDouble(right.get().toString())); + assertTrue(ok); + Object r = runner.execute("'1.2' + '2.3'", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals(3.5d, r); + // end::replaceDefaultOperator[] + } + + @Test + public void addOperatorBiFunctionTest() { + // tag::addOperatorBiFunction[] + Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + express4Runner.addOperatorBiFunction("join", (left, right) -> left + "," + right); + Object resultOperator = + express4Runner.execute("1 join 2 join 3", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals("1,2,3", resultOperator); + // end::addOperatorBiFunction[] + } + + @Test + public void addOperatorByVarargsTest() { + // tag::addOperatorByVarargs[] + Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + runner.addOperator("join", params -> params[0] + "," + params[1]); + Object r = runner.execute("1 join 2", Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals("1,2", r); + // end::addOperatorByVarargs[] + } + + @Test + public void qlfunctionalvarargsAllInOneTest() { + // tag::qlfunctionalvarargsAllInOne[] + Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); + QLFunctionalVarargs allInOne = params -> { + // sum numbers no matter how many args + double sum = 0d; + for (Object p : params) { + if (p instanceof Number) { + sum += ((Number)p).doubleValue(); + } + } + return sum; + }; + + // as function + runner.addVarArgsFunction("sumAll", allInOne); + // as operator + runner.addOperator("+&", allInOne); + // as extension function: first arg is the receiver, followed by call arguments + runner.addExtendFunction("plusAll", Number.class, allInOne); + + Map ctx = new HashMap<>(); + Object rf = runner.execute("sumAll(1,2,3)", ctx, QLOptions.DEFAULT_OPTIONS).getResult(); + Object ro = runner.execute("1 +& 4", ctx, QLOptions.DEFAULT_OPTIONS).getResult(); + Object re = runner.execute("1.plusAll(5)", ctx, QLOptions.DEFAULT_OPTIONS).getResult(); + assertEquals(6d, rf); + assertEquals(5d, ro); + assertEquals(6d, re); + // end::qlfunctionalvarargsAllInOne[] + } +} diff --git a/src/test/java/com/alibaba/qlexpress4/exception/MockErrorReporter.java b/src/test/java/com/alibaba/qlexpress4/exception/MockErrorReporter.java new file mode 100644 index 0000000..b48298b --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/exception/MockErrorReporter.java @@ -0,0 +1,8 @@ +package com.alibaba.qlexpress4.exception; + +public class MockErrorReporter implements ErrorReporter { + @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/test/java/com/alibaba/qlexpress4/exception/QLExceptionTest.java b/src/test/java/com/alibaba/qlexpress4/exception/QLExceptionTest.java new file mode 100644 index 0000000..140f4fc --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/exception/QLExceptionTest.java @@ -0,0 +1,24 @@ +package com.alibaba.qlexpress4.exception; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Author: DQinYuan + */ +public class QLExceptionTest { + + @Test + public void reportTest() { + String script = "if (3>1) {\n" + " break 9;\n" + "} else {\n" + " return 11;\n" + "}"; + QLSyntaxException qlSyntaxException = QLException + .reportScannerErr(script, 13, 2, 3, "break", "BREAK_MUST_IN_FOR_OR_WHILE", "break must in for/while"); + assertEquals( + "[Error BREAK_MUST_IN_FOR_OR_WHILE: break must in for/while]\n" + + "[Near: if (3>1) { break 9; } else { retur...]\n" + " ^^^^^\n" + + "[Line: 2, Column: 3]", + qlSyntaxException.getMessage()); + } + +} \ No newline at end of file diff --git a/src/test/java/com/alibaba/qlexpress4/generic/GenericTypeTest.java b/src/test/java/com/alibaba/qlexpress4/generic/GenericTypeTest.java new file mode 100644 index 0000000..3518a9e --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/generic/GenericTypeTest.java @@ -0,0 +1,48 @@ +package com.alibaba.qlexpress4.generic; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Collections; +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; + +public class GenericTypeTest { + @Test + public void test() + throws NoSuchFieldException, NoSuchMethodException { + // 获取属性的泛型类型 + Field field1 = Class4GenericType.class.getDeclaredField("field1"); + Type type1 = field1.getGenericType(); + Type[] actualTypeArguments = ((ParameterizedType)type1).getActualTypeArguments(); + Assert.assertEquals(String.class, actualTypeArguments[0]); + + Field field2 = Class4GenericType.class.getDeclaredField("field2"); + Type type2 = field2.getGenericType(); + Assert.assertEquals(String.class, type2); + + // 获取方法出入参的泛型类型 + Method method1 = Class4GenericType.class.getMethod("method1", List.class); + Parameter firstParameter = method1.getParameters()[0]; + ParameterizedType firstParameterizedType = (ParameterizedType)(firstParameter.getParameterizedType()); + Assert.assertEquals(Long.class, firstParameterizedType.getActualTypeArguments()[0]); + + Type returnType = method1.getGenericReturnType(); + ParameterizedType returnParameterizedType = (ParameterizedType)returnType; + Assert.assertEquals(String.class, returnParameterizedType.getActualTypeArguments()[0]); + } + + public static class Class4GenericType { + private List field1; + + private String field2; + + public List method1(List longList) { + return Collections.emptyList(); + } + } +} diff --git a/src/test/java/com/alibaba/qlexpress4/inport/InterGrandPa.java b/src/test/java/com/alibaba/qlexpress4/inport/InterGrandPa.java new file mode 100644 index 0000000..f403e10 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/inport/InterGrandPa.java @@ -0,0 +1,9 @@ +package com.alibaba.qlexpress4.inport; + +public class InterGrandPa { + + public String haha() { + return "grandPa"; + } + +} diff --git a/src/test/java/com/alibaba/qlexpress4/inport/InterWithDefaultImpl.java b/src/test/java/com/alibaba/qlexpress4/inport/InterWithDefaultImpl.java new file mode 100644 index 0000000..2828e03 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/inport/InterWithDefaultImpl.java @@ -0,0 +1,4 @@ +package com.alibaba.qlexpress4.inport; + +public class InterWithDefaultImpl implements InterWithDefaultMethod { +} diff --git a/src/test/java/com/alibaba/qlexpress4/inport/InterWithDefaultImplChild.java b/src/test/java/com/alibaba/qlexpress4/inport/InterWithDefaultImplChild.java new file mode 100644 index 0000000..a7200bc --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/inport/InterWithDefaultImplChild.java @@ -0,0 +1,4 @@ +package com.alibaba.qlexpress4.inport; + +public class InterWithDefaultImplChild extends InterWithDefaultImpl { +} diff --git a/src/test/java/com/alibaba/qlexpress4/inport/InterWithDefaultImplExtGrandPa.java b/src/test/java/com/alibaba/qlexpress4/inport/InterWithDefaultImplExtGrandPa.java new file mode 100644 index 0000000..3106a03 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/inport/InterWithDefaultImplExtGrandPa.java @@ -0,0 +1,4 @@ +package com.alibaba.qlexpress4.inport; + +public class InterWithDefaultImplExtGrandPa extends InterGrandPa implements InterWithDefaultMethod { +} diff --git a/src/test/java/com/alibaba/qlexpress4/inport/InterWithDefaultImplGrandPaChild.java b/src/test/java/com/alibaba/qlexpress4/inport/InterWithDefaultImplGrandPaChild.java new file mode 100644 index 0000000..b58505c --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/inport/InterWithDefaultImplGrandPaChild.java @@ -0,0 +1,4 @@ +package com.alibaba.qlexpress4.inport; + +public class InterWithDefaultImplGrandPaChild extends InterWithDefaultImplExtGrandPa { +} diff --git a/src/test/java/com/alibaba/qlexpress4/inport/InterWithDefaultMethod.java b/src/test/java/com/alibaba/qlexpress4/inport/InterWithDefaultMethod.java new file mode 100644 index 0000000..03448cc --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/inport/InterWithDefaultMethod.java @@ -0,0 +1,9 @@ +package com.alibaba.qlexpress4.inport; + +public interface InterWithDefaultMethod { + + default String haha() { + return "haha"; + } + +} diff --git a/src/test/java/com/alibaba/qlexpress4/inport/MyDesk.java b/src/test/java/com/alibaba/qlexpress4/inport/MyDesk.java new file mode 100644 index 0000000..200c3cc --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/inport/MyDesk.java @@ -0,0 +1,27 @@ +package com.alibaba.qlexpress4.inport; + +/** + * Author: DQinYuan + */ +public class MyDesk { + + private String book1; + + private String book2; + + public String getBook1() { + return book1; + } + + public void setBook1(String book1) { + this.book1 = book1; + } + + public String getBook2() { + return book2; + } + + public void setBook2(String book2) { + this.book2 = book2; + } +} diff --git a/src/test/java/com/alibaba/qlexpress4/inport/MyHome.java b/src/test/java/com/alibaba/qlexpress4/inport/MyHome.java new file mode 100644 index 0000000..f7d4ee6 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/inport/MyHome.java @@ -0,0 +1,43 @@ +package com.alibaba.qlexpress4.inport; + +/** + * Author: DQinYuan + */ +public class MyHome { + + private String sofa; + + private String chair; + + private MyDesk myDesk; + + private String bed; + + public String getSofa() { + return sofa; + } + + public void setSofa(String sofa) { + this.sofa = sofa; + } + + public String getChair() { + return chair; + } + + public MyDesk getMyDesk() { + return myDesk; + } + + public void setMyDesk(MyDesk myDesk) { + this.myDesk = myDesk; + } + + public void setChair(String chair) { + this.chair = chair; + } + + public String getBed() { + return bed; + } +} diff --git a/src/test/java/com/alibaba/qlexpress4/inport/Person.java b/src/test/java/com/alibaba/qlexpress4/inport/Person.java new file mode 100644 index 0000000..6a2cef7 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/inport/Person.java @@ -0,0 +1,17 @@ +package com.alibaba.qlexpress4.inport; + +/** + * @author 冰够 + */ +public class Person implements Comparable { + private final int age; + + public Person(int age) { + this.age = age; + } + + @Override + public int compareTo(Person o) { + return Integer.compare(age, o.age); + } +} diff --git a/src/test/java/com/alibaba/qlexpress4/inport/Sample.java b/src/test/java/com/alibaba/qlexpress4/inport/Sample.java new file mode 100644 index 0000000..280e63d --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/inport/Sample.java @@ -0,0 +1,10 @@ +package com.alibaba.qlexpress4.inport; + +/** + * Author: DQinYuan + */ +public class Sample { + + public static int value = 1; + +} diff --git a/src/test/java/com/alibaba/qlexpress4/inport/Sample1.java b/src/test/java/com/alibaba/qlexpress4/inport/Sample1.java new file mode 100644 index 0000000..1b57ef2 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/inport/Sample1.java @@ -0,0 +1,10 @@ +package com.alibaba.qlexpress4.inport; + +/** + * Author: DQinYuan + */ +public class Sample1 { + + public static int value = 10; + +} diff --git a/src/test/java/com/alibaba/qlexpress4/pf4j/Pf4jClassSupplierTest.java b/src/test/java/com/alibaba/qlexpress4/pf4j/Pf4jClassSupplierTest.java new file mode 100644 index 0000000..1a9b852 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/pf4j/Pf4jClassSupplierTest.java @@ -0,0 +1,56 @@ +package com.alibaba.qlexpress4.pf4j; + +import com.alibaba.qlexpress4.ClassSupplier; +import com.alibaba.qlexpress4.Express4Runner; +import com.alibaba.qlexpress4.InitOptions; +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.security.QLSecurityStrategy; + +import org.junit.Assert; +import org.junit.Test; +import org.pf4j.DefaultPluginManager; +import org.pf4j.PluginManager; +import org.pf4j.PluginWrapper; + +import java.io.File; +import java.nio.file.Path; +import java.util.Collections; + +public class Pf4jClassSupplierTest { + @Test + public void testPluginClassSupplier() + throws Exception { + // tag::pluginClassSupplier[] + // Specify plugin directory (test-plugins directory under test resources) + Path pluginsDir = new File("src/test/resources/test-plugins").toPath(); + PluginManager pluginManager = new DefaultPluginManager(pluginsDir); + pluginManager.loadPlugins(); + pluginManager.startPlugins(); + + // Get the PluginClassLoader of the first plugin + PluginWrapper plugin = pluginManager.getPlugins().get(0); + ClassLoader pluginClassLoader = plugin.getPluginClassLoader(); + + // Custom class supplier using plugin ClassLoader + ClassSupplier pluginClassSupplier = clsName -> { + try { + return Class.forName(clsName, true, pluginClassLoader); + } + catch (ClassNotFoundException | NoClassDefFoundError e) { + return null; + } + }; + + InitOptions options = InitOptions.builder() + .securityStrategy(QLSecurityStrategy.open()) + .classSupplier(pluginClassSupplier) + .build(); + Express4Runner runner = new Express4Runner(options); + + String script = "import com.alibaba.qlexpress4.pf4j.TestPluginInterface; TestPluginInterface.TEST_CONSTANT"; + Object result = runner.execute(script, Collections.emptyMap(), QLOptions.DEFAULT_OPTIONS).getResult(); + + Assert.assertEquals("Hello from PF4J Plugin!", result.toString()); + // end::pluginClassSupplier[] + } +} \ No newline at end of file diff --git a/src/test/java/com/alibaba/qlexpress4/pf4j/TestPluginImpl.java b/src/test/java/com/alibaba/qlexpress4/pf4j/TestPluginImpl.java new file mode 100644 index 0000000..2a8e308 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/pf4j/TestPluginImpl.java @@ -0,0 +1,24 @@ +package com.alibaba.qlexpress4.pf4j; + +import org.pf4j.Plugin; +import org.pf4j.PluginWrapper; + +/** + * Test plugin implementation class + */ +public class TestPluginImpl extends Plugin { + + public TestPluginImpl(PluginWrapper wrapper) { + super(wrapper); + } + + @Override + public void start() { + System.out.println("TestPlugin started"); + } + + @Override + public void stop() { + System.out.println("TestPlugin stopped"); + } +} \ No newline at end of file diff --git a/src/test/java/com/alibaba/qlexpress4/pf4j/TestPluginInterface.java b/src/test/java/com/alibaba/qlexpress4/pf4j/TestPluginInterface.java new file mode 100644 index 0000000..770a3fd --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/pf4j/TestPluginInterface.java @@ -0,0 +1,13 @@ +package com.alibaba.qlexpress4.pf4j; + +/** + * Test plugin interface containing a string constant + */ +public interface TestPluginInterface { + + /** + * Test string constant + */ + String TEST_CONSTANT = "Hello from PF4J Plugin!"; + +} \ No newline at end of file diff --git a/src/test/java/com/alibaba/qlexpress4/runtime/FixedSizeStackTest.java b/src/test/java/com/alibaba/qlexpress4/runtime/FixedSizeStackTest.java new file mode 100644 index 0000000..829d9bf --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/runtime/FixedSizeStackTest.java @@ -0,0 +1,32 @@ +package com.alibaba.qlexpress4.runtime; + +import com.alibaba.qlexpress4.runtime.data.DataValue; +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Author: DQinYuan + */ +public class FixedSizeStackTest { + + @Test + public void pushPopTest() { + FixedSizeStack fixedSizeStack = new FixedSizeStack(4); + fixedSizeStack.push(new DataValue(1)); + fixedSizeStack.push(new DataValue(2)); + fixedSizeStack.push(new DataValue(3)); + fixedSizeStack.push(new DataValue(4)); + assertEquals(4, fixedSizeStack.pop().get()); + assertEquals(3, fixedSizeStack.pop().get()); + fixedSizeStack.push(new DataValue(5)); + fixedSizeStack.push(new DataValue(6)); + Parameters parameters = fixedSizeStack.pop(3); + assertEquals(2, parameters.get(0).get()); + assertEquals(5, parameters.get(1).get()); + assertEquals(6, parameters.get(2).get()); + // exceed size + assertNull(parameters.get(3)); + } + +} \ No newline at end of file diff --git a/src/test/java/com/alibaba/qlexpress4/runtime/MemberResolverTest.java b/src/test/java/com/alibaba/qlexpress4/runtime/MemberResolverTest.java new file mode 100644 index 0000000..f661dc3 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/runtime/MemberResolverTest.java @@ -0,0 +1,62 @@ +package com.alibaba.qlexpress4.runtime; + +import org.junit.Test; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.function.Predicate; + +import static org.junit.Assert.*; + +/** + * Author: DQinYuan + */ +public class MemberResolverTest { + + public static class HelloMemberResolver { + + public HelloMemberResolver(Number i) { + + } + + public HelloMemberResolver(long i) { + + } + + public HelloMemberResolver(Long i, Runnable runnable) { + + } + + public HelloMemberResolver(long i, Runnable runnable) { + + } + } + + @Test + public void resolveConstructorTest() { + Constructor constructor = + MemberResolver.resolveConstructor(HelloMemberResolver.class, new Class[] {Integer.class}); + assertEquals(long.class, constructor.getParameterTypes()[0]); + + Constructor constructor1 = + MemberResolver.resolveConstructor(HelloMemberResolver.class, new Class[] {Long.class, QLambda.class}); + assertEquals(Runnable.class, constructor1.getParameterTypes()[1]); + } + + @Test + public void resolvePriorityTest() { + int result = MemberResolver.resolvePriority(new Class[] {boolean.class}, new Class[] {Boolean.class}); + assertEquals(MemberResolver.MatchPriority.UNBOX.priority, result); + + int result1 = MemberResolver.resolvePriority(new Class[] {}, new Class[] {}); + assertEquals(MemberResolver.MatchPriority.EQUAL.priority, result1); + } + + @Test + public void resolveStreamTest() { + IMethod result = MemberResolver + .resolveMethod(new ArrayList().stream().getClass(), "filter", new Class[] {Predicate.class}, false, false); + assertNotNull(result); + } +} \ No newline at end of file diff --git a/src/test/java/com/alibaba/qlexpress4/runtime/MockLeftValue.java b/src/test/java/com/alibaba/qlexpress4/runtime/MockLeftValue.java new file mode 100644 index 0000000..b5f5b18 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/runtime/MockLeftValue.java @@ -0,0 +1,24 @@ +package com.alibaba.qlexpress4.runtime; + +import com.alibaba.qlexpress4.runtime.operator.MockValue; + +public class MockLeftValue extends MockValue implements LeftValue { + public MockLeftValue(Object value, Class declaredClass) { + super(value, declaredClass); + } + + @Override + public Class getDefinedType() { + return declaredClass; + } + + @Override + public void setInner(Object newValue) { + this.value = newValue; + } + + @Override + public String getSymbolName() { + return null; + } +} diff --git a/src/test/java/com/alibaba/qlexpress4/runtime/instruction/CallInstructionTest.java b/src/test/java/com/alibaba/qlexpress4/runtime/instruction/CallInstructionTest.java new file mode 100644 index 0000000..65865fd --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/runtime/instruction/CallInstructionTest.java @@ -0,0 +1,39 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.exception.MockErrorReporter; +import com.alibaba.qlexpress4.runtime.QLambda; +import com.alibaba.qlexpress4.test.property.Child; +import com.alibaba.qlexpress4.test.property.ParentParameters; +import org.junit.Assert; +import org.junit.Test; + +/** + * Author: TaoKan + */ +public class CallInstructionTest { + /** + * child.getMethod1 (methodInstruction+callInstruction) + * getMethod1 + */ + @Test + public void case1() { + ErrorReporter errorReporter = new MockErrorReporter(); + GetMethodInstruction getMethodInstruction = new GetMethodInstruction(errorReporter, "getMethod1"); + MockQContextParent mockQContextParent = new MockQContextParent(true); + mockQContextParent.push(new Child()); + getMethodInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS); + Assert.assertTrue(mockQContextParent.getValue().get() instanceof QLambda); + QLambda qLambda = (QLambda)mockQContextParent.getValue().get(); + + CallInstruction callInstruction = new CallInstruction(errorReporter, 2); + ParentParameters parentParameters = new ParentParameters(); + parentParameters.push(qLambda); + parentParameters.push(1); + parentParameters.push(2); + mockQContextParent.setParameters(parentParameters); + callInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS); + Assert.assertEquals(mockQContextParent.getValue().get(), 3); + } +} diff --git a/src/test/java/com/alibaba/qlexpress4/runtime/instruction/GetFieldInstructionTest.java b/src/test/java/com/alibaba/qlexpress4/runtime/instruction/GetFieldInstructionTest.java new file mode 100644 index 0000000..e48d738 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/runtime/instruction/GetFieldInstructionTest.java @@ -0,0 +1,208 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.exception.MockErrorReporter; +import com.alibaba.qlexpress4.exception.QLRuntimeException; +import com.alibaba.qlexpress4.runtime.LeftValue; +import com.alibaba.qlexpress4.runtime.MetaClass; +import com.alibaba.qlexpress4.runtime.data.DataValue; +import com.alibaba.qlexpress4.test.property.Child; +import com.alibaba.qlexpress4.test.property.Parent; +import com.alibaba.qlexpress4.test.property.TestEnum; +import org.junit.Assert; +import org.junit.Test; + +/** + * Author: TaoKan + */ +public class GetFieldInstructionTest { + /** + * Parent::getStaticGet() setMethod is null and fieldNotAccess + */ + @Test + public void case1() { + ErrorReporter errorReporter = new MockErrorReporter(); + GetFieldInstruction getFieldInstruction = new GetFieldInstruction(errorReporter, "staticGet", false); + MockQContextParent mockQContextParent = new MockQContextParent(false); + mockQContextParent.push(new DataValue(new MetaClass(Parent.class))); + getFieldInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS); + Assert.assertEquals((mockQContextParent.getValue()).get(), "staticGet1"); + } + + /** + * public static case(Static field of Parent) + * Parent.staticSet + */ + @Test + public void case2() { + ErrorReporter errorReporter = new MockErrorReporter(); + GetFieldInstruction getFieldInstruction = new GetFieldInstruction(errorReporter, "staticSet", false); + MockQContextParent mockQContextParent = new MockQContextParent(false); + mockQContextParent.push(new DataValue(new MetaClass(Parent.class))); + getFieldInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS); + ((LeftValue)mockQContextParent.getValue()).set("staticSet1", errorReporter); + Assert.assertEquals((mockQContextParent.getValue()).get(), "staticSet1"); + } + + /** + * private static case(Static field of Parent) + * Parent.staticSetPrivate notSetAble + */ + @Test + public void case3() { + ErrorReporter errorReporter = new MockErrorReporter(); + GetFieldInstruction getFieldInstruction = new GetFieldInstruction(errorReporter, "staticSetPrivate", false); + MockQContextParent mockQContextParent = new MockQContextParent(false); + mockQContextParent.push(new DataValue(new MetaClass(Parent.class))); + try { + getFieldInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS); + } + catch (QLRuntimeException e) { + Assert.assertEquals("FIELD_NOT_FOUND", e.getErrorCode()); + return; + } + Assert.fail(); + } + + /** + * private static case(Static field of Parent) + * Parent.staticSetPrivate allowPrivateAccess + */ + @Test + public void case4() { + ErrorReporter errorReporter = new MockErrorReporter(); + GetFieldInstruction getFieldInstruction = new GetFieldInstruction(errorReporter, "staticSetPrivate", false); + MockQContextParent mockQContextParent = new MockQContextParent(true); + mockQContextParent.push(new DataValue(new MetaClass(Parent.class))); + getFieldInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS); + Assert.assertEquals("staticSetPrivate", (mockQContextParent.getValue()).get()); + } + + /** + * static case(Static method of Parent instance) + * parent.getStaticGet() allowPrivateAccess + */ + @Test + public void case5() { + ErrorReporter errorReporter = new MockErrorReporter(); + GetFieldInstruction getFieldInstruction = new GetFieldInstruction(errorReporter, "staticGet", false); + MockQContextParent mockQContextParent = new MockQContextParent(true); + mockQContextParent.push(new Parent()); + getFieldInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS); + Assert.assertEquals("staticGet1", (mockQContextParent.getValue()).get()); + } + + /** + * normal case(member method of Parent instance) + * parent.getAge() + */ + @Test + public void case6() { + ErrorReporter errorReporter = new MockErrorReporter(); + GetFieldInstruction getFieldInstruction = new GetFieldInstruction(errorReporter, "age", false); + MockQContextParent mockQContextParent = new MockQContextParent(false); + mockQContextParent.push(new Parent()); + getFieldInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS); + ((LeftValue)mockQContextParent.getValue()).set(35, errorReporter); + Assert.assertEquals((mockQContextParent.getValue()).get(), 35); + } + + /** + * normal case(member field of Parent instance) + * parent.name allowPrivateAccess + */ + @Test + public void case7() { + ErrorReporter errorReporter = new MockErrorReporter(); + GetFieldInstruction getFieldInstruction = new GetFieldInstruction(errorReporter, "name", false); + MockQContextParent mockQContextParent = new MockQContextParent(true); + mockQContextParent.push(new Parent()); + getFieldInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS); + ((LeftValue)mockQContextParent.getValue()).set("name1", errorReporter); + Assert.assertEquals((mockQContextParent.getValue()).get(), "name1"); + } + + /** + * error case(member field of Parent instance) + * parent.name not accessible + */ + @Test + public void case8() { + ErrorReporter errorReporter = new MockErrorReporter(); + GetFieldInstruction getFieldInstruction = new GetFieldInstruction(errorReporter, "name", false); + MockQContextParent mockQContextParent = new MockQContextParent(false); + mockQContextParent.push(new Parent()); + try { + getFieldInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS); + Assert.fail(); + } + catch (QLRuntimeException e) { + Assert.assertEquals("FIELD_NOT_FOUND", e.getErrorCode()); + } + } + + /** + * normal case(normal method of Parent,Child instance) + * child.getAge() instead of parent.getAge() + */ + @Test + public void case9() { + ErrorReporter errorReporter = new MockErrorReporter(); + GetFieldInstruction getFieldInstruction = new GetFieldInstruction(errorReporter, "age", false); + MockQContextParent mockQContextParent = new MockQContextParent(true); + mockQContextParent.push(new Child()); + getFieldInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS); + Assert.assertEquals((mockQContextParent.getValue()).get(), 11); + } + + /** + * normal case(member method of Parent,Child instance) + * parent.getBirth() public instead of child.birth(not allow) + */ + @Test + public void case10() { + ErrorReporter errorReporter = new MockErrorReporter(); + GetFieldInstruction getFieldInstruction = new GetFieldInstruction(errorReporter, "birth", false); + MockQContextParent mockQContextParent = new MockQContextParent(false); + mockQContextParent.push(new Child()); + getFieldInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS); + Assert.assertEquals((mockQContextParent.getValue()).get(), "2022-01-01"); + } + + /** + * error case(member method of Parent instance) + * parent.getMethod1() not found + */ + @Test + public void case11() { + ErrorReporter errorReporter = new MockErrorReporter(); + GetFieldInstruction getFieldInstruction = new GetFieldInstruction(errorReporter, "method1", false); + MockQContextParent mockQContextParent = new MockQContextParent(true); + mockQContextParent.push(new Child()); + try { + getFieldInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS); + Assert.fail(); + } + catch (QLRuntimeException e) { + Assert.assertEquals("FIELD_NOT_FOUND", e.getErrorCode()); + } + } + + /** + * enum attr get + * TestEnum.skt.getValue() + */ + @Test + public void case12() { + ErrorReporter errorReporter = new MockErrorReporter(); + GetFieldInstruction getFieldInstruction = new GetFieldInstruction(errorReporter, "SKT", false); + MockQContextParent mockQContextParent = new MockQContextParent(true); + mockQContextParent.push(new DataValue(new MetaClass(TestEnum.class))); + getFieldInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS); + GetFieldInstruction getFieldInstruction1 = new GetFieldInstruction(errorReporter, "value", false); + getFieldInstruction1.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS); + Assert.assertEquals((mockQContextParent.getValue()).get(), -1); + } + +} diff --git a/src/test/java/com/alibaba/qlexpress4/runtime/instruction/InterWithDefaultMethod.java b/src/test/java/com/alibaba/qlexpress4/runtime/instruction/InterWithDefaultMethod.java new file mode 100644 index 0000000..7e0d121 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/runtime/instruction/InterWithDefaultMethod.java @@ -0,0 +1,13 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +/** + * Author: DQinYuan + */ +public interface InterWithDefaultMethod { + + int getI(); + + default int returnI() { + return getI(); + } +} diff --git a/src/test/java/com/alibaba/qlexpress4/runtime/instruction/MethodInvokeInstructionTest.java b/src/test/java/com/alibaba/qlexpress4/runtime/instruction/MethodInvokeInstructionTest.java new file mode 100644 index 0000000..7b2cc49 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/runtime/instruction/MethodInvokeInstructionTest.java @@ -0,0 +1,265 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.exception.MockErrorReporter; +import com.alibaba.qlexpress4.exception.QLRuntimeException; +import com.alibaba.qlexpress4.test.property.*; +import org.junit.Assert; +import org.junit.Test; + +import java.math.BigDecimal; + +import static org.junit.Assert.*; + +/** + * Author: TaoKan + */ +public class MethodInvokeInstructionTest { + /** + * child.getMethod1 (methodInstruction+callInstruction) + * getMethod1 + */ + @Test + public void equalTypeTest() { + ErrorReporter errorReporter = new MockErrorReporter(); + MethodInvokeInstruction methodInvokeInstruction = + new MethodInvokeInstruction(errorReporter, "getMethod1", 2, false); + MockQContextParent mockQContextParent = new MockQContextParent(true); + ParentParameters parentParameters = new ParentParameters(); + parentParameters.push(new Child()); + parentParameters.push(1); + parentParameters.push(2); + mockQContextParent.setParameters(parentParameters); + methodInvokeInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS); + assertEquals(mockQContextParent.getValue().get(), 3); + } + + @Test + public void runnableTest() { + ErrorReporter errorReporter = new MockErrorReporter(); + MethodInvokeInstruction methodInvokeInstruction = new MethodInvokeInstruction(errorReporter, "run", 0, false); + MockQContextParent mockQContextParent = new MockQContextParent(true); + ParentParameters parentParameters = new ParentParameters(); + parentParameters.push((Runnable)() -> { + }); + mockQContextParent.setParameters(parentParameters); + methodInvokeInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS); + } + + @Test + public void defaultMethodTest() { + ErrorReporter errorReporter = new MockErrorReporter(); + MethodInvokeInstruction methodInvokeInstruction = + new MethodInvokeInstruction(errorReporter, "returnI", 0, false); + MockQContextParent mockQContextParent = new MockQContextParent(true); + ParentParameters parentParameters = new ParentParameters(); + parentParameters.push((InterWithDefaultMethod)() -> 9); + mockQContextParent.setParameters(parentParameters); + methodInvokeInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS); + assertEquals(9, mockQContextParent.getValue().get()); + } + + @Test + public void childMethodMatchTest() { + ErrorReporter errorReporter = new MockErrorReporter(); + MethodInvokeInstruction methodInvokeInstruction = + new MethodInvokeInstruction(errorReporter, "getMethod11", 2, false); + MockQContextParent mockQContextParent = new MockQContextParent(true); + ParentParameters parentParameters = new ParentParameters(); + parentParameters.push(new Child()); + parentParameters.push(1); + parentParameters.push(2); + mockQContextParent.setParameters(parentParameters); + methodInvokeInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS); + Assert.assertEquals(mockQContextParent.getValue().get(), 3L); + } + + @Test + public void parentMethodMatch() { + ErrorReporter errorReporter = new MockErrorReporter(); + MethodInvokeInstruction methodInvokeInstruction = + new MethodInvokeInstruction(errorReporter, "getMethod12", 2, false); + MockQContextParent mockQContextParent = new MockQContextParent(true); + ParentParameters parentParameters = new ParentParameters(); + parentParameters.push(new Child()); + parentParameters.push(1); + parentParameters.push(2); + mockQContextParent.setParameters(parentParameters); + methodInvokeInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS); + Assert.assertEquals(mockQContextParent.getValue().get(), 3L); + } + + @Test + public void convertTypeAssignedMatch() { + ErrorReporter errorReporter = new MockErrorReporter(); + MethodInvokeInstruction methodInvokeInstruction = + new MethodInvokeInstruction(errorReporter, "getMethod5", 1, false); + MockQContextParent mockQContextParent = new MockQContextParent(true); + ParentParameters parentParameters = new ParentParameters(); + parentParameters.push(new Child3()); + parentParameters.push(new Child3()); + mockQContextParent.setParameters(parentParameters); + methodInvokeInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS); + Assert.assertEquals(mockQContextParent.getValue().get(), 0); + } + + @Test + public void arrayParamTest() { + ErrorReporter errorReporter = new MockErrorReporter(); + MethodInvokeInstruction methodInvokeInstruction = + new MethodInvokeInstruction(errorReporter, "getMethod6", 1, false); + MockQContextParent mockQContextParent = new MockQContextParent(true); + ParentParameters parentParameters = new ParentParameters(); + parentParameters.push(new Child3()); + parentParameters.push(new Integer[] {5, 6}); + mockQContextParent.setParameters(parentParameters); + methodInvokeInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS); + Assert.assertEquals(mockQContextParent.getValue().get(), 10); + } + + @Test + public void primitiveParamTest() { + ErrorReporter errorReporter = new MockErrorReporter(); + MethodInvokeInstruction methodInvokeInstruction = + new MethodInvokeInstruction(errorReporter, "getMethod7", 1, false); + MockQContextParent mockQContextParent = new MockQContextParent(true); + ParentParameters parentParameters = new ParentParameters(); + parentParameters.push(new Child4()); + parentParameters.push(new Integer(5)); + mockQContextParent.setParameters(parentParameters); + methodInvokeInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS); + Assert.assertEquals(mockQContextParent.getValue().get(), 5); + } + + @Test + public void primitiveImplicitTest() { + ErrorReporter errorReporter = new MockErrorReporter(); + MethodInvokeInstruction methodInvokeInstruction = + new MethodInvokeInstruction(errorReporter, "getMethod8", 1, false); + MockQContextParent mockQContextParent = new MockQContextParent(true); + ParentParameters parentParameters = new ParentParameters(); + parentParameters.push(new Child5()); + parentParameters.push(5); + mockQContextParent.setParameters(parentParameters); + methodInvokeInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS); + Assert.assertEquals(mockQContextParent.getValue().get(), 5.0); + } + + @Test + public void bigIntegerImplicitTest() { + ErrorReporter errorReporter = new MockErrorReporter(); + MethodInvokeInstruction methodInvokeInstruction = + new MethodInvokeInstruction(errorReporter, "getMethod9", 1, false); + MockQContextParent mockQContextParent = new MockQContextParent(true); + ParentParameters parentParameters = new ParentParameters(); + parentParameters.push(new Child6()); + parentParameters.push(5); + mockQContextParent.setParameters(parentParameters); + methodInvokeInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS); + Assert.assertEquals(mockQContextParent.getValue().get(), 5); + } + + @Test + public void doubleMatchTest() { + ErrorReporter errorReporter = new MockErrorReporter(); + MethodInvokeInstruction methodInvokeInstruction = + new MethodInvokeInstruction(errorReporter, "getMethod10", 1, false); + MockQContextParent mockQContextParent = new MockQContextParent(true); + ParentParameters parentParameters = new ParentParameters(); + parentParameters.push(new Child6()); + parentParameters.push(5.0f); + mockQContextParent.setParameters(parentParameters); + methodInvokeInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS); + Assert.assertEquals(mockQContextParent.getValue().get(), new BigDecimal("5.0")); + } + + @Test + public void varArgTest() { + ErrorReporter errorReporter = new MockErrorReporter(); + MethodInvokeInstruction methodInvokeInstruction = + new MethodInvokeInstruction(errorReporter, "addField", 3, false); + MockQContextParent mockQContextParent = new MockQContextParent(true); + ParentParameters parentParameters = new ParentParameters(); + parentParameters.push(new Child9()); + parentParameters.push(5); + parentParameters.push("5.0"); + parentParameters.push("5.0"); + mockQContextParent.setParameters(parentParameters); + methodInvokeInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS); + Assert.assertEquals(mockQContextParent.getValue().get(), "1"); + } + + @Test + public void varArgTest2() { + ErrorReporter errorReporter = new MockErrorReporter(); + MethodInvokeInstruction methodInvokeInstruction = + new MethodInvokeInstruction(errorReporter, "addField1", 3, false); + MockQContextParent mockQContextParent = new MockQContextParent(true); + ParentParameters parentParameters = new ParentParameters(); + parentParameters.push(new Child9()); + parentParameters.push(5); + parentParameters.push("5.0"); + parentParameters.push("5.0"); + mockQContextParent.setParameters(parentParameters); + methodInvokeInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS); + Assert.assertEquals(mockQContextParent.getValue().get(), "1"); + } + + @Test + public void varArgTest3() { + ErrorReporter errorReporter = new MockErrorReporter(); + MethodInvokeInstruction methodInvokeInstruction = + new MethodInvokeInstruction(errorReporter, "addField2", 3, false); + MockQContextParent mockQContextParent = new MockQContextParent(true); + ParentParameters parentParameters = new ParentParameters(); + parentParameters.push(new Child9()); + parentParameters.push(5); + parentParameters.push("5.0"); + parentParameters.push("5.0"); + mockQContextParent.setParameters(parentParameters); + methodInvokeInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS); + Assert.assertEquals(mockQContextParent.getValue().get(), "1"); + } + + @Test + public void varArgNotMatchTest() { + ErrorReporter errorReporter = new MockErrorReporter(); + MethodInvokeInstruction methodInvokeInstruction = + new MethodInvokeInstruction(errorReporter, "addField3", 3, false); + MockQContextParent mockQContextParent = new MockQContextParent(true); + ParentParameters parentParameters = new ParentParameters(); + parentParameters.push(new Child9()); + parentParameters.push(5); + parentParameters.push("asd"); + parentParameters.push("sss"); + mockQContextParent.setParameters(parentParameters); + try { + methodInvokeInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS); + Assert.fail(); + } + catch (QLRuntimeException e) { + Assert.assertEquals("METHOD_NOT_FOUND", e.getErrorCode()); + } + } + + @Test + public void varArgNotMatchTest2() { + ErrorReporter errorReporter = new MockErrorReporter(); + MethodInvokeInstruction methodInvokeInstruction = + new MethodInvokeInstruction(errorReporter, "addField", 3, false); + MockQContextParent mockQContextParent = new MockQContextParent(true); + ParentParameters parentParameters = new ParentParameters(); + parentParameters.push(new Child9()); + parentParameters.push(5); + parentParameters.push(1); + parentParameters.push(1); + mockQContextParent.setParameters(parentParameters); + try { + methodInvokeInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS); + } + catch (QLRuntimeException e) { + Assert.assertEquals("METHOD_NOT_FOUND", e.getErrorCode()); + } + } +} diff --git a/src/test/java/com/alibaba/qlexpress4/runtime/instruction/MockCastParameters.java b/src/test/java/com/alibaba/qlexpress4/runtime/instruction/MockCastParameters.java new file mode 100644 index 0000000..ed84ad0 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/runtime/instruction/MockCastParameters.java @@ -0,0 +1,35 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import com.alibaba.qlexpress4.runtime.Parameters; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.data.DataValue; + +/** + * Author: TaoKan + */ +public class MockCastParameters implements Parameters { + private Class aClass; + + private Object obj; + + public MockCastParameters(Class aClass, Object obj) { + this.aClass = aClass; + this.obj = obj; + } + + @Override + public Value get(int i) { + if (i == 0) { + return new DataValue(aClass); + } + if (i == 1) { + return new DataValue(obj); + } + return null; + } + + @Override + public int size() { + return 0; + } +} \ No newline at end of file diff --git a/src/test/java/com/alibaba/qlexpress4/runtime/instruction/MockParametersParentClass.java b/src/test/java/com/alibaba/qlexpress4/runtime/instruction/MockParametersParentClass.java new file mode 100644 index 0000000..9f85276 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/runtime/instruction/MockParametersParentClass.java @@ -0,0 +1,21 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import com.alibaba.qlexpress4.runtime.Parameters; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.test.property.ParentClass; + +/** + * Author: TaoKan + */ +public class MockParametersParentClass implements Parameters { + @Override + public Value get(int i) { + ParentClass parent = new ParentClass(); + return parent; + } + + @Override + public int size() { + return 0; + } +} diff --git a/src/test/java/com/alibaba/qlexpress4/runtime/instruction/MockQContextParent.java b/src/test/java/com/alibaba/qlexpress4/runtime/instruction/MockQContextParent.java new file mode 100644 index 0000000..3914662 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/runtime/instruction/MockQContextParent.java @@ -0,0 +1,129 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import 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 com.alibaba.qlexpress4.security.QLSecurityStrategy; + +import java.util.Collections; +import java.util.Map; + +/** + * Author: TaoKan + */ +public class MockQContextParent implements QContext { + private Value value; + + private Parameters parameters; + + private final boolean allowPrivateAccess; + + private final ReflectLoader reflectLoader; + + public MockQContextParent(boolean allowPrivateAccess) { + this.allowPrivateAccess = allowPrivateAccess; + this.reflectLoader = new ReflectLoader(QLSecurityStrategy.open(), allowPrivateAccess); + } + + public Value getValue() { + return value; + } + + public void setParameters(Parameters parameters) { + this.parameters = parameters; + } + + @Override + public LeftValue getSymbol(String varName) { + return null; + } + + @Override + public Value getSymbolValue(String varName) { + return null; + } + + @Override + public void defineLocalSymbol(String varName, Class varClz, Object value) { + + } + + @Override + public void defineFunction(String functionName, CustomFunction function) { + + } + + @Override + public CustomFunction getFunction(String functionName) { + return null; + } + + @Override + public Map getFunctionTable() { + return Collections.emptyMap(); + } + + @Override + public void push(Value value) { + this.value = value; + } + + public void pushParameter(Parameters parameters) { + this.parameters = parameters; + } + + @Override + public Parameters pop(int number) { + return this.parameters; + } + + @Override + public Value pop() { + return this.value; + } + + @Override + public Value peek() { + return this.value; + } + + @Override + public QScope getParent() { + return null; + } + + @Override + public QScope newScope() { + return null; + } + + @Override + public long scriptStartTimeStamp() { + return 0; + } + + @Override + public Map attachment() { + return null; + } + + @Override + public ReflectLoader getReflectLoader() { + return reflectLoader; + } + + @Override + public QTraces getTraces() { + return null; + } + + @Override + public QScope getCurrentScope() { + return this; + } + + @Override + public void closeScope() { + } +} diff --git a/src/test/java/com/alibaba/qlexpress4/runtime/instruction/NewInstanceInstructionTest.java b/src/test/java/com/alibaba/qlexpress4/runtime/instruction/NewInstanceInstructionTest.java new file mode 100644 index 0000000..d2c4b01 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/runtime/instruction/NewInstanceInstructionTest.java @@ -0,0 +1,151 @@ +package com.alibaba.qlexpress4.runtime.instruction; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.exception.MockErrorReporter; +import com.alibaba.qlexpress4.exception.QLRuntimeException; +import com.alibaba.qlexpress4.test.property.*; +import org.junit.Assert; +import org.junit.Test; + +/** + * Author: TaoKan + */ +public class NewInstanceInstructionTest { + @Test + public void newInstructionTest() { + ErrorReporter errorReporter = new MockErrorReporter(); + NewInstanceInstruction newInstructionForParent0 = new NewInstanceInstruction(errorReporter, Parent.class, 0); + MockQContextParent mockQContextParent = new MockQContextParent(false); + newInstructionForParent0.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS); + Object s = mockQContextParent.getValue().get(); + Assert.assertTrue(s instanceof Parent); + + NewInstanceInstruction newInstructionForParentWithAge = + new NewInstanceInstruction(errorReporter, Parent.class, 1); + MockQContextParent mockQContextParent1 = new MockQContextParent(false); + ParentParameters parentParameters = new ParentParameters(); + parentParameters.push(6); + mockQContextParent1.pushParameter(parentParameters); + newInstructionForParentWithAge.execute(mockQContextParent1, QLOptions.DEFAULT_OPTIONS); + Object result = mockQContextParent1.getValue().get(); + Assert.assertTrue(result instanceof Parent && ((Parent)result).getAge() == 6); + } + + @Test + public void constructorNotFoundTest() { + ErrorReporter errorReporter = new MockErrorReporter(); + NewInstanceInstruction newInstruction = new NewInstanceInstruction(errorReporter, Child.class, 2); + MockQContextParent mockQContextParent = new MockQContextParent(false); + ParentParameters parentParameters = new ParentParameters(); + parentParameters.push(2); + parentParameters.push(3); + mockQContextParent.pushParameter(parentParameters); + try { + newInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS); + Assert.fail(); + } + catch (QLRuntimeException e) { + Assert.assertEquals("NO_SUITABLE_CONSTRUCTOR", e.getErrorCode()); + } + } + + @Test + public void constructorConvertMatchTest() { + ErrorReporter errorReporter = new MockErrorReporter(); + NewInstanceInstruction newInstruction = new NewInstanceInstruction(errorReporter, Child1.class, 2); + MockQContextParent mockQContextParent = new MockQContextParent(false); + ParentParameters parentParameters = new ParentParameters(); + parentParameters.push(2); + parentParameters.push(3); + mockQContextParent.pushParameter(parentParameters); + newInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS); + Assert.assertTrue(mockQContextParent.getValue().get() instanceof Child1); + } + + @Test + public void constructorConvertAssignedMatch() { + ErrorReporter errorReporter = new MockErrorReporter(); + NewInstanceInstruction newInstruction = new NewInstanceInstruction(errorReporter, Child3.class, 1); + MockQContextParent mockQContextParent = new MockQContextParent(false); + ParentParameters parentParameters = new ParentParameters(); + parentParameters.push(new Child3()); + mockQContextParent.pushParameter(parentParameters); + newInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS); + Assert.assertTrue(mockQContextParent.getValue().get() instanceof Child3); + } + + @Test + public void arrayParamTest() { + ErrorReporter errorReporter = new MockErrorReporter(); + NewInstanceInstruction newInstruction = new NewInstanceInstruction(errorReporter, Child3.class, 1); + MockQContextParent mockQContextParent = new MockQContextParent(false); + ParentParameters parentParameters = new ParentParameters(); + parentParameters.push(new Integer[] {5, 6}); + mockQContextParent.pushParameter(parentParameters); + newInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS); + Assert.assertTrue(mockQContextParent.getValue().get() instanceof Child3); + } + + @Test + public void primitiveParamTest() { + ErrorReporter errorReporter = new MockErrorReporter(); + NewInstanceInstruction newInstruction = new NewInstanceInstruction(errorReporter, Child4.class, 1); + MockQContextParent mockQContextParent = new MockQContextParent(false); + ParentParameters parentParameters = new ParentParameters(); + parentParameters.push(5); + mockQContextParent.pushParameter(parentParameters); + newInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS); + Assert.assertTrue(mockQContextParent.getValue().get() instanceof Child4); + } + + @Test + public void primitiveImplicitTest() { + ErrorReporter errorReporter = new MockErrorReporter(); + NewInstanceInstruction newInstruction = new NewInstanceInstruction(errorReporter, Child5.class, 1); + MockQContextParent mockQContextParent = new MockQContextParent(false); + ParentParameters parentParameters = new ParentParameters(); + parentParameters.push(5); + mockQContextParent.pushParameter(parentParameters); + newInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS); + Assert.assertTrue(mockQContextParent.getValue().get() instanceof Child5); + } + + @Test + public void bigIntegerImplicitTest() { + ErrorReporter errorReporter = new MockErrorReporter(); + NewInstanceInstruction newInstruction = new NewInstanceInstruction(errorReporter, Child6.class, 1); + MockQContextParent mockQContextParent = new MockQContextParent(false); + ParentParameters parentParameters = new ParentParameters(); + parentParameters.push(5); + mockQContextParent.pushParameter(parentParameters); + newInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS); + Assert.assertTrue(mockQContextParent.getValue().get() instanceof Child6); + } + + @Test + public void numberConstructorMatchTest() { + ErrorReporter errorReporter = new MockErrorReporter(); + NewInstanceInstruction newInstruction = new NewInstanceInstruction(errorReporter, NumberConstructor.class, 1); + MockQContextParent mockQContextParent = new MockQContextParent(false); + ParentParameters parentParameters = new ParentParameters(); + parentParameters.push(5.0); + mockQContextParent.pushParameter(parentParameters); + newInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS); + NumberConstructor numberConstructor = (NumberConstructor)mockQContextParent.getValue().get(); + Assert.assertEquals(0, numberConstructor.getFlag()); + } + + @Test + public void varArgTest() { + ErrorReporter errorReporter = new MockErrorReporter(); + NewInstanceInstruction newInstruction = new NewInstanceInstruction(errorReporter, Child9.class, 3); + MockQContextParent mockQContextParent = new MockQContextParent(false); + ParentParameters parentParameters = new ParentParameters(); + parentParameters.push(5); + parentParameters.push("5.0"); + parentParameters.push("5.0"); + mockQContextParent.pushParameter(parentParameters); + newInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS); + } +} diff --git a/src/test/java/com/alibaba/qlexpress4/runtime/operator/MockValue.java b/src/test/java/com/alibaba/qlexpress4/runtime/operator/MockValue.java new file mode 100644 index 0000000..13c2a02 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/runtime/operator/MockValue.java @@ -0,0 +1,19 @@ +package com.alibaba.qlexpress4.runtime.operator; + +import com.alibaba.qlexpress4.runtime.Value; + +public class MockValue implements Value { + protected Object value; + + protected Class declaredClass; + + public MockValue(Object value, Class declaredClass) { + this.value = value; + this.declaredClass = declaredClass; + } + + @Override + public Object get() { + return value; + } +} diff --git a/src/test/java/com/alibaba/qlexpress4/runtime/operator/unary/MinusMinusPrefixUnaryOperatorTest.java b/src/test/java/com/alibaba/qlexpress4/runtime/operator/unary/MinusMinusPrefixUnaryOperatorTest.java new file mode 100644 index 0000000..f090430 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/runtime/operator/unary/MinusMinusPrefixUnaryOperatorTest.java @@ -0,0 +1,26 @@ +package com.alibaba.qlexpress4.runtime.operator.unary; + +import java.math.BigInteger; + +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.exception.MockErrorReporter; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.MockValue; + +import junit.framework.TestCase; +import org.junit.Assert; + +public class MinusMinusPrefixUnaryOperatorTest extends TestCase { + public void testExecute() { + ErrorReporter errorReporter = new MockErrorReporter(); + MinusMinusPrefixUnaryOperator minusMinusPrefixUnaryOperator = MinusMinusPrefixUnaryOperator.getInstance(); + + Value value = new MockValue(10L, null); + Object result = minusMinusPrefixUnaryOperator.execute(value, errorReporter); + Assert.assertEquals(10L, result); + + value = new MockValue(new BigInteger("10"), BigInteger.class); + BigInteger bigIntegerResult = (BigInteger)minusMinusPrefixUnaryOperator.execute(value, errorReporter); + Assert.assertEquals(10L, bigIntegerResult.intValue()); + } +} \ No newline at end of file diff --git a/src/test/java/com/alibaba/qlexpress4/runtime/operator/unary/MinusMinusSuffixUnaryOperatorTest.java b/src/test/java/com/alibaba/qlexpress4/runtime/operator/unary/MinusMinusSuffixUnaryOperatorTest.java new file mode 100644 index 0000000..fd5e31e --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/runtime/operator/unary/MinusMinusSuffixUnaryOperatorTest.java @@ -0,0 +1,27 @@ +package com.alibaba.qlexpress4.runtime.operator.unary; + +import java.math.BigInteger; + +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.exception.MockErrorReporter; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.operator.MockValue; + +import junit.framework.TestCase; +import org.junit.Assert; + +public class MinusMinusSuffixUnaryOperatorTest extends TestCase { + public void testExecute() { + ErrorReporter errorReporter = new MockErrorReporter(); + MinusMinusSuffixUnaryOperator minusMinusSuffixUnaryOperator = MinusMinusSuffixUnaryOperator.getInstance(); + + Value value = new MockValue(10L, null); + Object result = minusMinusSuffixUnaryOperator.execute(value, errorReporter); + Assert.assertEquals(9L, result); + + BigInteger bigInteger = BigInteger.valueOf(10L); + value = new MockValue(bigInteger, BigInteger.class); + BigInteger bigIntegerResult = (BigInteger)minusMinusSuffixUnaryOperator.execute(value, errorReporter); + Assert.assertEquals(9L, bigIntegerResult.longValue()); + } +} \ No newline at end of file diff --git a/src/test/java/com/alibaba/qlexpress4/spring/HelloService.java b/src/test/java/com/alibaba/qlexpress4/spring/HelloService.java new file mode 100644 index 0000000..dcc0252 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/spring/HelloService.java @@ -0,0 +1,18 @@ +package com.alibaba.qlexpress4.spring; + +import org.springframework.stereotype.Service; + +/** + * Spring Bean example service class + */ +@Service +public class HelloService { + + /** + * Hello method that returns a greeting string + * @return greeting string + */ + public String hello(String name) { + return "Hello, " + name + "!"; + } +} \ No newline at end of file diff --git a/src/test/java/com/alibaba/qlexpress4/spring/QLExecuteService.java b/src/test/java/com/alibaba/qlexpress4/spring/QLExecuteService.java new file mode 100644 index 0000000..0338727 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/spring/QLExecuteService.java @@ -0,0 +1,26 @@ +package com.alibaba.qlexpress4.spring; + +import java.util.Map; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Service; + +import com.alibaba.qlexpress4.Express4Runner; +import com.alibaba.qlexpress4.InitOptions; +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.security.QLSecurityStrategy; + +@Service +public class QLExecuteService { + + private final Express4Runner runner = + new Express4Runner(InitOptions.builder().securityStrategy(QLSecurityStrategy.open()).build()); + + @Autowired + private ApplicationContext applicationContext; + + public Object execute(String script, Map context) { + QLSpringContext springContext = new QLSpringContext(context, applicationContext); + return runner.execute(script, springContext, QLOptions.DEFAULT_OPTIONS).getResult(); + } +} \ No newline at end of file diff --git a/src/test/java/com/alibaba/qlexpress4/spring/QLSpringContext.java b/src/test/java/com/alibaba/qlexpress4/spring/QLSpringContext.java new file mode 100644 index 0000000..f1b41f0 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/spring/QLSpringContext.java @@ -0,0 +1,35 @@ +package com.alibaba.qlexpress4.spring; + +import java.util.Map; + +import org.springframework.context.ApplicationContext; + +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.context.ExpressContext; +import com.alibaba.qlexpress4.runtime.data.DataValue; +import com.alibaba.qlexpress4.runtime.data.MapItemValue; + +public class QLSpringContext implements ExpressContext { + + private final Map context; + + private final ApplicationContext applicationContext; + + public QLSpringContext(Map context, ApplicationContext applicationContext) { + this.context = context; + this.applicationContext = applicationContext; + } + + @Override + public Value get(Map attachments, String variableName) { + Object value = context.get(variableName); + if (value != null) { + return new MapItemValue(context, variableName); + } + Object bean = applicationContext.getBean(variableName); + if (bean != null) { + return new DataValue(bean); + } + return new MapItemValue(context, value); + } +} diff --git a/src/test/java/com/alibaba/qlexpress4/spring/SpringDemoTest.java b/src/test/java/com/alibaba/qlexpress4/spring/SpringDemoTest.java new file mode 100644 index 0000000..6102dd3 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/spring/SpringDemoTest.java @@ -0,0 +1,30 @@ +package com.alibaba.qlexpress4.spring; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import java.util.HashMap; +import java.util.Map; + +/** + * HelloService unit test class + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = SpringTestConfig.class) +public class SpringDemoTest { + + @Autowired + private QLExecuteService qlExecuteService; + + @Test + public void qlExecuteWithSpringContextTest() { + Map context = new HashMap<>(); + context.put("name", "Wang"); + String result = (String)qlExecuteService.execute("helloService.hello(name)", context); + Assert.assertEquals("Hello, Wang!", result); + } +} \ No newline at end of file diff --git a/src/test/java/com/alibaba/qlexpress4/spring/SpringTestConfig.java b/src/test/java/com/alibaba/qlexpress4/spring/SpringTestConfig.java new file mode 100644 index 0000000..9637b68 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/spring/SpringTestConfig.java @@ -0,0 +1,13 @@ +package com.alibaba.qlexpress4.spring; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +/** + * Spring test configuration class + */ +@Configuration +@ComponentScan(basePackages = "com.alibaba.qlexpress4.spring") +public class SpringTestConfig { + // Configuration class, enables component scanning +} \ No newline at end of file diff --git a/src/test/java/com/alibaba/qlexpress4/test/annotation/QL4AliasTest.java b/src/test/java/com/alibaba/qlexpress4/test/annotation/QL4AliasTest.java new file mode 100644 index 0000000..9c2bccc --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/test/annotation/QL4AliasTest.java @@ -0,0 +1,56 @@ +package com.alibaba.qlexpress4.test.annotation; + +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.exception.ErrorReporter; +import com.alibaba.qlexpress4.exception.MockErrorReporter; +import com.alibaba.qlexpress4.runtime.MetaClass; +import com.alibaba.qlexpress4.runtime.data.DataValue; +import com.alibaba.qlexpress4.runtime.instruction.GetFieldInstruction; +import com.alibaba.qlexpress4.runtime.instruction.MethodInvokeInstruction; +import com.alibaba.qlexpress4.runtime.instruction.MockQContextParent; +import com.alibaba.qlexpress4.test.property.Child7; +import com.alibaba.qlexpress4.test.property.ParentParameters; +import org.junit.Assert; +import org.junit.Test; + +/** + * Author: TaoKan + */ +public class QL4AliasTest { + + @Test + public void classFieldTest() + throws Exception { + ErrorReporter errorReporter = new MockErrorReporter(); + GetFieldInstruction getFieldInstruction = new GetFieldInstruction(errorReporter, "测试静态字段", false); + MockQContextParent mockQContextParent = new MockQContextParent(false); + mockQContextParent.push(new DataValue(new MetaClass(Child7.class))); + getFieldInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS); + Assert.assertEquals((mockQContextParent.getValue()).get(), 8); + } + + @Test + public void staticMethodTest() { + ErrorReporter errorReporter = new MockErrorReporter(); + MethodInvokeInstruction methodInvokeInstruction = + new MethodInvokeInstruction(errorReporter, "测试静态方法", 0, false); + MockQContextParent mockQContextParent = new MockQContextParent(true); + ParentParameters parentParameters = new ParentParameters(); + parentParameters.push(new MetaClass(Child7.class)); + mockQContextParent.setParameters(parentParameters); + methodInvokeInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS); + Assert.assertEquals(mockQContextParent.getValue().get(), 11); + } + + @Test + public void memberMethodTest() { + ErrorReporter errorReporter = new MockErrorReporter(); + MethodInvokeInstruction methodInvokeInstruction = new MethodInvokeInstruction(errorReporter, "测试方法", 0, false); + MockQContextParent mockQContextParent = new MockQContextParent(true); + ParentParameters parentParameters = new ParentParameters(); + parentParameters.push(new Child7()); + mockQContextParent.setParameters(parentParameters); + methodInvokeInstruction.execute(mockQContextParent, QLOptions.DEFAULT_OPTIONS); + Assert.assertEquals(mockQContextParent.getValue().get(), 10); + } +} diff --git a/src/test/java/com/alibaba/qlexpress4/test/constructor/HelloChild.java b/src/test/java/com/alibaba/qlexpress4/test/constructor/HelloChild.java new file mode 100644 index 0000000..0e91e51 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/test/constructor/HelloChild.java @@ -0,0 +1,7 @@ +package com.alibaba.qlexpress4.test.constructor; + +/** + * Author: DQinYuan + */ +public class HelloChild extends HelloParent { +} diff --git a/src/test/java/com/alibaba/qlexpress4/test/constructor/HelloConstructor.java b/src/test/java/com/alibaba/qlexpress4/test/constructor/HelloConstructor.java new file mode 100644 index 0000000..e254321 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/test/constructor/HelloConstructor.java @@ -0,0 +1,35 @@ +package com.alibaba.qlexpress4.test.constructor; + +import com.alibaba.qlexpress4.runtime.QLambda; + +/** + * Author: DQinYuan + */ +public class HelloConstructor { + + public int flag; + + public HelloConstructor(HelloParent helloParent) { + this.flag = 0; + } + + public HelloConstructor(HelloChild child) { + this.flag = 1; + } + + public HelloConstructor(String... s) { + this.flag = 2; + } + + public HelloConstructor(String s) { + this.flag = 3; + } + + public HelloConstructor(HelloChild child, Runnable r) { + this.flag = 4; + } + + public HelloConstructor(HelloParent helloParent, QLambda q) { + this.flag = 5; + } +} diff --git a/src/test/java/com/alibaba/qlexpress4/test/constructor/HelloParent.java b/src/test/java/com/alibaba/qlexpress4/test/constructor/HelloParent.java new file mode 100644 index 0000000..30d4b63 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/test/constructor/HelloParent.java @@ -0,0 +1,7 @@ +package com.alibaba.qlexpress4.test.constructor; + +/** + * Author: DQinYuan + */ +public class HelloParent { +} diff --git a/src/test/java/com/alibaba/qlexpress4/test/function/HelloFunction.java b/src/test/java/com/alibaba/qlexpress4/test/function/HelloFunction.java new file mode 100644 index 0000000..df123aa --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/test/function/HelloFunction.java @@ -0,0 +1,14 @@ +package com.alibaba.qlexpress4.test.function; + +import com.alibaba.qlexpress4.runtime.Parameters; +import com.alibaba.qlexpress4.runtime.QContext; +import com.alibaba.qlexpress4.runtime.function.CustomFunction; + +public class HelloFunction implements CustomFunction { + @Override + public Object call(QContext qContext, Parameters parameters) + throws Throwable { + String tenant = (String)qContext.attachment().get("tenant"); + return "hello," + tenant; + } +} diff --git a/src/test/java/com/alibaba/qlexpress4/test/issue/Issue318Test.java b/src/test/java/com/alibaba/qlexpress4/test/issue/Issue318Test.java new file mode 100644 index 0000000..ca13de7 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/test/issue/Issue318Test.java @@ -0,0 +1,54 @@ +package com.alibaba.qlexpress4.test.issue; + +import java.util.HashMap; +import java.util.Map; + +import com.alibaba.qlexpress4.Express4Runner; +import com.alibaba.qlexpress4.InitOptions; +import com.alibaba.qlexpress4.QLOptions; +import com.alibaba.qlexpress4.security.QLSecurityStrategy; + +import org.junit.Assert; +import org.junit.Test; + +/** + * @author 冰够 + */ +public class Issue318Test { + @Test + public void test() + throws Exception { + Express4Runner runner = + new Express4Runner(InitOptions.builder().securityStrategy(QLSecurityStrategy.open()).build()); + Map context = new HashMap<>(); + + Student student = new Student(); + student.name = "张三"; + student.alias = "zhangsan"; + context.put("student", student); + Object result = runner.execute("student.name == \"张三\"", context, QLOptions.DEFAULT_OPTIONS).getResult(); + Assert.assertTrue((Boolean)result); + + result = runner.execute("student.alias == \"zhangsan\"", context, QLOptions.DEFAULT_OPTIONS).getResult(); + Assert.assertTrue((Boolean)result); + } + + public static class Student { + /** + * 默认不支持属性直接访问,必须要有getter方法 + * + * @return + */ + public String name; + + public String alias; + + public String getName() { + return name; + } + + public String getAlias() { + return alias; + } + } +} \ No newline at end of file diff --git a/src/test/java/com/alibaba/qlexpress4/test/lambda/UserFunctionalInterface.java b/src/test/java/com/alibaba/qlexpress4/test/lambda/UserFunctionalInterface.java new file mode 100644 index 0000000..52b221c --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/test/lambda/UserFunctionalInterface.java @@ -0,0 +1,10 @@ +package com.alibaba.qlexpress4.test.lambda; + +/** + * Author: DQinYuan + */ +public interface UserFunctionalInterface { + + int lala(int a, int b); + +} diff --git a/src/test/java/com/alibaba/qlexpress4/test/method/InterWithDefault.java b/src/test/java/com/alibaba/qlexpress4/test/method/InterWithDefault.java new file mode 100644 index 0000000..9f12d93 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/test/method/InterWithDefault.java @@ -0,0 +1,12 @@ +package com.alibaba.qlexpress4.test.method; + +/** + * Author: DQinYuan + */ +public interface InterWithDefault { + + default int get100() { + return 100; + } + +} diff --git a/src/test/java/com/alibaba/qlexpress4/test/method/TestChild.java b/src/test/java/com/alibaba/qlexpress4/test/method/TestChild.java new file mode 100644 index 0000000..ec9e443 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/test/method/TestChild.java @@ -0,0 +1,15 @@ +package com.alibaba.qlexpress4.test.method; + +/** + * Author: DQinYuan + */ +public class TestChild extends TestParent implements InterWithDefault { + + public int get10() { + return 10; + } + + public int get10(String... s) { + return 11; + } +} diff --git a/src/test/java/com/alibaba/qlexpress4/test/method/TestParent.java b/src/test/java/com/alibaba/qlexpress4/test/method/TestParent.java new file mode 100644 index 0000000..15ddb46 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/test/method/TestParent.java @@ -0,0 +1,12 @@ +package com.alibaba.qlexpress4.test.method; + +/** + * Author: DQinYuan + */ +public class TestParent { + + public int get1() { + return 1; + } + +} diff --git a/src/test/java/com/alibaba/qlexpress4/test/property/Child.java b/src/test/java/com/alibaba/qlexpress4/test/property/Child.java new file mode 100644 index 0000000..dd51e87 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/test/property/Child.java @@ -0,0 +1,73 @@ +package com.alibaba.qlexpress4.test.property; + +/** + * Author: TaoKan + */ +public class Child extends Parent { + public String work = "childWork"; + + public static String staticWork = "childStaticWork"; + + private Boolean booValue = false; + + private int age = 11; + + private String birth = "2021-02-02"; + + private final long result; + + public Child() { + this.result = 0L; + } + + public Child(boolean a, int b) { + this.result = 1; + } + + public static String getStaticGet() { + return "5"; + } + + public static String getStaticGetParam(Integer a, Integer b) { + return "5"; + } + + public String getWork() { + return "child"; + } + + public Boolean getBooValue() { + return true; + } + + public void setBooValue(Boolean booValue) { + this.booValue = booValue; + } + + private int getMethod1(int a, int b) { + return a + b; + } + + private long getMethod11(long a, int b) { + return a + b; + } + + private long getMethod12(Boolean a, int b) { + return b; + } + + @Override + public int getAge() { + return age; + } + + @Override + public void setAge(int age) { + this.age = age; + } + + @Override + public Child getParentOwn() { + return new Child(); + } +} diff --git a/src/test/java/com/alibaba/qlexpress4/test/property/Child1.java b/src/test/java/com/alibaba/qlexpress4/test/property/Child1.java new file mode 100644 index 0000000..2953a9e --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/test/property/Child1.java @@ -0,0 +1,28 @@ +package com.alibaba.qlexpress4.test.property; + +/** + * Author: TaoKan + */ +public class Child1 extends Parent { + private final long result; + + public Child1() { + this.result = 0L; + } + + public Child1(boolean t) { + this.result = 0L; + } + + public Child1(long a, int b) { + this.result = a + b + 1; + } + + public boolean getMethod3(boolean t) { + return t; + } + + public int getMethod4(Object s, boolean t) { + return 2; + } +} diff --git a/src/test/java/com/alibaba/qlexpress4/test/property/Child10.java b/src/test/java/com/alibaba/qlexpress4/test/property/Child10.java new file mode 100644 index 0000000..d62fa74 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/test/property/Child10.java @@ -0,0 +1,30 @@ +package com.alibaba.qlexpress4.test.property; + +/** + * Author: TaoKan + */ +public class Child10 { + public void setAA(int a) { + System.out.println("setAA"); + } + + public boolean setA(int a) { + return true; + } + + public Integer setAAA(Integer... a) { + int s = 0; + for (int i : a) { + s += i; + } + return s; + } + + public Integer setAAAA(String a, Integer... b) { + int s = 0; + for (int i : b) { + s += i; + } + return s; + } +} diff --git a/src/test/java/com/alibaba/qlexpress4/test/property/Child2.java b/src/test/java/com/alibaba/qlexpress4/test/property/Child2.java new file mode 100644 index 0000000..49c102b --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/test/property/Child2.java @@ -0,0 +1,28 @@ +package com.alibaba.qlexpress4.test.property; + +/** + * Author: TaoKan + */ +public class Child2 extends Parent { + private final long result; + + public Child2() { + this.result = 0L; + } + + public Child2(boolean t) { + this.result = 0L; + } + + public Child2(Object a, boolean b) { + this.result = 1; + } + + public boolean getMethod3(boolean t) { + return t; + } + + public int getMethod4(Object s, Boolean t) { + return 2; + } +} diff --git a/src/test/java/com/alibaba/qlexpress4/test/property/Child3.java b/src/test/java/com/alibaba/qlexpress4/test/property/Child3.java new file mode 100644 index 0000000..289edd8 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/test/property/Child3.java @@ -0,0 +1,29 @@ +package com.alibaba.qlexpress4.test.property; + +/** + * Author: TaoKan + */ +public class Child3 extends Parent { + private final long result; + + public Child3() { + this.result = 0L; + } + + public Child3(Parent t) { + this.result = 0L; + } + + public Child3(Object[] t) { + this.result = 0L; + } + + public int getMethod5(Parent t) { + return t.getAge(); + } + + public int getMethod6(Object[] obj) { + return 10; + } + +} diff --git a/src/test/java/com/alibaba/qlexpress4/test/property/Child4.java b/src/test/java/com/alibaba/qlexpress4/test/property/Child4.java new file mode 100644 index 0000000..fba08c4 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/test/property/Child4.java @@ -0,0 +1,21 @@ +package com.alibaba.qlexpress4.test.property; + +/** + * Author: TaoKan + */ +public class Child4 extends Parent { + private final long result; + + public Child4() { + this.result = 0L; + } + + public Child4(int t) { + this.result = 0L; + } + + public int getMethod7(int t) { + return t; + } + +} diff --git a/src/test/java/com/alibaba/qlexpress4/test/property/Child5.java b/src/test/java/com/alibaba/qlexpress4/test/property/Child5.java new file mode 100644 index 0000000..dec2df9 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/test/property/Child5.java @@ -0,0 +1,21 @@ +package com.alibaba.qlexpress4.test.property; + +/** + * Author: TaoKan + */ +public class Child5 extends Parent { + private final long result; + + public Child5() { + this.result = 0L; + } + + public Child5(double t) { + this.result = 0L; + } + + public double getMethod8(double t) { + return t; + } + +} diff --git a/src/test/java/com/alibaba/qlexpress4/test/property/Child6.java b/src/test/java/com/alibaba/qlexpress4/test/property/Child6.java new file mode 100644 index 0000000..1583510 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/test/property/Child6.java @@ -0,0 +1,32 @@ +package com.alibaba.qlexpress4.test.property; + +import java.math.BigDecimal; +import java.math.BigInteger; + +/** + * Author: TaoKan + */ +public class Child6 extends Parent { + private final long result; + + public Child6() { + this.result = 0L; + } + + public Child6(double t) { + this.result = 0L; + } + + public Child6(BigInteger bigInteger) { + this.result = 0L; + } + + public int getMethod9(BigInteger t) { + return t.intValue(); + } + + public BigDecimal getMethod10(double t) { + return new BigDecimal(String.valueOf(t)); + } + +} diff --git a/src/test/java/com/alibaba/qlexpress4/test/property/Child7.java b/src/test/java/com/alibaba/qlexpress4/test/property/Child7.java new file mode 100644 index 0000000..11180cf --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/test/property/Child7.java @@ -0,0 +1,24 @@ +package com.alibaba.qlexpress4.test.property; + +import com.alibaba.qlexpress4.annotation.QLAlias; + +/** + * Author: TaoKan + */ +public class Child7 { + @QLAlias("测试静态字段") + public static final int t = 8; + + @QLAlias("测试字段") + public int ts = 9; + + @QLAlias("测试方法") + public int getSide() { + return 10; + } + + @QLAlias("测试静态方法") + public static int getSii() { + return 11; + } +} diff --git a/src/test/java/com/alibaba/qlexpress4/test/property/Child9.java b/src/test/java/com/alibaba/qlexpress4/test/property/Child9.java new file mode 100644 index 0000000..a3ea764 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/test/property/Child9.java @@ -0,0 +1,29 @@ +package com.alibaba.qlexpress4.test.property; + +/** + * Author: TaoKan + */ +public class Child9 { + public Child9() { + + } + + public Child9(int a, String... args) { + } + + public String addField(int a, String... args) { + return "1"; + }; + + public String addField1(Object... args) { + return "1"; + }; + + public String addField2(Object s, Object... args) { + return "1"; + }; + + public String addField3(Object s, Integer... args) { + return "1"; + }; +} diff --git a/src/test/java/com/alibaba/qlexpress4/test/property/NumberConstructor.java b/src/test/java/com/alibaba/qlexpress4/test/property/NumberConstructor.java new file mode 100644 index 0000000..9345945 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/test/property/NumberConstructor.java @@ -0,0 +1,31 @@ +package com.alibaba.qlexpress4.test.property; + +import java.math.BigDecimal; + +/** + * Author: TaoKan + */ +public class NumberConstructor { + + private int flag; + + public NumberConstructor(double a) { + this.flag = 0; + } + + public NumberConstructor(Number a) { + this.flag = 1; + } + + public NumberConstructor(BigDecimal a) { + this.flag = 2; + } + + public NumberConstructor(String a) { + this.flag = 3; + } + + public int getFlag() { + return flag; + } +} diff --git a/src/test/java/com/alibaba/qlexpress4/test/property/Parent.java b/src/test/java/com/alibaba/qlexpress4/test/property/Parent.java new file mode 100644 index 0000000..91388fc --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/test/property/Parent.java @@ -0,0 +1,113 @@ +package com.alibaba.qlexpress4.test.property; + +import com.alibaba.qlexpress4.annotation.QLAlias; +import com.alibaba.qlexpress4.runtime.Value; + +/** + * Author: TaoKan + */ +public class Parent implements Value { + private final long result; + + public static final String staticPublic = "staticPublic"; + + private static final String staticPrivate = "staticPrivate"; + + private static final String staticGet = "staticGet"; + + public static String staticSet = "staticSet"; + + private static String staticSetPrivate = "staticSetPrivate"; + + private static final String staticFinal = "staticFinal"; + + public String sex = "man"; + + public int lockStatus; + + public Integer lockStatus2; + + public Parent() { + this.result = 0L; + } + + public Parent(int age) { + this.age = age; + this.result = 0L; + } + + public Parent(int a, int b) { + this.result = a + b; + } + + @QLAlias("生日") + public String birth = "2022-01-01"; + + private int age; + + private String name = "example"; + + public static String getStaticGet() { + return "staticGet1"; + } + + public static String getStaticGetParam(Integer a) { + return staticGet; + } + + public static String findStatic() { + return "static"; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + public String getWork() { + return "parent"; + } + + public Parent getParentOwn() { + return new Parent(); + } + + private String getMethod1(int a, int b) { + return "a"; + } + + public String getBirth() { + return birth; + } + + public void setBirth(String birth) { + this.birth = birth; + } + + @Override + public Object get() { + return this; + } + + // public String getMethod2(long a,long b){return "b";} + + public String getMethod2(long a) { + return "b"; + } + + public String getMethod2(int b) { + return "b"; + } + + private long getMethod11(int a, int b) { + return a + b + 1; + } + + private long getMethod12(int a, int b) { + return a + b; + } + +} diff --git a/src/test/java/com/alibaba/qlexpress4/test/property/ParentClass.java b/src/test/java/com/alibaba/qlexpress4/test/property/ParentClass.java new file mode 100644 index 0000000..4a01376 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/test/property/ParentClass.java @@ -0,0 +1,15 @@ +package com.alibaba.qlexpress4.test.property; + +import com.alibaba.qlexpress4.runtime.Value; + +/** + * Author: TaoKan + */ +public class ParentClass implements Value { + @Override + public Object get() { + Parent parent = new Parent(); + parent.setAge(35); + return parent.getClass(); + } +} diff --git a/src/test/java/com/alibaba/qlexpress4/test/property/ParentParameters.java b/src/test/java/com/alibaba/qlexpress4/test/property/ParentParameters.java new file mode 100644 index 0000000..311b370 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/test/property/ParentParameters.java @@ -0,0 +1,29 @@ +package com.alibaba.qlexpress4.test.property; + +import com.alibaba.qlexpress4.runtime.Parameters; +import com.alibaba.qlexpress4.runtime.Value; +import com.alibaba.qlexpress4.runtime.data.DataValue; + +import java.util.ArrayList; +import java.util.List; + +/** + * Author: TaoKan + */ +public class ParentParameters implements Parameters { + public List values = new ArrayList<>(); + + @Override + public Value get(int i) { + return values.get(i); + } + + @Override + public int size() { + return values.size(); + } + + public void push(Object s) { + values.add(new DataValue(s)); + } +} diff --git a/src/test/java/com/alibaba/qlexpress4/test/property/Sample.java b/src/test/java/com/alibaba/qlexpress4/test/property/Sample.java new file mode 100644 index 0000000..892d7be --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/test/property/Sample.java @@ -0,0 +1,22 @@ +package com.alibaba.qlexpress4.test.property; + +/** + * Author: TaoKan + */ +public class Sample { + + private int count; + + public Sample(int count) { + this.count = count; + } + + public int getCount() { + return count; + } + + public void setCount(int count) { + this.count = count; + } + +} diff --git a/src/test/java/com/alibaba/qlexpress4/test/property/SampleEnum.java b/src/test/java/com/alibaba/qlexpress4/test/property/SampleEnum.java new file mode 100644 index 0000000..d9bd289 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/test/property/SampleEnum.java @@ -0,0 +1,12 @@ +package com.alibaba.qlexpress4.test.property; + +/** + * Author: TaoKan + */ +public enum SampleEnum { + NORMAL, UN_SUPPORT; + + public int testField = 10; + + public static int testStaticField = 1000; +} diff --git a/src/test/java/com/alibaba/qlexpress4/test/property/SampleForPrivate.java b/src/test/java/com/alibaba/qlexpress4/test/property/SampleForPrivate.java new file mode 100644 index 0000000..905f9db --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/test/property/SampleForPrivate.java @@ -0,0 +1,14 @@ +package com.alibaba.qlexpress4.test.property; + +/** + * Author: TaoKan + */ +public class SampleForPrivate { + + private int count; + + public SampleForPrivate(int count) { + this.count = count; + } + +} diff --git a/src/test/java/com/alibaba/qlexpress4/test/property/SampleSet.java b/src/test/java/com/alibaba/qlexpress4/test/property/SampleSet.java new file mode 100644 index 0000000..9627010 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/test/property/SampleSet.java @@ -0,0 +1,8 @@ +package com.alibaba.qlexpress4.test.property; + +/** + * Author: TaoKan + */ +public class SampleSet { + public int count; +} diff --git a/src/test/java/com/alibaba/qlexpress4/test/property/SomeInter.java b/src/test/java/com/alibaba/qlexpress4/test/property/SomeInter.java new file mode 100644 index 0000000..022a186 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/test/property/SomeInter.java @@ -0,0 +1,7 @@ +package com.alibaba.qlexpress4.test.property; + +public interface SomeInter { + + String INTER_CONST_1 = "test1"; + +} diff --git a/src/test/java/com/alibaba/qlexpress4/test/property/TestEnum.java b/src/test/java/com/alibaba/qlexpress4/test/property/TestEnum.java new file mode 100644 index 0000000..64b91b4 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/test/property/TestEnum.java @@ -0,0 +1,19 @@ +package com.alibaba.qlexpress4.test.property; + +/** + * Author: TaoKan + */ +public enum TestEnum { + SKT(-1), KSY(1); + + private final int value; + + TestEnum(int i) { + this.value = i; + } + + public int getValue() { + return value; + } + +} diff --git a/src/test/java/com/alibaba/qlexpress4/test/qlalias/Order.java b/src/test/java/com/alibaba/qlexpress4/test/qlalias/Order.java new file mode 100644 index 0000000..062667e --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/test/qlalias/Order.java @@ -0,0 +1,29 @@ +package com.alibaba.qlexpress4.test.qlalias; + +import com.alibaba.qlexpress4.annotation.QLAlias; + +@QLAlias("订单") +public class Order { + + @QLAlias("订单号") + private String orderNum; + + @QLAlias("金额") + private int amount; + + public String getOrderNum() { + return orderNum; + } + + public void setOrderNum(String orderNum) { + this.orderNum = orderNum; + } + + public int getAmount() { + return amount; + } + + public void setAmount(int amount) { + this.amount = amount; + } +} diff --git a/src/test/java/com/alibaba/qlexpress4/test/qlalias/Patient.java b/src/test/java/com/alibaba/qlexpress4/test/qlalias/Patient.java new file mode 100644 index 0000000..f15ee4b --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/test/qlalias/Patient.java @@ -0,0 +1,36 @@ +package com.alibaba.qlexpress4.test.qlalias; + +import com.alibaba.qlexpress4.annotation.QLAlias; + +@QLAlias("患者") +public class Patient extends Person { + + @QLAlias("级别") + private String level = "高危"; + + @QLAlias("患者姓名") + private String name; + + @QLAlias("获取患者年龄") + public int getAge() { + return 2021 - Integer.parseInt(this.getBirth().substring(0, 4)); + } + + public String getLevel() { + return level; + } + + public void setLevel(String level) { + this.level = level; + } + + @Override + public String getName() { + return name; + } + + @Override + public void setName(String name) { + this.name = name; + } +} diff --git a/src/test/java/com/alibaba/qlexpress4/test/qlalias/Person.java b/src/test/java/com/alibaba/qlexpress4/test/qlalias/Person.java new file mode 100644 index 0000000..3724b46 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/test/qlalias/Person.java @@ -0,0 +1,44 @@ +package com.alibaba.qlexpress4.test.qlalias; + +import com.alibaba.qlexpress4.annotation.QLAlias; + +public class Person { + @QLAlias({"出生年月", "生日"}) + private String birth; + + @QLAlias("姓名") + private String name; + + @QLAlias("性别") + private String sex; + + @QLAlias("获取年龄") + public int getAge() { + return 2021 - Integer.parseInt(this.birth.substring(0, 4)); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getSex() { + return sex; + } + + public void setSex(String sex) { + this.sex = sex; + } + + @QLAlias({"出生年月", "生日"}) + public String getBirth() { + return birth; + } + + public void setBirth(String birth) { + this.birth = birth; + } +} diff --git a/src/test/java/com/alibaba/qlexpress4/test/qlalias/User.java b/src/test/java/com/alibaba/qlexpress4/test/qlalias/User.java new file mode 100644 index 0000000..f4979d2 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/test/qlalias/User.java @@ -0,0 +1,29 @@ +package com.alibaba.qlexpress4.test.qlalias; + +import com.alibaba.qlexpress4.annotation.QLAlias; + +@QLAlias("用户") +public class User { + + @QLAlias("是vip") + private boolean vip; + + @QLAlias("用户名") + private String name; + + public boolean isVip() { + return vip; + } + + public void setVip(boolean vip) { + this.vip = vip; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/src/test/java/com/alibaba/qlexpress4/test/stream/STObject.java b/src/test/java/com/alibaba/qlexpress4/test/stream/STObject.java new file mode 100644 index 0000000..e94e326 --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/test/stream/STObject.java @@ -0,0 +1,14 @@ +package com.alibaba.qlexpress4.test.stream; + +public class STObject { + + private final String payload; + + public STObject(String payload) { + this.payload = payload; + } + + public String getPayload() { + return payload; + } +} diff --git a/src/test/java/com/alibaba/qlexpress4/test/trycatch/SampleException.java b/src/test/java/com/alibaba/qlexpress4/test/trycatch/SampleException.java new file mode 100644 index 0000000..dca16cc --- /dev/null +++ b/src/test/java/com/alibaba/qlexpress4/test/trycatch/SampleException.java @@ -0,0 +1,12 @@ +package com.alibaba.qlexpress4.test.trycatch; + +/** + * Author: DQinYuan + */ +public class SampleException { + + public void willThrow() { + throw new IllegalStateException(); + } + +} diff --git a/src/test/resources/perf/complexDataProcessing.ql b/src/test/resources/perf/complexDataProcessing.ql new file mode 100644 index 0000000..b9bb928 --- /dev/null +++ b/src/test/resources/perf/complexDataProcessing.ql @@ -0,0 +1,514 @@ +import java.lang.String; +import java.lang.Integer; +import java.lang.Long; +import java.lang.Double; +import java.lang.Boolean; +import java.lang.Math; +import java.lang.System; +import java.util.List; +import java.util.Map; +import java.util.HashMap; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.HashSet; +import java.util.TreeSet; +import java.util.Collections; +import java.util.Comparator; +import java.util.Random; +import java.util.Date; +import java.util.UUID; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Complex Data Processing Engine + * This script demonstrates complex business logic with multiple functions, + * nested conditions, loops, and extensive data transformations. + */ + +// Configuration constants +Integer MAX_RETRY_COUNT = 5; +Integer BATCH_SIZE = 100; +Double THRESHOLD_RATIO = 0.85; +Long TIMEOUT_MILLIS = 30000L; +Boolean ENABLE_CACHE = true; +Boolean STRICT_MODE = false; + +/** + * Calculate weighted score based on multiple factors + */ +function calculateWeightedScore(Map dataPoint, Map weights) { + Double totalScore = 0.0; + Double totalWeight = 0.0; + + if (dataPoint == null || weights == null) { + return 0.0; + } + + for (String key : weights.keySet()) { + if (dataPoint.containsKey(key)) { + Double value = (Double) dataPoint.get(key); + Double weight = (Double) weights.get(key); + + if (value != null && weight != null && weight > 0) { + totalScore = totalScore + (value * weight); + totalWeight = totalWeight + weight; + } + } + } + + if (totalWeight > 0) { + return totalScore / totalWeight; + } else { + return 0.0; + } +} + +/** + * Validate data quality and completeness + */ +function validateDataQuality(List records, Map schema) { + Integer validCount = 0; + Integer invalidCount = 0; + List errorMessages = new ArrayList(); + + for (Integer i = 0; i < records.size(); i++) { + Map record = (Map) records.get(i); + Boolean isValid = true; + + // Check required fields + for (String fieldName : schema.keySet()) { + Map fieldSpec = (Map) schema.get(fieldName); + Boolean required = (Boolean) fieldSpec.get("required"); + + if (required != null && required) { + if (!record.containsKey(fieldName) || record.get(fieldName) == null) { + isValid = false; + errorMessages.add("Record " + i + " missing required field: " + fieldName); + } + } + + // Type validation + if (record.containsKey(fieldName) && record.get(fieldName) != null) { + String expectedType = (String) fieldSpec.get("type"); + Object value = record.get(fieldName); + + if ("number".equals(expectedType) && !(value instanceof Number)) { + isValid = false; + errorMessages.add("Record " + i + " field " + fieldName + " has wrong type"); + } else if ("string".equals(expectedType) && !(value instanceof String)) { + isValid = false; + errorMessages.add("Record " + i + " field " + fieldName + " has wrong type"); + } + } + } + + if (isValid) { + validCount = validCount + 1; + } else { + invalidCount = invalidCount + 1; + } + } + + Map result = new HashMap(); + result.put("validCount", validCount); + result.put("invalidCount", invalidCount); + result.put("totalCount", records.size()); + result.put("qualityRatio", (Double) validCount / records.size()); + result.put("errors", errorMessages); + + return result; +} + +/** + * Transform and normalize data + */ +function transformData(List rawData, Map transformRules) { + List transformedData = new ArrayList(); + + for (Integer idx = 0; idx < rawData.size(); idx++) { + Map rawRecord = (Map) rawData.get(idx); + Map transformedRecord = new HashMap(); + + for (String sourceField : transformRules.keySet()) { + Map rule = (Map) transformRules.get(sourceField); + String targetField = (String) rule.get("target"); + String operation = (String) rule.get("operation"); + + if (rawRecord.containsKey(sourceField)) { + Object rawValue = rawRecord.get(sourceField); + Object transformedValue = rawValue; + + if ("uppercase".equals(operation) && rawValue instanceof String) { + transformedValue = ((String) rawValue).toUpperCase(); + } else if ("lowercase".equals(operation) && rawValue instanceof String) { + transformedValue = ((String) rawValue).toLowerCase(); + } else if ("multiply".equals(operation) && rawValue instanceof Number) { + Double multiplier = (Double) rule.get("factor"); + transformedValue = ((Number) rawValue).doubleValue() * multiplier; + } else if ("round".equals(operation) && rawValue instanceof Number) { + transformedValue = Math.round(((Number) rawValue).doubleValue()); + } else if ("abs".equals(operation) && rawValue instanceof Number) { + transformedValue = Math.abs(((Number) rawValue).doubleValue()); + } + + transformedRecord.put(targetField, transformedValue); + } + } + + // Copy unmapped fields + for (String field : rawRecord.keySet()) { + if (!transformedRecord.containsKey(field)) { + transformedRecord.put(field, rawRecord.get(field)); + } + } + + transformedData.add(transformedRecord); + } + + return transformedData; +} + +/** + * Aggregate data with grouping and statistics + */ +function aggregateData(List data, List groupByFields, Map aggregations) { + Map groups = new HashMap(); + + // Group data + for (Integer i = 0; i < data.size(); i++) { + Map record = (Map) data.get(i); + String groupKey = ""; + + for (Integer j = 0; j < groupByFields.size(); j++) { + String field = (String) groupByFields.get(j); + Object value = record.get(field); + groupKey = groupKey + field + ":" + value + "|"; + } + + if (!groups.containsKey(groupKey)) { + groups.put(groupKey, new ArrayList()); + } + + List groupRecords = (List) groups.get(groupKey); + groupRecords.add(record); + } + + // Calculate aggregations + List results = new ArrayList(); + + for (String groupKey : groups.keySet()) { + List groupRecords = (List) groups.get(groupKey); + Map aggResult = new HashMap(); + + // Add group key fields + String[] keyParts = groupKey.split("\\|"); + for (Integer k = 0; k < keyParts.length - 1; k++) { + String[] fieldValue = keyParts[k].split(":"); + if (fieldValue.length == 2) { + aggResult.put(fieldValue[0], fieldValue[1]); + } + } + + // Calculate aggregations + for (String aggField : aggregations.keySet()) { + Map aggSpec = (Map) aggregations.get(aggField); + String operation = (String) aggSpec.get("operation"); + + if ("count".equals(operation)) { + aggResult.put(aggField + "_count", groupRecords.size()); + } else if ("sum".equals(operation)) { + Double sum = 0.0; + for (Integer m = 0; m < groupRecords.size(); m++) { + Map rec = (Map) groupRecords.get(m); + Object val = rec.get(aggField); + if (val instanceof Number) { + sum = sum + ((Number) val).doubleValue(); + } + } + aggResult.put(aggField + "_sum", sum); + } else if ("avg".equals(operation)) { + Double sum = 0.0; + Integer count = 0; + for (Integer m = 0; m < groupRecords.size(); m++) { + Map rec = (Map) groupRecords.get(m); + Object val = rec.get(aggField); + if (val instanceof Number) { + sum = sum + ((Number) val).doubleValue(); + count = count + 1; + } + } + aggResult.put(aggField + "_avg", count > 0 ? sum / count : 0.0); + } else if ("max".equals(operation)) { + Double max = null; + for (Integer m = 0; m < groupRecords.size(); m++) { + Map rec = (Map) groupRecords.get(m); + Object val = rec.get(aggField); + if (val instanceof Number) { + Double numVal = ((Number) val).doubleValue(); + if (max == null || numVal > max) { + max = numVal; + } + } + } + aggResult.put(aggField + "_max", max); + } else if ("min".equals(operation)) { + Double min = null; + for (Integer m = 0; m < groupRecords.size(); m++) { + Map rec = (Map) groupRecords.get(m); + Object val = rec.get(aggField); + if (val instanceof Number) { + Double numVal = ((Number) val).doubleValue(); + if (min == null || numVal < min) { + min = numVal; + } + } + } + aggResult.put(aggField + "_min", min); + } + } + + results.add(aggResult); + } + + return results; +} + +/** + * Filter data based on complex conditions + */ +function filterData(List data, Map filterConditions) { + List filteredData = new ArrayList(); + + for (Integer i = 0; i < data.size(); i++) { + Map record = (Map) data.get(i); + Boolean matchesAllConditions = true; + + for (String field : filterConditions.keySet()) { + Map condition = (Map) filterConditions.get(field); + String operator = (String) condition.get("operator"); + Object expectedValue = condition.get("value"); + Object actualValue = record.get(field); + + Boolean matches = false; + + if ("equals".equals(operator)) { + matches = (actualValue != null && actualValue.equals(expectedValue)); + } else if ("notEquals".equals(operator)) { + matches = (actualValue == null || !actualValue.equals(expectedValue)); + } else if ("greaterThan".equals(operator) && actualValue instanceof Number && expectedValue instanceof Number) { + matches = ((Number) actualValue).doubleValue() > ((Number) expectedValue).doubleValue(); + } else if ("lessThan".equals(operator) && actualValue instanceof Number && expectedValue instanceof Number) { + matches = ((Number) actualValue).doubleValue() < ((Number) expectedValue).doubleValue(); + } else if ("greaterOrEqual".equals(operator) && actualValue instanceof Number && expectedValue instanceof Number) { + matches = ((Number) actualValue).doubleValue() >= ((Number) expectedValue).doubleValue(); + } else if ("lessOrEqual".equals(operator) && actualValue instanceof Number && expectedValue instanceof Number) { + matches = ((Number) actualValue).doubleValue() <= ((Number) expectedValue).doubleValue(); + } else if ("contains".equals(operator) && actualValue instanceof String && expectedValue instanceof String) { + matches = ((String) actualValue).contains((String) expectedValue); + } else if ("startsWith".equals(operator) && actualValue instanceof String && expectedValue instanceof String) { + matches = ((String) actualValue).startsWith((String) expectedValue); + } else if ("endsWith".equals(operator) && actualValue instanceof String && expectedValue instanceof String) { + matches = ((String) actualValue).endsWith((String) expectedValue); + } else if ("in".equals(operator) && expectedValue instanceof List) { + List valueList = (List) expectedValue; + matches = valueList.contains(actualValue); + } + + if (!matches) { + matchesAllConditions = false; + break; + } + } + + if (matchesAllConditions) { + filteredData.add(record); + } + } + + return filteredData; +} + +/** + * Join two datasets + */ +function joinDatasets(List leftData, List rightData, String leftKey, String rightKey, String joinType) { + List joinedData = new ArrayList(); + + // Build index for right dataset + Map rightIndex = new HashMap(); + for (Integer i = 0; i < rightData.size(); i++) { + Map rightRecord = (Map) rightData.get(i); + Object keyValue = rightRecord.get(rightKey); + + if (!rightIndex.containsKey(keyValue)) { + rightIndex.put(keyValue, new ArrayList()); + } + + List rightRecords = (List) rightIndex.get(keyValue); + rightRecords.add(rightRecord); + } + + // Perform join + for (Integer i = 0; i < leftData.size(); i++) { + Map leftRecord = (Map) leftData.get(i); + Object keyValue = leftRecord.get(leftKey); + + if (rightIndex.containsKey(keyValue)) { + List matchingRightRecords = (List) rightIndex.get(keyValue); + + for (Integer j = 0; j < matchingRightRecords.size(); j++) { + Map rightRecord = (Map) matchingRightRecords.get(j); + Map joinedRecord = new HashMap(); + + // Add left record fields + for (String field : leftRecord.keySet()) { + joinedRecord.put("left_" + field, leftRecord.get(field)); + } + + // Add right record fields + for (String field : rightRecord.keySet()) { + joinedRecord.put("right_" + field, rightRecord.get(field)); + } + + joinedData.add(joinedRecord); + } + } else if ("left".equals(joinType) || "outer".equals(joinType)) { + Map joinedRecord = new HashMap(); + + // Add left record fields + for (String field : leftRecord.keySet()) { + joinedRecord.put("left_" + field, leftRecord.get(field)); + } + + joinedData.add(joinedRecord); + } + } + + return joinedData; +} + +/** + * Sort data by multiple fields + */ +function sortData(List data, List sortFields, List sortOrders) { + // Simple bubble sort implementation for demonstration + List sortedData = new ArrayList(); + for (Integer i = 0; i < data.size(); i++) { + sortedData.add(data.get(i)); + } + + for (Integer i = 0; i < sortedData.size() - 1; i++) { + for (Integer j = 0; j < sortedData.size() - i - 1; j++) { + Map record1 = (Map) sortedData.get(j); + Map record2 = (Map) sortedData.get(j + 1); + + Boolean shouldSwap = false; + + for (Integer k = 0; k < sortFields.size(); k++) { + String field = (String) sortFields.get(k); + String order = (String) sortOrders.get(k); + + Object value1 = record1.get(field); + Object value2 = record2.get(field); + + if (value1 == null && value2 != null) { + shouldSwap = "asc".equals(order); + break; + } else if (value1 != null && value2 == null) { + shouldSwap = "desc".equals(order); + break; + } else if (value1 != null && value2 != null) { + if (value1 instanceof Number && value2 instanceof Number) { + Double num1 = ((Number) value1).doubleValue(); + Double num2 = ((Number) value2).doubleValue(); + + if (num1 < num2) { + shouldSwap = "desc".equals(order); + break; + } else if (num1 > num2) { + shouldSwap = "asc".equals(order); + break; + } + } else if (value1 instanceof String && value2 instanceof String) { + Integer comparison = ((String) value1).compareTo((String) value2); + + if (comparison < 0) { + shouldSwap = "desc".equals(order); + break; + } else if (comparison > 0) { + shouldSwap = "asc".equals(order); + break; + } + } + } + } + + if (shouldSwap) { + Object temp = sortedData.get(j); + sortedData.set(j, sortedData.get(j + 1)); + sortedData.set(j + 1, temp); + } + } + } + + return sortedData; +} + +// Main processing logic +Map processingConfig = new HashMap(); +processingConfig.put("enableValidation", true); +processingConfig.put("enableTransformation", true); +processingConfig.put("enableAggregation", true); +processingConfig.put("enableFiltering", true); + +List sampleData = new ArrayList(); +Map weights = new HashMap(); +weights.put("quality", 0.3); +weights.put("speed", 0.5); +weights.put("cost", 0.2); + +// Create sample validation schema +Map schema = new HashMap(); +Map field1 = new HashMap(); +field1.put("required", true); +field1.put("type", "string"); +schema.put("id", field1); + +Map field2 = new HashMap(); +field2.put("required", true); +field2.put("type", "number"); +schema.put("value", field2); + +// Process data in batches +Integer totalProcessed = 0; +Integer totalErrors = 0; +Double avgScore = 0.0; + +String status = "initialized"; +Long startTime = System.currentTimeMillis(); + +if (processingConfig.get("enableValidation") != null && (Boolean) processingConfig.get("enableValidation")) { + status = "validating"; +} + +if (processingConfig.get("enableTransformation") != null && (Boolean) processingConfig.get("enableTransformation")) { + status = "transforming"; +} + +Long endTime = System.currentTimeMillis(); +Long duration = endTime - startTime; + +Map finalResult = new HashMap(); +finalResult.put("status", status); +finalResult.put("totalProcessed", totalProcessed); +finalResult.put("totalErrors", totalErrors); +finalResult.put("avgScore", avgScore); +finalResult.put("duration", duration); +finalResult.put("timestamp", endTime); + +return finalResult; diff --git a/src/test/resources/test-plugins/build-plugin.sh b/src/test/resources/test-plugins/build-plugin.sh new file mode 100644 index 0000000..b70ef07 --- /dev/null +++ b/src/test/resources/test-plugins/build-plugin.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# Create temporary directory +mkdir -p target/test-plugin-classes + +# Compile test plugin classes +javac -cp "$(mvn dependency:build-classpath -Dmdep.outputFile=/dev/stdout -q):target/classes" \ + -d target/test-plugin-classes \ + src/test/java/com/alibaba/qlexpress4/pf4j/TestPluginInterface.java \ + src/test/java/com/alibaba/qlexpress4/pf4j/TestPluginImpl.java + +# Copy plugin configuration file +cp src/test/resources/test-plugins/plugin.properties target/test-plugin-classes/ + +# Package into jar +jar cf src/test/resources/test-plugins/test-plugin.jar -C target/test-plugin-classes . + +echo "Plugin jar created: src/test/resources/test-plugins/test-plugin.jar" \ No newline at end of file diff --git a/src/test/resources/test-plugins/plugin.properties b/src/test/resources/test-plugins/plugin.properties new file mode 100644 index 0000000..3ffa0aa --- /dev/null +++ b/src/test/resources/test-plugins/plugin.properties @@ -0,0 +1,7 @@ +plugin.id=test-plugin +plugin.version=1.0.0 +plugin.class=com.alibaba.qlexpress4.pf4j.TestPluginImpl +plugin.description=Test plugin for QLExpress PF4J integration +plugin.provider=QLExpress Test +plugin.dependencies= +plugin.requires=* \ No newline at end of file diff --git a/src/test/resources/test-plugins/test-plugin.jar b/src/test/resources/test-plugins/test-plugin.jar new file mode 100644 index 0000000000000000000000000000000000000000..6929465630acd64446a259403847fb796ca14e70 GIT binary patch literal 2154 zcmWIWW@Zs#;Nak32%hpQiUA35GO#fCx`sIFdiuHP|2xINz|0Wf&CUT*!30$nfK#&w zPz7AGucM!*n`>~0p0C?y-!rFuymj?1@_OrPojY@WbCAIm;|EWR^t^m^Jbf>gu43Vg zcp-U2I!a0_8mp0%Z$w zO4Bp*^a_gd3sQ?pGE<9PdmOo%4Fp`j+qzzBpYg=YmPvYh_AdW>peZne=NFe~2y0Zn%NAL7gLzSxrDoXtvsmQ8d*^EW zk2OovCd)Jb-c+n%CbV4UOxvxUua2#_xBhlqz}dZZT3npJGd4wClR4fh<5s)Evgn9p z^Wg+eS8<8LS(%|?(Mm zDiU)tlM<5>@u&m^J;*GW%EFw~ih`ol;$jm#nq+WmDo8WQ0_(({SPcm13`s362>?fz zXKq1`UUE)iadB>dum2$jf!fpI3&R?f7Cd5~HA6w|+acb^M~@t7kr3*bw|#?|>ffT> z5`QDBE*xpLKOmnvXIit@4d1l%duR5{HIKh9KcAs02i&b-gRanXW*^$oq?{*xV`y@~$<^Pjuish1itj?7W*dC?5yY1As^R3{d ze$nY&inl#aCyBc8{L+|nx^qX-#G^8;Oj>=?DMlW-hNu zT9tVIfF0ko$%?%usW1GT?mwEBT|7IQe_ln`l8(pslK%XczjVd>kc8GrfoaE?k4xDe z_xhotFlTblFNL!^7Mf1UTj1u!_3pdp5w01I-IGozAF4|K&>kfC=EA*#4klfD%g7Bb z#*uRuDW|+(YJK<8`UZC;Pj<>z5z`6BtrXp|CjUx4B)NBKc*>bK(nY6rLzT=qZ${jm z6o2v3f@x}ssh^Bj`~hbrc{`K;af}QM2EYOXcUCebKJM~LQj5|OlT)D)=zq}fkb}sa zb}vRl!9tc*!tpSh{Ffp>jQl;nd+ z241nRi)V(racqy8wZSkq@}{`-5%D>vzbtQGP_#WXXH}1JShcdewZ8A<6|obqyqYiZ z@K^o;tJSLfEwj@Nj~#6-zg=PMFZMD2#Xf(9FT2_~$}J98mg`$xbN;`?Ro%vd36x3S zWQTH|28P8~AP(?mWD)@uLC6I&tkeY+%%}if(t?U literal 0 HcmV?d00001 diff --git a/src/test/resources/testsuite/independent/array/array_index_out_of_bound.ql b/src/test/resources/testsuite/independent/array/array_index_out_of_bound.ql new file mode 100644 index 0000000..fe30c50 --- /dev/null +++ b/src/test/resources/testsuite/independent/array/array_index_out_of_bound.ql @@ -0,0 +1,7 @@ +/* +{ + "errCode": "INDEX_OUT_BOUND" +} +*/ +a = []; +a[1] \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/array/array_literal.ql b/src/test/resources/testsuite/independent/array/array_literal.ql new file mode 100644 index 0000000..9c3463f --- /dev/null +++ b/src/test/resources/testsuite/independent/array/array_literal.ql @@ -0,0 +1,6 @@ +a = [1,2,3, "123"]; +assert(a == [1,2,3, "123"]); +assert(a != [1,2,3, "125"]); +assert(a[0] == 1 && a[3] == "123"); +assert(a.length == 4); +assert(a[-1] == a[3]); \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/array/float_index.ql b/src/test/resources/testsuite/independent/array/float_index.ql new file mode 100644 index 0000000..cea9dae --- /dev/null +++ b/src/test/resources/testsuite/independent/array/float_index.ql @@ -0,0 +1,2 @@ +a = [1,2,3,4]; +assert(a[2.8] == 3); \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/array/invalid_index.ql b/src/test/resources/testsuite/independent/array/invalid_index.ql new file mode 100644 index 0000000..bf5f94d --- /dev/null +++ b/src/test/resources/testsuite/independent/array/invalid_index.ql @@ -0,0 +1,7 @@ +/* +{ + "errCode": "INVALID_INDEX" +} +*/ +a = [1]; +a["aaa"] = 2; \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/array/max_arr_len.ql b/src/test/resources/testsuite/independent/array/max_arr_len.ql new file mode 100644 index 0000000..3230505 --- /dev/null +++ b/src/test/resources/testsuite/independent/array/max_arr_len.ql @@ -0,0 +1,15 @@ +/* +{ + "qlOptions": QLOptions.builder().maxArrLength(10) +} +*/ + +try { + a = new int[10] + a = new int[1][2][10][10][9] +} catch(o) { + assert(false); +} + +assertErrorCode(() -> new int[11], "EXCEED_MAX_ARR_LENGTH") +assertErrorCode(() -> new int[1][13][3], "EXCEED_MAX_ARR_LENGTH") \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/array/miss_comma_between_elements.ql b/src/test/resources/testsuite/independent/array/miss_comma_between_elements.ql new file mode 100644 index 0000000..a50297c --- /dev/null +++ b/src/test/resources/testsuite/independent/array/miss_comma_between_elements.ql @@ -0,0 +1,6 @@ +/* +{ + "errCode": "SYNTAX_ERROR" +} +*/ +[123 334] \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/array/no_rbrack_to_match.ql b/src/test/resources/testsuite/independent/array/no_rbrack_to_match.ql new file mode 100644 index 0000000..81517a2 --- /dev/null +++ b/src/test/resources/testsuite/independent/array/no_rbrack_to_match.ql @@ -0,0 +1,6 @@ +/* +{ + "errCode": "SYNTAX_ERROR" +} +*/ +a = [1223,34,34 \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/array/slice.ql b/src/test/resources/testsuite/independent/array/slice.ql new file mode 100644 index 0000000..3173344 --- /dev/null +++ b/src/test/resources/testsuite/independent/array/slice.ql @@ -0,0 +1,7 @@ +a = [1,2,3,4,5,6]; +assert(a[3:] == [4,5,6]); +assert(a[:2] == [1,2]); +assert(a[2:4] == [3,4]); +assert(a[4:10] == [5, 6]); +assert(a[-88:100] == [1,2,3,4,5,6]) +assert(a[3:-1] == [4,5]) \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/array/unindexable.ql b/src/test/resources/testsuite/independent/array/unindexable.ql new file mode 100644 index 0000000..152a4c1 --- /dev/null +++ b/src/test/resources/testsuite/independent/array/unindexable.ql @@ -0,0 +1,7 @@ +/* +{ + "errCode": "NONINDEXABLE_OBJECT" +} +*/ +a = new HashSet(); +a[1] \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/avoidnullpointer/avoid_null_pointer.ql b/src/test/resources/testsuite/independent/avoidnullpointer/avoid_null_pointer.ql new file mode 100644 index 0000000..bc6ffd9 --- /dev/null +++ b/src/test/resources/testsuite/independent/avoidnullpointer/avoid_null_pointer.ql @@ -0,0 +1,15 @@ +/* +{ + "qlOptions": QLOptions.builder().avoidNullPointer(true) +} +*/ +assert(a.b == null); +assert(a.b.c == null); +assert(a.b() == null); +assert(a.b().c.d() == null); +assert(a::b == null); +assert(a.b.c.mm() == null); +assert(a.b.c.mm()() == null); +assert(mmm() == null) +assert(a.n.c[2]==null) +assert(a.n.c[1:4]==null) \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/avoidnullpointer/can_not_find_function.ql b/src/test/resources/testsuite/independent/avoidnullpointer/can_not_find_function.ql new file mode 100644 index 0000000..69feaa8 --- /dev/null +++ b/src/test/resources/testsuite/independent/avoidnullpointer/can_not_find_function.ql @@ -0,0 +1,4 @@ +/*{ + "errCode": "FUNCTION_NOT_FOUND" +}*/ +mmm() \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/avoidnullpointer/get_from_null.ql b/src/test/resources/testsuite/independent/avoidnullpointer/get_from_null.ql new file mode 100644 index 0000000..537d6f8 --- /dev/null +++ b/src/test/resources/testsuite/independent/avoidnullpointer/get_from_null.ql @@ -0,0 +1,6 @@ +/* +{ + "errCode": "NULL_FIELD_ACCESS" +} +*/ +a.b \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/avoidnullpointer/get_method_from_null.ql b/src/test/resources/testsuite/independent/avoidnullpointer/get_method_from_null.ql new file mode 100644 index 0000000..4cf51c6 --- /dev/null +++ b/src/test/resources/testsuite/independent/avoidnullpointer/get_method_from_null.ql @@ -0,0 +1,4 @@ +/*{ + "errCode": "NULL_METHOD_ACCESS" +}*/ +a::b \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/block/block_as_expr.ql b/src/test/resources/testsuite/independent/block/block_as_expr.ql new file mode 100644 index 0000000..679fb87 --- /dev/null +++ b/src/test/resources/testsuite/independent/block/block_as_expr.ql @@ -0,0 +1,19 @@ +a = { + 1 + 1 +} + 1; +assert(a == 3); +b = { + String c = 'ccc'; + String d = 'ddd'; + c + '-' + d +}; +assert(b == 'ccc-ddd'); +f = { + int e = 10; + if (a > 5) { + a + e + } else { + a * 2 + } +}; +assert(f == 6); \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/block/block_at_if.ql b/src/test/resources/testsuite/independent/block/block_at_if.ql new file mode 100644 index 0000000..a6cf646 --- /dev/null +++ b/src/test/resources/testsuite/independent/block/block_at_if.ql @@ -0,0 +1,12 @@ +a = 1; +b = if (a < 5) { + { + a + 10 + } +} else { + { + a * 10 + } +}; +assert(b == 11); + diff --git a/src/test/resources/testsuite/independent/block/lambda_with_block.ql b/src/test/resources/testsuite/independent/block/lambda_with_block.ql new file mode 100644 index 0000000..04bbd47 --- /dev/null +++ b/src/test/resources/testsuite/independent/block/lambda_with_block.ql @@ -0,0 +1,10 @@ +f = (x) -> { + int e = 10; + if (x > 5) { + x + e + } else { + x * 2 + } +}; +assert(f(6) == 16); +assert(f(3) == 6); \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/block/missing_rbrace.ql b/src/test/resources/testsuite/independent/block/missing_rbrace.ql new file mode 100644 index 0000000..d1f9391 --- /dev/null +++ b/src/test/resources/testsuite/independent/block/missing_rbrace.ql @@ -0,0 +1,7 @@ +/* +{ + "errCode": "SYNTAX_ERROR" +} +*/ +{ + 1+1 diff --git a/src/test/resources/testsuite/independent/block/return_at_block.ql b/src/test/resources/testsuite/independent/block/return_at_block.ql new file mode 100644 index 0000000..18fd722 --- /dev/null +++ b/src/test/resources/testsuite/independent/block/return_at_block.ql @@ -0,0 +1,17 @@ +function returnAtBlock(a) { + int i = if (a > 10) { + { + return 100; + } + } else { + if (a < 5) { + {return 1000;} + } + {return 101;} + }; + 10000 +} + +assert(returnAtBlock(11) == 100); +assert(returnAtBlock(5) == 101); +assert(returnAtBlock(-5) == 1000) \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/bool/bool_literal.ql b/src/test/resources/testsuite/independent/bool/bool_literal.ql new file mode 100644 index 0000000..594db4a --- /dev/null +++ b/src/test/resources/testsuite/independent/bool/bool_literal.ql @@ -0,0 +1,4 @@ +assert(true == true); +assert(false == false); +assert(true != false); +assert(true != "true"); \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/bool/bool_literal_is_keyword.ql b/src/test/resources/testsuite/independent/bool/bool_literal_is_keyword.ql new file mode 100644 index 0000000..56ee7b6 --- /dev/null +++ b/src/test/resources/testsuite/independent/bool/bool_literal_is_keyword.ql @@ -0,0 +1,6 @@ +/* +{ + "errCode": "SYNTAX_ERROR" +} +*/ +true = 1; \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/bool/short_circuit.ql b/src/test/resources/testsuite/independent/bool/short_circuit.ql new file mode 100644 index 0000000..3e63651 --- /dev/null +++ b/src/test/resources/testsuite/independent/bool/short_circuit.ql @@ -0,0 +1,24 @@ +function a(int value, boolean b) { + a = value; + return b; +} + +c = a(1, false) && a(10, true); +assert(c == false); +assert(a == 1); + +c = a(100, true) || a(1, false); +assert(c == true); +assert(a == 100); + +d = a(1000, true) && a(10000, false); +assert(d == false); +assert(a == 10000); + +e = a(11, false) or a(111, true); +assert(e == true); +assert(a == 111); + +f = a(2, true) || a(3, false) && a(5, false); +assert(f == true); +assert(a == 2); \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/cast/cast_express.ql b/src/test/resources/testsuite/independent/cast/cast_express.ql new file mode 100644 index 0000000..4aa00ca --- /dev/null +++ b/src/test/resources/testsuite/independent/cast/cast_express.ql @@ -0,0 +1,7 @@ +a = int; +assert(a == int); +b = 12L; +c = (int) b; +assert(c.class == a.class); +d = (int) 100.12d; +assert(d == 100); \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/cast/null_cast.ql b/src/test/resources/testsuite/independent/cast/null_cast.ql new file mode 100644 index 0000000..8231e6e --- /dev/null +++ b/src/test/resources/testsuite/independent/cast/null_cast.ql @@ -0,0 +1,3 @@ +Integer a = null; +assert(!(boolean) a); +assert((int) a == null); \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/comment/comment.ql b/src/test/resources/testsuite/independent/comment/comment.ql new file mode 100644 index 0000000..50d4bb9 --- /dev/null +++ b/src/test/resources/testsuite/independent/comment/comment.ql @@ -0,0 +1,6 @@ +// in-line comment +/* +multiline comment +*/ + +// \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/doc/convenient_syntax_elements.ql b/src/test/resources/testsuite/independent/doc/convenient_syntax_elements.ql new file mode 100644 index 0000000..0ddc0e6 --- /dev/null +++ b/src/test/resources/testsuite/independent/doc/convenient_syntax_elements.ql @@ -0,0 +1,21 @@ +// list +l = [1,2,3] +assert(l[0]==1) +assert(l[-1]==3) +// Underlying data type of list is ArrayList in Java +assert(l instanceof ArrayList) +// map +m = { + "aa": 10, + "bb": { + "cc": "cc1", + "dd": "dd1" + } +} +assert(m['aa']==10) +// Underlying data type of map is ArrayList in Java +assert(m instanceof LinkedHashMap) +// empty map +emMap = {:} +emMap['haha']='huhu' +assert(emMap['haha']=='huhu') \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/doc/dynamic_string.ql b/src/test/resources/testsuite/independent/doc/dynamic_string.ql new file mode 100644 index 0000000..63acbb1 --- /dev/null +++ b/src/test/resources/testsuite/independent/doc/dynamic_string.ql @@ -0,0 +1,12 @@ +a = 123 +assert("hello,${a-1}" == "hello,122") + +// escape $ with \$ +assert("hello,\${a-1}" == "hello,\${a-1}") + +b = "test" +assert("m xx ${ + if (b like 't%') { + 'YYY' + } +}" == "m xx YYY") \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/doc/dynamic_typing.ql b/src/test/resources/testsuite/independent/doc/dynamic_typing.ql new file mode 100644 index 0000000..7458a82 --- /dev/null +++ b/src/test/resources/testsuite/independent/doc/dynamic_typing.ql @@ -0,0 +1,8 @@ +// Dynamic Typeing +a = 1; +a = "1"; +// Static Typing +int b = 2; +// throw QLException with error code INCOMPATIBLE_ASSIGNMENT_TYPE when assign with incompatible type String +assertErrorCode(() -> b = "1", "INCOMPATIBLE_ASSIGNMENT_TYPE") + diff --git a/src/test/resources/testsuite/independent/doc/for.ql b/src/test/resources/testsuite/independent/doc/for.ql new file mode 100644 index 0000000..9240489 --- /dev/null +++ b/src/test/resources/testsuite/independent/doc/for.ql @@ -0,0 +1,5 @@ +l = []; +for (int i = 3; i < 6; i++) { + l.add(i); +} +assert(l==[3,4,5]) \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/doc/for_each.ql b/src/test/resources/testsuite/independent/doc/for_each.ql new file mode 100644 index 0000000..9065e5e --- /dev/null +++ b/src/test/resources/testsuite/independent/doc/for_each.ql @@ -0,0 +1,8 @@ +sum = 0; +for (i: [0,1,2,3,4]) { + if (i == 2) { + continue; + } + sum += i; +} +assert(sum==8) \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/doc/function.ql b/src/test/resources/testsuite/independent/doc/function.ql new file mode 100644 index 0000000..6744785 --- /dev/null +++ b/src/test/resources/testsuite/independent/doc/function.ql @@ -0,0 +1,4 @@ +function sub(a, b) { + return a-b; +} +assert(sub(3,1)==2) \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/doc/if.ql b/src/test/resources/testsuite/independent/doc/if.ql new file mode 100644 index 0000000..d231a89 --- /dev/null +++ b/src/test/resources/testsuite/independent/doc/if.ql @@ -0,0 +1,13 @@ +a = 11; +// if ... else ... +assert(if (a >= 0 && a < 5) { + true +} else if (a >= 5 && a < 10) { + false +} else if (a >= 10 && a < 15) { + true +} == true) + +// if ... then ... else ... +r = if (a == 11) then true else false +assert(r == true) \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/doc/if_as_expr.ql b/src/test/resources/testsuite/independent/doc/if_as_expr.ql new file mode 100644 index 0000000..424dbcc --- /dev/null +++ b/src/test/resources/testsuite/independent/doc/if_as_expr.ql @@ -0,0 +1,5 @@ +assert(if (11 == 11) { + 10 +} else { + 20 + 2 +} + 1 == 11) \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/doc/if_then.ql b/src/test/resources/testsuite/independent/doc/if_then.ql new file mode 100644 index 0000000..82a1928 --- /dev/null +++ b/src/test/resources/testsuite/independent/doc/if_then.ql @@ -0,0 +1,3 @@ +a = 11; + +assert(if (a >= 0 && a < 5) then true else false == false) \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/doc/lambda.ql b/src/test/resources/testsuite/independent/doc/lambda.ql new file mode 100644 index 0000000..07c162a --- /dev/null +++ b/src/test/resources/testsuite/independent/doc/lambda.ql @@ -0,0 +1,4 @@ +add = (a, b) -> { + return a + b; +} +assert(add(1,2)==3) \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/doc/list_map_filter.ql b/src/test/resources/testsuite/independent/doc/list_map_filter.ql new file mode 100644 index 0000000..c8d86a4 --- /dev/null +++ b/src/test/resources/testsuite/independent/doc/list_map_filter.ql @@ -0,0 +1,3 @@ +l = ["a-111", "a-222", "b-333", "c-888"] +assert(l.filter(i -> i.startsWith("a-")) + .map(i -> i.split("-")[1]) == ["111", "222"]) \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/doc/try_catch.ql b/src/test/resources/testsuite/independent/doc/try_catch.ql new file mode 100644 index 0000000..cb836db --- /dev/null +++ b/src/test/resources/testsuite/independent/doc/try_catch.ql @@ -0,0 +1,6 @@ +assert(try { + 100 + 1/0 +} catch(e) { + // Throw a zero-division exception + 11 +} == 11) \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/doc/try_catch_as_expr.ql b/src/test/resources/testsuite/independent/doc/try_catch_as_expr.ql new file mode 100644 index 0000000..4a9ccfa --- /dev/null +++ b/src/test/resources/testsuite/independent/doc/try_catch_as_expr.ql @@ -0,0 +1,6 @@ +assert(1 + try { + 100 + 1/0 +} catch(e) { + // Throw a zero-division exception + 11 +} == 12) \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/doc/while.ql b/src/test/resources/testsuite/independent/doc/while.ql new file mode 100644 index 0000000..8b2f732 --- /dev/null +++ b/src/test/resources/testsuite/independent/doc/while.ql @@ -0,0 +1,7 @@ +i = 0; +while (i < 5) { + if (++i == 2) { + break; + } +} +assert(i==2) \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/extensionfunction/extension_function.ql b/src/test/resources/testsuite/independent/extensionfunction/extension_function.ql new file mode 100644 index 0000000..bdf6db2 --- /dev/null +++ b/src/test/resources/testsuite/independent/extensionfunction/extension_function.ql @@ -0,0 +1,7 @@ +a = [1,2,3,4].filter(i -> i > 2) +assert(a == [3,4]) +assert(a instanceof List) + +assert([1,2].map(i -> i+2) == a) + +assertErrorCode(() -> {'a':1}.filter(en -> en.value > 10), "METHOD_NOT_FOUND") \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/for/break_continue.ql b/src/test/resources/testsuite/independent/for/break_continue.ql new file mode 100644 index 0000000..6197323 --- /dev/null +++ b/src/test/resources/testsuite/independent/for/break_continue.ql @@ -0,0 +1,27 @@ +for (i = 0; i < 5; i++) { + if (i == 2) { + break; + } +} +assert(i == 2); + +sum = 0; +for (i = 0; i < 5; i++) { + if (i == 2) { + continue; + } + sum += i; +} +assert(sum == 8); + +sum = 0; +for (i = 0; i < 5; i++) { + if (i == 2) { + if (i == 2) { + continue; + } + } + sum += i; +} +assert(sum == 8); + diff --git a/src/test/resources/testsuite/independent/for/c_for.ql b/src/test/resources/testsuite/independent/for/c_for.ql new file mode 100644 index 0000000..df8e0e6 --- /dev/null +++ b/src/test/resources/testsuite/independent/for/c_for.ql @@ -0,0 +1,19 @@ +l = []; +for (int i = 3; i < 6; i++) { + l.add(i); +} +assert(l == [3,4,5]); +assert(i == null); + +l1 = []; +for (j = 10; j > 8; j--) { + l1.add(j); +} +assert(l1 == [10, 9]); +assert(j == 8); + +// scope test; h not in for condition scope +for (m = 0; m < 5 && h == null; m++) { + int h = 5; +} +assert(m == 5); \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/for/condition_not_bool.ql b/src/test/resources/testsuite/independent/for/condition_not_bool.ql new file mode 100644 index 0000000..a5e7eb1 --- /dev/null +++ b/src/test/resources/testsuite/independent/for/condition_not_bool.ql @@ -0,0 +1,6 @@ +/* +{ + "errCode": "FOR_CONDITION_BOOL_REQUIRED" +} +*/ +for (i = 0; 1+1; false) {} \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/for/for_each.ql b/src/test/resources/testsuite/independent/for/for_each.ql new file mode 100644 index 0000000..f65949f --- /dev/null +++ b/src/test/resources/testsuite/independent/for/for_each.ql @@ -0,0 +1,10 @@ +i = 0; +l = [1,2,3]; +for (ele : l) { + assert(l[i++] == ele); +} + +j = 0; +for (int ele : l) { + assert(l[j++] == ele); +} \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/for/for_each_break_continue.ql b/src/test/resources/testsuite/independent/for/for_each_break_continue.ql new file mode 100644 index 0000000..515a50b --- /dev/null +++ b/src/test/resources/testsuite/independent/for/for_each_break_continue.ql @@ -0,0 +1,29 @@ +globalI = 0; +for (int i: [0,1,2,3,4]) { + globalI = i; + if (i == 2) { + break; + } +} +assert(globalI == 2); + +sum = 0; +for (i: [0,1,2,3,4]) { + if (i == 2) { + continue; + } + sum += i; +} +assert(sum == 8); + +sum = 0; +for (i: [0,1,2,3,4]) { + if (i == 2) { + if (i == 2) { + continue; + } + } + sum += i; +} +assert(sum == 8); + diff --git a/src/test/resources/testsuite/independent/for/for_each_invalid_type.ql b/src/test/resources/testsuite/independent/for/for_each_invalid_type.ql new file mode 100644 index 0000000..1e2fcb2 --- /dev/null +++ b/src/test/resources/testsuite/independent/for/for_each_invalid_type.ql @@ -0,0 +1,10 @@ +/* +{ + "errCode": "FOR_EACH_TYPE_MISMATCH" +} +*/ +a = [1,2,"abc"]; + +for (int b : a) { + c = 2 +} \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/for/for_each_not_iterable.ql b/src/test/resources/testsuite/independent/for/for_each_not_iterable.ql new file mode 100644 index 0000000..2f4003c --- /dev/null +++ b/src/test/resources/testsuite/independent/for/for_each_not_iterable.ql @@ -0,0 +1,7 @@ +/* +{ + "errCode": "FOR_EACH_ITERABLE_REQUIRED" +} +*/ +for (c : 100) { +} \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/for/infinite_loop.ql b/src/test/resources/testsuite/independent/for/infinite_loop.ql new file mode 100644 index 0000000..2a6a995 --- /dev/null +++ b/src/test/resources/testsuite/independent/for/infinite_loop.ql @@ -0,0 +1,15 @@ +i = 0; +for (;;) { + if (i > 3) { + break; + } + i++; +} +assert(i == 4); + +for (j = 0; ; j++) { + if (j > 3) { + break; + } +} +assert(j == 4); \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/for/missing_lparen_at_for.ql b/src/test/resources/testsuite/independent/for/missing_lparen_at_for.ql new file mode 100644 index 0000000..6a665f0 --- /dev/null +++ b/src/test/resources/testsuite/independent/for/missing_lparen_at_for.ql @@ -0,0 +1,6 @@ +/* +{ + "errCode": "SYNTAX_ERROR" +} +*/ +for int i;;;) \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/for/missing_rparen_after_for_update.ql b/src/test/resources/testsuite/independent/for/missing_rparen_after_for_update.ql new file mode 100644 index 0000000..da2f162 --- /dev/null +++ b/src/test/resources/testsuite/independent/for/missing_rparen_after_for_update.ql @@ -0,0 +1,6 @@ +/* +{ + "errCode": "SYNTAX_ERROR" +} +*/ +for (int i = 0; i < 10; i++ {} \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/for/missing_rparen_at_for_each.ql b/src/test/resources/testsuite/independent/for/missing_rparen_at_for_each.ql new file mode 100644 index 0000000..90be511 --- /dev/null +++ b/src/test/resources/testsuite/independent/for/missing_rparen_at_for_each.ql @@ -0,0 +1,7 @@ +/* +{ + "errCode": "SYNTAX_ERROR" +} +*/ +for (a :[1,2,3] { +} \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/for/missing_semi_after_for_init.ql b/src/test/resources/testsuite/independent/for/missing_semi_after_for_init.ql new file mode 100644 index 0000000..d8eeaa1 --- /dev/null +++ b/src/test/resources/testsuite/independent/for/missing_semi_after_for_init.ql @@ -0,0 +1,6 @@ +/* +{ + "errCode": "SYNTAX_ERROR" +} +*/ +for (i = 0 i < 10; i++) {} \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/for/return_from_for.ql b/src/test/resources/testsuite/independent/for/return_from_for.ql new file mode 100644 index 0000000..37b5fa3 --- /dev/null +++ b/src/test/resources/testsuite/independent/for/return_from_for.ql @@ -0,0 +1,13 @@ +function test(l) { + for (o:l) { + if (o == 10) { + return "find" + o; + } + } +} + +r1 = test([3,4,10]); +assert(r1 == 'find10'); + +r2 = test([3,4,11]); +assert(r2 == null); \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/function/complex_parameters.ql b/src/test/resources/testsuite/independent/function/complex_parameters.ql new file mode 100644 index 0000000..4696f9b --- /dev/null +++ b/src/test/resources/testsuite/independent/function/complex_parameters.ql @@ -0,0 +1,67 @@ +// Test functions with various parameter types and complexity +assert(processData(5, true) == 15) +assert(calculateComplex(1.5, 2.5, 10) == 160.0) + +// Test function calls with complex expressions as parameters +assert(mathOperations(add(2, 3), multiply(2, 2)) == 625) // 5^4 = 625 + +function processData(int count, boolean flag) { + int result = count * 2; + if (flag) { + result = result + 5; + } + return result; +} + +function calculateComplex(double x, double y, int multiplier) { + double base = x + y; + return base * base * multiplier; +} + +function mathOperations(int a, int b) { + return power(a, b); +} + +function add(int x, int y) { + return x + y; +} + +function multiply(int x, int y) { + return x * y; +} + +function power(int base, int exp) { + if (exp == 0) { + return 1; + } + int result = 1; + for (int i = 0; i < exp; i++) { + result *= base; + } + return result; +} + +// Test functions with no parameters +assert(getConstant() == 42) +assert(generateRandom() > 0) + +function getConstant() { + return 42; +} + +function generateRandom() { + // Simple pseudo-random using current execution context + return 123; // For deterministic testing +} + +// Test function overloading-like behavior with different parameter counts +assert(calculate(5) == 25) +assert(calculate2(5, 3) == 39) // 5*5 + 3*3 + 5 = 25 + 9 + 5 = 39 + +function calculate(int x) { + return x * x; +} + +function calculate2(int x, int y) { + return x * x + y * y + 5; +} \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/function/edge_cases.ql b/src/test/resources/testsuite/independent/function/edge_cases.ql new file mode 100644 index 0000000..1462523 --- /dev/null +++ b/src/test/resources/testsuite/independent/function/edge_cases.ql @@ -0,0 +1,100 @@ +// Test edge cases for function hoisting +// Test function called immediately at global level before definition +int immediateResult = callImmediately(); +assert(immediateResult == 42) + +// Test function in conditional blocks called before definition +if (true) { + assert(conditionalFunction(5) == 10) +} + +// Test function in loop called before definition +for (int i = 0; i < 2; i++) { + assert(loopFunction(i) == i * 3) +} + +// Test function with same name as built-in (should work) +assert(toString(123) == "custom_123") + +// Test function that returns function call result +assert(chainReturn() == 999) + +// Function definitions (after all calls) +function callImmediately() { + return 42; +} + +function conditionalFunction(int x) { + return x * 2; +} + +function loopFunction(int x) { + return x * 3; +} + +function toString(int value) { + return "custom_" + value; +} + +function chainReturn() { + return getSpecialValue(); +} + +function getSpecialValue() { + return 999; +} + +// Test empty function +assert(emptyFunction() == null) + +function emptyFunction() { + // Empty body +} + +// Test function that just returns constant +assert(constantFunction() == "CONSTANT") + +function constantFunction() { + return "CONSTANT"; +} + +// Test functions with early returns +assert(earlyReturnFunction(true) == 1) +assert(earlyReturnFunction(false) == 2) + +function earlyReturnFunction(boolean condition) { + if (condition) { + return 1; + } + return 2; +} + +// Test function calling itself indirectly (through another function) +assert(indirectSelfCall(3) == 6) + +function indirectSelfCall(int n) { + if (n <= 0) { + return 0; + } + return n + helperForIndirect(n - 1); +} + +function helperForIndirect(int n) { + return indirectSelfCall(n); +} + +// Test function with multiple return types based on conditions +function dynamicReturn(int choice) { + if (choice == 1) { + return 100; + } else if (choice == 2) { + return "text"; + } else { + return true; + } +} + +// Test the dynamic returns +assert(dynamicReturn(1) == 100) +assert(dynamicReturn(2) == "text") +assert(dynamicReturn(3) == true) \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/function/function_call.ql b/src/test/resources/testsuite/independent/function/function_call.ql new file mode 100644 index 0000000..daa1310 --- /dev/null +++ b/src/test/resources/testsuite/independent/function/function_call.ql @@ -0,0 +1,19 @@ +function add(int a, int b) { + return a+b; +} + +assert(add(1,1)==2) + +function sub(a, b) { + return a-b; +} + +assert(sub(3,1)==2) + +assertErrorCode(() -> {add(1, "2")}, "INVALID_ARGUMENT") + +assert(check(3,1)==false) + +function check(a, b) { + return a < b; +} diff --git a/src/test/resources/testsuite/independent/function/function_scoping.ql b/src/test/resources/testsuite/independent/function/function_scoping.ql new file mode 100644 index 0000000..125a735 --- /dev/null +++ b/src/test/resources/testsuite/independent/function/function_scoping.ql @@ -0,0 +1,84 @@ +// Test function scoping and parameter shadowing +int outerVariable = 10; + +// Test function with same parameter name as global variable +assert(testScoping(5) == 15) + +// Test that global variable is not affected +assert(outerVariable == 10) + +function testScoping(int outerVariable) { + // Parameter shadows global variable + return outerVariable + 10; +} + +// Test nested scoping with local variables +assert(nestedScoping(3) == 18) + +function nestedScoping(int x) { + int localVar = x * 2; // 6 + if (localVar > 5) { + int innerVar = localVar * 2; // 12 + return innerVar + 6; // 18 + } + return localVar; +} + +// Test functions that call other functions with same parameter names +assert(chainedScoping(2) == 14) + +function chainedScoping(int value) { + return helperFunction(value + 1); +} + +function helperFunction(int value) { + // Different 'value' parameter + return value * 4 + 2; // (2+1) * 4 + 2 = 14 +} + +// Test function with multiple parameters having local scope +assert(multipleParams(1, 2, 3) == 12) + +function multipleParams(int a, int b, int c) { + int sum = a + b + c; + int doubled = sum * 2; + return doubled; +} + +// Test function that modifies parameters (local copies) +int originalValue = 5; +assert(modifyParameter(originalValue) == 25) +assert(originalValue == 5) // Original should remain unchanged + +function modifyParameter(int param) { + param = param * 5; // Modifying local copy + return param; +} + +// Test function with conditional blocks and local variables +assert(conditionalScoping(true, 10) == 30) +assert(conditionalScoping(false, 10) == 0) // 10 - 10 = 0 + +function conditionalScoping(boolean condition, int base) { + int result = base; + if (condition) { + int bonus = 20; + result = result + bonus; + } else { + int penalty = 10; + result = result - penalty; + } + return result; +} + +// Test loops with local scope +assert(loopScoping(3) == 6) + +function loopScoping(int count) { + int total = 0; + for (int i = 1; i <= count; i++) { + int squared = i; // Local to loop iteration + total += squared; + } + return total; // 1 + 2 + 3 = 6 +} \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/function/invalid_argument.ql b/src/test/resources/testsuite/independent/function/invalid_argument.ql new file mode 100644 index 0000000..78de2ac --- /dev/null +++ b/src/test/resources/testsuite/independent/function/invalid_argument.ql @@ -0,0 +1,10 @@ +/* +{ + "errCode": "INVALID_ARGUMENT" +} +*/ +function add(a, int b) { + return a + b; +} + +add("aaa", "ffff"); \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/function/mixed_declarations.ql b/src/test/resources/testsuite/independent/function/mixed_declarations.ql new file mode 100644 index 0000000..2e7da8e --- /dev/null +++ b/src/test/resources/testsuite/independent/function/mixed_declarations.ql @@ -0,0 +1,54 @@ +// Test mixed function and variable declarations with forward references +int globalVar = computeInitialValue(); +int message = formatMessage(42, getValue()); + +assert(globalVar == 100) +assert(message == 92) // 42 + 50 = 92 + +// Variables referencing functions before they're defined +int result1 = doubleValue(25); +assert(result1 == 50) + +function computeInitialValue() { + return 100; +} + +function formatMessage(int prefix, int value) { + return prefix + value; +} + +function getValue() { + return 50; +} + +function doubleValue(int x) { + return x * 2; +} + +// Test functions that modify and return based on global state +int counter = 0; + +function increment() { + counter = counter + 1; + return counter; +} + +function getCounterValue() { + return counter; +} + +// Test the counter functions +int first = increment(); // Should be 1 +int second = increment(); // Should be 2 +int current = getCounterValue(); // Should be 2 + +assert(first == 1) +assert(second == 2) +assert(current == 2) + +// Test simple calculation with constants +function simpleCalculation() { + return 10 * 30; // 300 +} + +assert(simpleCalculation() == 300) \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/function/multi_call.ql b/src/test/resources/testsuite/independent/function/multi_call.ql new file mode 100644 index 0000000..f80f3fd --- /dev/null +++ b/src/test/resources/testsuite/independent/function/multi_call.ql @@ -0,0 +1,15 @@ +function mc() { + return () -> 10; +} + +assert(mc()()==10); + +a = {:}; + +function a() { + return a; +} + +a().b = 10; + +assert(a.b==10); \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/function/nested_function_calls.ql b/src/test/resources/testsuite/independent/function/nested_function_calls.ql new file mode 100644 index 0000000..761a17a --- /dev/null +++ b/src/test/resources/testsuite/independent/function/nested_function_calls.ql @@ -0,0 +1,40 @@ +// Test nested function calls with forward declarations +assert(outerFunc(5) == 25) + +assert(calculateArea(3, 4) == 26) // calls getPerimeter inside - 2*(3+4) + 3*4 = 14 + 12 = 26 + +function outerFunc(x) { + return innerFunc(x) * 5; +} + +function innerFunc(x) { + return x; +} + +function calculateArea(width, height) { + int perimeter = getPerimeter(width, height); + return perimeter + (width * height); +} + +function getPerimeter(w, h) { + return 2 * (w + h); +} + +// Test deeply nested calls +assert(level1(2) == 14) // level4(2)*3-1+2*2 = 6-1+2*2 = 5+2*2 = 7*2 = 14 + +function level1(x) { + return level2(x) * 2; +} + +function level2(x) { + return level3(x) + 2; +} + +function level3(x) { + return level4(x) - 1; +} + +function level4(x) { + return x * 3; +} \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/function/recursive_functions.ql b/src/test/resources/testsuite/independent/function/recursive_functions.ql new file mode 100644 index 0000000..c445d1c --- /dev/null +++ b/src/test/resources/testsuite/independent/function/recursive_functions.ql @@ -0,0 +1,52 @@ +// Test recursive functions with forward declarations +assert(factorial(5) == 120) +assert(fibonacci(6) == 8) +assert(gcd(48, 18) == 6) + +// Forward references to recursive functions +int result1 = factorial(4); +assert(result1 == 24) + +int result2 = fibonacci(7); +assert(result2 == 13) + +function factorial(n) { + if (n <= 1) { + return 1; + } + return n * factorial(n - 1); +} + +function fibonacci(n) { + if (n <= 1) { + return n; + } + return fibonacci(n - 1) + fibonacci(n - 2); +} + +function gcd(a, b) { + if (b == 0) { + return a; + } + return gcd(b, a % b); +} + +// Test mutually recursive functions called before definition +assert(isEven(10) == true) +assert(isOdd(10) == false) +assert(isEven(7) == false) +assert(isOdd(7) == true) + +function isEven(n) { + if (n == 0) { + return true; + } + return isOdd(n - 1); +} + +function isOdd(n) { + if (n == 0) { + return false; + } + return isEven(n - 1); +} \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/function/return_left_value.ql b/src/test/resources/testsuite/independent/function/return_left_value.ql new file mode 100644 index 0000000..25ef14d --- /dev/null +++ b/src/test/resources/testsuite/independent/function/return_left_value.ql @@ -0,0 +1,20 @@ +map = {a:1, b:123} + +function returnLeftValue() { + return map.b; +} + +c = returnLeftValue(); +assert(c == 123); +// c's modification will not effect m.b +c = 190; +assert(map.b == 123); + +function returnEmbedLeftValue() { + return () -> map.b; +} + +c = returnEmbedLeftValue()(); +assert(c == 123); +c = 190; +assert(map.b == 123); \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/if/if_as_expr.ql b/src/test/resources/testsuite/independent/if/if_as_expr.ql new file mode 100644 index 0000000..02d2173 --- /dev/null +++ b/src/test/resources/testsuite/independent/if/if_as_expr.ql @@ -0,0 +1,16 @@ +a = if (11 == 11) { + 10 +} else { + 20 + 2 +} + 1; +b = if (a == 11) 20 else 9; +c = if (a != 11) 11 else 12; +println(b); +assert(b == 20); +assert(c == 12); + +assert(if (20==20) { + 11 == 11 +}); + +assert(if (20==20) 11 == 11); \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/if/if_condition_not_bool.ql b/src/test/resources/testsuite/independent/if/if_condition_not_bool.ql new file mode 100644 index 0000000..07d77ec --- /dev/null +++ b/src/test/resources/testsuite/independent/if/if_condition_not_bool.ql @@ -0,0 +1,8 @@ +/* +{ + "errCode": "CONDITION_BOOL_REQUIRED" +} +*/ +if (1) { + return 2; +} \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/if/if_else_if.ql b/src/test/resources/testsuite/independent/if/if_else_if.ql new file mode 100644 index 0000000..8f21e10 --- /dev/null +++ b/src/test/resources/testsuite/independent/if/if_else_if.ql @@ -0,0 +1,8 @@ +a = 11; +if (a >= 0 && a < 5) { + assert(false); +} else if (a >= 5 && a < 10) { + assert(false); +} else if (a >= 10 && a < 15) { + assert(true); +} \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/if/if_else_miss_body.ql b/src/test/resources/testsuite/independent/if/if_else_miss_body.ql new file mode 100644 index 0000000..782aa79 --- /dev/null +++ b/src/test/resources/testsuite/independent/if/if_else_miss_body.ql @@ -0,0 +1,8 @@ +/* +{ + "errCode": "SYNTAX_ERROR" +} +*/ +if (1>2) { + return 10; +} else \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/if/if_followed_by_cast.ql b/src/test/resources/testsuite/independent/if/if_followed_by_cast.ql new file mode 100644 index 0000000..5e8de38 --- /dev/null +++ b/src/test/resources/testsuite/independent/if/if_followed_by_cast.ql @@ -0,0 +1,3 @@ +if(true) { +} +((long)2); \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/if/if_miss_body.ql b/src/test/resources/testsuite/independent/if/if_miss_body.ql new file mode 100644 index 0000000..d06d3de --- /dev/null +++ b/src/test/resources/testsuite/independent/if/if_miss_body.ql @@ -0,0 +1,6 @@ +/* +{ + "errCode": "SYNTAX_ERROR" +} +*/ +if (1>2) \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/if/if_with_one_statement_body.ql b/src/test/resources/testsuite/independent/if/if_with_one_statement_body.ql new file mode 100644 index 0000000..2efdfa6 --- /dev/null +++ b/src/test/resources/testsuite/independent/if/if_with_one_statement_body.ql @@ -0,0 +1,6 @@ +b = () -> if (a != 100) + return 11; +else + return 12; +; +assert(b() == 11); \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/if/if_without_condition.ql b/src/test/resources/testsuite/independent/if/if_without_condition.ql new file mode 100644 index 0000000..3ae8e9c --- /dev/null +++ b/src/test/resources/testsuite/independent/if/if_without_condition.ql @@ -0,0 +1,7 @@ +/* +{ + "errCode": "SYNTAX_ERROR" +} +*/ +if () { +} \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/if/if_without_condition_2.ql b/src/test/resources/testsuite/independent/if/if_without_condition_2.ql new file mode 100644 index 0000000..db4d526 --- /dev/null +++ b/src/test/resources/testsuite/independent/if/if_without_condition_2.ql @@ -0,0 +1,6 @@ +/* +{ + "errCode": "SYNTAX_ERROR" +} +*/ +if( \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/if/miss_if_lparen.ql b/src/test/resources/testsuite/independent/if/miss_if_lparen.ql new file mode 100644 index 0000000..d2c06f0 --- /dev/null +++ b/src/test/resources/testsuite/independent/if/miss_if_lparen.ql @@ -0,0 +1,6 @@ +/* +{ + "errCode": "SYNTAX_ERROR" +} +*/ +if a( \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/if/miss_if_rparen.ql b/src/test/resources/testsuite/independent/if/miss_if_rparen.ql new file mode 100644 index 0000000..6cd63b6 --- /dev/null +++ b/src/test/resources/testsuite/independent/if/miss_if_rparen.ql @@ -0,0 +1,6 @@ +/* +{ + "errCode": "SYNTAX_ERROR" +} +*/ +if (a>10; \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/if/return_at_if.ql b/src/test/resources/testsuite/independent/if/return_at_if.ql new file mode 100644 index 0000000..3e8c2c4 --- /dev/null +++ b/src/test/resources/testsuite/independent/if/return_at_if.ql @@ -0,0 +1,14 @@ +function returnFromIf(a) { + int i = if (a > 10) { + return 100; + } else { + if (a < 5) { + return 1000; + } + return 101; + }; +} + +assert(returnFromIf(11) == 100); +assert(returnFromIf(5) == 101); +assert(returnFromIf(-5) == 1000) \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/if/simple_if.ql b/src/test/resources/testsuite/independent/if/simple_if.ql new file mode 100644 index 0000000..0676cb7 --- /dev/null +++ b/src/test/resources/testsuite/independent/if/simple_if.ql @@ -0,0 +1,27 @@ +int a = 10; +if (a > 9) { + a = 11; +} else { + a = 5; +} + +assert(a == 11); + +b = 5; +if (a > 20) { + b = 90; +} +assert(b == 5); + +if (b==5) a = 90 else a = 900; + +assert(a == 90); + +if (b==5) { + int m = 100; +} + +if (mmm != null) { +} else { + int mmm = 201; +} \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/lambda/invalid_argument.ql b/src/test/resources/testsuite/independent/lambda/invalid_argument.ql new file mode 100644 index 0000000..aecb6cf --- /dev/null +++ b/src/test/resources/testsuite/independent/lambda/invalid_argument.ql @@ -0,0 +1,9 @@ +/* +{ + "errCode": "INVALID_ARGUMENT" +} +*/ +add = (a, int b) -> { + return a + b; +}; +add('aa', 'bbb'); \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/lambda/invalid_argument_call.ql b/src/test/resources/testsuite/independent/lambda/invalid_argument_call.ql new file mode 100644 index 0000000..038f3b1 --- /dev/null +++ b/src/test/resources/testsuite/independent/lambda/invalid_argument_call.ql @@ -0,0 +1,10 @@ +/* +{ + "errCode": "INVALID_ARGUMENT" +} +*/ +a = () -> { + return (int c) -> c + 1; +}; + +a()("abc"); \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/lambda/lambda_doc.ql b/src/test/resources/testsuite/independent/lambda/lambda_doc.ql new file mode 100644 index 0000000..30be04c --- /dev/null +++ b/src/test/resources/testsuite/independent/lambda/lambda_doc.ql @@ -0,0 +1,5 @@ +add = (a, b) -> { + return a + b; +}; +i = add(1,2); +assert(i == 3); \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/lambda/lambda_return.ql b/src/test/resources/testsuite/independent/lambda/lambda_return.ql new file mode 100644 index 0000000..7a1aa7f --- /dev/null +++ b/src/test/resources/testsuite/independent/lambda/lambda_return.ql @@ -0,0 +1,7 @@ +add = (a, int b) -> { + return a + b; +}; +i = add(1,2); +assert(i == 3); +j = add(4,5); +assert(j == 9); \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/lambda/simple_lambda.ql b/src/test/resources/testsuite/independent/lambda/simple_lambda.ql new file mode 100644 index 0000000..b178be5 --- /dev/null +++ b/src/test/resources/testsuite/independent/lambda/simple_lambda.ql @@ -0,0 +1,10 @@ +exprLambda = () -> 12; +assert(exprLambda() == 12); + +blockLambda = () -> { + return 6 + 6; +}; +assert(blockLambda() == 12); + +emptyLambda = () -> {}; +assert(emptyLambda() == null); \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/lambda/unmatch_param_num.ql b/src/test/resources/testsuite/independent/lambda/unmatch_param_num.ql new file mode 100644 index 0000000..fb34718 --- /dev/null +++ b/src/test/resources/testsuite/independent/lambda/unmatch_param_num.ql @@ -0,0 +1,3 @@ +l = (a,b) -> a + b; + +assert(l("abc-") == "abc-null"); \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/macro/empty_macro.ql b/src/test/resources/testsuite/independent/macro/empty_macro.ql new file mode 100644 index 0000000..1d88a92 --- /dev/null +++ b/src/test/resources/testsuite/independent/macro/empty_macro.ql @@ -0,0 +1,9 @@ +macro empty { +} + +function func() { + 1+1; + empty; +} + +assert(func() == null); \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/macro/invalid_macro_name.ql b/src/test/resources/testsuite/independent/macro/invalid_macro_name.ql new file mode 100644 index 0000000..34d38e9 --- /dev/null +++ b/src/test/resources/testsuite/independent/macro/invalid_macro_name.ql @@ -0,0 +1,7 @@ +/* +{ + "errCode": "SYNTAX_ERROR" +} +*/ +macro if { +} \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/macro/macro.ql b/src/test/resources/testsuite/independent/macro/macro.ql new file mode 100644 index 0000000..a4ac872 --- /dev/null +++ b/src/test/resources/testsuite/independent/macro/macro.ql @@ -0,0 +1,26 @@ +// tag::addMacroInScript[] +macro add { + c = a + b; +} + +a = 1; +b = 2; +add; +assert(c == 3); +// end::addMacroInScript[] +b = 10; +add; +assert(c == 11); +// variable has the same name with macro +add = 100; +a = 3; +add; +assert(c == 13); +assert(add == 100); + +// expression auto return +function macroReturn(a, b) { + add +} + +assert(macroReturn(6,7)==13) \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/macro/macro_break_continue.ql b/src/test/resources/testsuite/independent/macro/macro_break_continue.ql new file mode 100644 index 0000000..7318b86 --- /dev/null +++ b/src/test/resources/testsuite/independent/macro/macro_break_continue.ql @@ -0,0 +1,12 @@ +macro bc { + if (i < 5) { + continue; + } +} + +s = 0; +for (int i = 0; i < 10; i++) { + bc; + s++; +} +assert(s == 5) \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/macro/macro_control_flow.ql b/src/test/resources/testsuite/independent/macro/macro_control_flow.ql new file mode 100644 index 0000000..15070f1 --- /dev/null +++ b/src/test/resources/testsuite/independent/macro/macro_control_flow.ql @@ -0,0 +1,16 @@ +macro control { + if (i > 3) { + return; + } +}; + +t = -1; +forBody = (i) -> { + control; + t = i; +}; + +forBody(10); +assert(t == -1); +forBody(2); +assert(t == 2); \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/macro/macro_define_in_sub_scope.ql b/src/test/resources/testsuite/independent/macro/macro_define_in_sub_scope.ql new file mode 100644 index 0000000..669887f --- /dev/null +++ b/src/test/resources/testsuite/independent/macro/macro_define_in_sub_scope.ql @@ -0,0 +1,17 @@ +function testMacroInSubScope() { + macro add { + int c = a + b; + } + int a = 1; + int b = 10; + add; + return c; +} + +c = testMacroInSubScope() +assert(c==11) +a = 11 +b = 100 +// add is not a macro in this scope +add +assert(c==11) \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/macro/macro_return.ql b/src/test/resources/testsuite/independent/macro/macro_return.ql new file mode 100644 index 0000000..3dcad3a --- /dev/null +++ b/src/test/resources/testsuite/independent/macro/macro_return.ql @@ -0,0 +1,9 @@ +macro test { + 1+1 +} + +l = () -> { + test +}; + +assert(l() == 2) \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/macro/missing_lbrace.ql b/src/test/resources/testsuite/independent/macro/missing_lbrace.ql new file mode 100644 index 0000000..8cd4a35 --- /dev/null +++ b/src/test/resources/testsuite/independent/macro/missing_lbrace.ql @@ -0,0 +1,6 @@ +/* +{ + "errCode": "SYNTAX_ERROR" +} +*/ +macro m a=1 \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/map/colon_absent_between_map_entry.ql b/src/test/resources/testsuite/independent/map/colon_absent_between_map_entry.ql new file mode 100644 index 0000000..8fab17a --- /dev/null +++ b/src/test/resources/testsuite/independent/map/colon_absent_between_map_entry.ql @@ -0,0 +1,9 @@ +/* +{ + "errCode": "SYNTAX_ERROR" +} +*/ +map = { + aa: 111 + bb: 222 +}; \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/map/colon_absent_in_entry.ql b/src/test/resources/testsuite/independent/map/colon_absent_in_entry.ql new file mode 100644 index 0000000..2c5d203 --- /dev/null +++ b/src/test/resources/testsuite/independent/map/colon_absent_in_entry.ql @@ -0,0 +1,9 @@ +/* +{ + "errCode": "SYNTAX_ERROR" +} +*/ +{ + aa: 123, + bb 444 +} \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/map/invalid_map_key.ql b/src/test/resources/testsuite/independent/map/invalid_map_key.ql new file mode 100644 index 0000000..58a017c --- /dev/null +++ b/src/test/resources/testsuite/independent/map/invalid_map_key.ql @@ -0,0 +1,8 @@ +/* +{ + "errCode": "SYNTAX_ERROR" +} +*/ +{ + 12 : 'aa' +} \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/map/key_word_can_not_get_from_field.ql b/src/test/resources/testsuite/independent/map/key_word_can_not_get_from_field.ql new file mode 100644 index 0000000..d546c6b --- /dev/null +++ b/src/test/resources/testsuite/independent/map/key_word_can_not_get_from_field.ql @@ -0,0 +1,9 @@ +/* +{ + "errCode": "SYNTAX_ERROR" +} +*/ +map = { + if: 1 +}; +assert(map.if == 1); \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/map/keyword_key_map.ql b/src/test/resources/testsuite/independent/map/keyword_key_map.ql new file mode 100644 index 0000000..4b2f1d9 --- /dev/null +++ b/src/test/resources/testsuite/independent/map/keyword_key_map.ql @@ -0,0 +1,8 @@ +map = { + if: 1, + else: 2, + int: 3 +}; +assert(map['if'] == 1); +assert(map['else'] == 2); +assert(map['int'] == 3); \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/map/map_at_block.ql b/src/test/resources/testsuite/independent/map/map_at_block.ql new file mode 100644 index 0000000..c681d13 --- /dev/null +++ b/src/test/resources/testsuite/independent/map/map_at_block.ql @@ -0,0 +1,7 @@ +m = { + { + mmm: 111, + ccc: 222 + } +}; +assert(m.mmm == 111); \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/map/map_define.ql b/src/test/resources/testsuite/independent/map/map_define.ql new file mode 100644 index 0000000..e5fff18 --- /dev/null +++ b/src/test/resources/testsuite/independent/map/map_define.ql @@ -0,0 +1,31 @@ +address = { + 'owner': 'cole', + age: 30, + contacts: [ + { + name: 'cassandra', + phoneNumber: '0000000' + }, + { + name: 'cole', + phoneNumber: '1111111' + } + ] +}; +assert(address['owner'] == 'cole'); +assert(address['age'] == 30); +assert(address.contacts[0].phoneNumber == '0000000'); + +List addressBook = [address, {owner: 'john'}]; + +assert(addressBook[0].owner == 'cole'); +assert(addressBook[1].owner == 'john'); + +empty = {:}; +assert(empty.a == null); + +extra_comma_map = { + "test_id" : "acd", + "cc_id" : "ttt", +} +assert(extra_comma_map.test_id == "acd") \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/map/string_literal_as_field_access.ql b/src/test/resources/testsuite/independent/map/string_literal_as_field_access.ql new file mode 100644 index 0000000..89da0c5 --- /dev/null +++ b/src/test/resources/testsuite/independent/map/string_literal_as_field_access.ql @@ -0,0 +1,6 @@ +assert({"门店 test": 1234}.'门店 test' == 1234) + +a = {"门店 test": 1234, "a b c d": 'oopp'} +assert(a.'门店 test' == 1234) +assert(a.'a b c d' == 'oopp') + diff --git a/src/test/resources/testsuite/independent/newlines/newlines.ql b/src/test/resources/testsuite/independent/newlines/newlines.ql new file mode 100644 index 0000000..4bd0051 --- /dev/null +++ b/src/test/resources/testsuite/independent/newlines/newlines.ql @@ -0,0 +1,88 @@ +function testAdd( + int a, int b + , int c, int d, + int e +) { +} + +a = (int a + , int b, int c, + int d, int e) -> + { + a + } + +try { +} catch (int a) { +} catch (Object b) { +} finally { +} + +assert( + a(1,2 + ,3, + 4,5,6) == + 1 +) + +m = +[ + 1,2 + ,3 + ,4,5, +] + +assert(m[ + 1 + : + 3 +]==[2,3]) + +assert(m[ + 2 + + 1 +] == 4) + +new ArrayList( + 10 +) + +a = 3 +b = new int[ + a+1 +][ + 9 +] + +c = new int[] { + 1,2 + ,3,4, + 5 +} +assert(c[2]==3) + +int ddd = +c[3], +eee = 10 +, mmm = 90; + +Map< + String + ,Map + > +> map = +{ + 'aaa': + {'bbb':['ccc']} +}; + +Map< +> map2 = { + : +}; + +f = true ? + 10: + 11 +assert(f==10) \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/number/number.ql b/src/test/resources/testsuite/independent/number/number.ql new file mode 100644 index 0000000..2629404 --- /dev/null +++ b/src/test/resources/testsuite/independent/number/number.ql @@ -0,0 +1,14 @@ +assert(-1==-1) +assert(17 == 0x11); +assert(0x11 == 021); +assert(021 == 0b10001); +assert(-17 == -0x11); +assert(-0x11 == -021); +assert(-021 == -0b10001); +assert(.0 == 0.); +assert(1l == 1L); +assert(1f == 1F); +assert(13.45d == 1.345e1); +assert(13.45d == 1.345e+1); +assert(13.45d == 134.5e-1); +assert(0 == 0); \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/number/precise.ql b/src/test/resources/testsuite/independent/number/precise.ql new file mode 100644 index 0000000..f7e6daa --- /dev/null +++ b/src/test/resources/testsuite/independent/number/precise.ql @@ -0,0 +1,4 @@ +assert(123456789.123456789+987654321.987654321==1111111111.11111111) +assert(123456789012345678901234567890*987654321098765432109876543210==121932631137021795226185032733622923332237463801111263526900) +assert(123456789.123456789/0.000000001==123456789123456789) +assert((123456789.123456789 + 987654321.987654321) * (1 - 0.000000001) / 2==555555554.999999999444444445) \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/operator/big_decimal.ql b/src/test/resources/testsuite/independent/operator/big_decimal.ql new file mode 100644 index 0000000..2c9e171 --- /dev/null +++ b/src/test/resources/testsuite/independent/operator/big_decimal.ql @@ -0,0 +1,87 @@ +/* +{ + "qlOptions": QLOptions.builder().precise(true) +} +*/ + +// plus +x = 0.1 + 1.1; +assert(x instanceof BigDecimal) +assert(x == 1.2); + +x = 3 + 2.2 +assert(x == 5.2) +assert(x instanceof BigDecimal) + +x = 2.2 + 4 +assert(x instanceof BigDecimal) +assert(x == 6.2) + +y = x + 1 +assert(y instanceof BigDecimal) +assert(y == 7.2) + +z = y + x + 1 + 2 +assert(z instanceof BigDecimal) +assert(z == 16.4) + +// minus +x = 1.1 - 0.01 +assert(x == 1.09) + +x = 6 - 2.2 +assert(x == 3.8) + +x = 5.8 - 2 +assert(x == 3.8) + +y = x - 1 +assert(y == 2.8) + +// multiply +x = 3 * 2.0 +assert(x == 6.0) + +x = 3.0 * 2 +assert(x == 6.0) + +x = 3.0 * 2.0 +assert(x == 6.0) + +y = x * 2 +assert(y == 12.0) + +y = 11 * 3.333 +assert(y == 36.663) + +y = 3.333 * 11 +assert(y == 36.663) + +// divide +x = 80.0 / 4 +assert(x == 20.0 , "x = " + x) + +x = 80 / 4.0 +assert(x == 20.0 , "x = " + x) + +y = x / 2 +assert(y == 10.0 , "y = " + y) +assert(y == 10 , "y = " + y) + +y = 34 / 3.000; +assert(y == 11.3333333333); + +y = 34.00000000000 / 3; +assert(y == 11.3333333333); + +// remainder +x = 100.0 % 3 +assert(x == 1) + +y = 5.5 +y %= 2.0 +assert(y == 1.5) + +y = -5.5 +y %= 2.0 +assert(y == -1.5) \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/operator/big_integer.ql b/src/test/resources/testsuite/independent/operator/big_integer.ql new file mode 100644 index 0000000..dbac005 --- /dev/null +++ b/src/test/resources/testsuite/independent/operator/big_integer.ql @@ -0,0 +1,58 @@ +// assign +BigInteger bi; +bi = (byte) 20; +assert(bi instanceof BigInteger); +assert(bi == 20); + +bi = (short) 20 +assert(bi instanceof BigInteger) +assert(bi == 20) + +bi = (int) 20 +assert(bi instanceof BigInteger) +assert(bi == 20) + +bi = (long) 20 +assert(bi instanceof BigInteger) +assert(bi == 20) + +bi = (float) 0.5f +assert(bi instanceof BigInteger) +assert(bi == 0) + +bi = (double) 0.5d +assert(bi instanceof BigInteger) +assert(bi == 0) + +bi = 10.5 +assert(bi instanceof BigInteger) +assert(bi == 10) + +double d; +d = 1000; +d *= d +d *= d +d *= d +assert((long)d != d) +assert((BigInteger) d == d) + +// plus +x = BigInteger.valueOf(2) + BigInteger.valueOf(3) +assert(x instanceof BigInteger) +assert(x == 5) + +// multiply +x = BigInteger.valueOf(2) * BigInteger.valueOf(3) +assert(x instanceof BigInteger) +assert(x == 6) + +// remainder +x = BigInteger.valueOf(100) % 3 +assert(x == 1) + +y = BigInteger.valueOf(11) +y %= 3 +assert(y == 2) +y = BigInteger.valueOf(-11) +y %= 3 +assert(y == -2) \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/operator/bitwise.ql b/src/test/resources/testsuite/independent/operator/bitwise.ql new file mode 100644 index 0000000..b210c46 --- /dev/null +++ b/src/test/resources/testsuite/independent/operator/bitwise.ql @@ -0,0 +1,145 @@ +assert(true & true); +assertFalse(true & false); +assertFalse(true & null); + +assertFalse(false & true); +assertFalse(false & false); +assertFalse(false & null); + +assert(true | true); +assert(true | false); +assert(true | null); + +assert(false | true); +assertFalse(false | false); +assertFalse(false | null); + +assertFalse(true ^ true); +assert(true ^ false); +assert(true ^ null); + +assert(false ^ true); +assertFalse(false ^ false); +assertFalse(false ^ null); + +// bitwise shift +a = 4; +b = -4; +assert(a << 1 == 8); +assert(a << 2 == 16); +assert(a >> 1 == 2); +assert(a >> 2 == 1); +assert(a >>> 1 == 2); +assert(a >>> 2 == 1); +assert(b << 1 == -8); +assert(b << 2 == -16); +assert(b >> 1 == -2); +assert(b >> 2 == -1); +assert(b >>> 1 == 0x7FFFFFFE); +assert(b >>> 2 == 0x3FFFFFFF); + +assertErrorCode(()-> {8.0F >> 2}, "EXECUTE_OPERATOR_EXCEPTION") +assertErrorCode(()-> {8 >> 2.0}, "EXECUTE_OPERATOR_EXCEPTION") + +// bitwise shift equal +a = 4; +a <<= 1; +assert(a == 8); +a <<= 2; +assert(a == 32); +a >>= 1; +assert(a == 16); +a >>= 2; +assert(a == 4); + +b = -4; +b <<= 1; +assert(b == -8); +b <<= 2; +assert(b == -32); +b >>= 1; +assert(b == -16); +b >>= 2; +assert(b == -4); + +b = -4; +b >>>= 1; +assert(b == 0x7FFFFFFE); +b = -8; +b >>>= 2; +assert(b == 0x3FFFFFFE); + +// bitwise and +a = 13; +assert((a & 3) == 1); // 0x0000000D & 0x00000003 +assert((a & 7) == 5); // 0x0000000D & 0x00000007 +b = -13; +assert((b & 3) == 3); // 0xFFFFFFF3 & 0x00000003 +assert((b & 7) == 3); // 0xFFFFFFF3 & 0x00000007 + +// bitwise and equals +a = 13; +a &= 3; +assert(a == 1); // 0x0000000D & 0x00000003 + +a &= 4; +assert(a == 0); // 0x00000001 & 0x00000004 + +b = -13; +b &= 3; +assert(b == 3); // 0xFFFFFFF3 & 0x00000003 + +b &= 7; +assert(b == 3); // 0x00000003 & 0x00000007 + +// bitwise or +a = 13; +assert((a | 8) == 13); // 0x0000000D | 0x00000008 +assert((a | 16) == 29); // 0x0000000D | 0x00000010 +b = -13; +assert((b | 8) == -5); // 0xFFFFFFF3 | 0x00000008 +assert((b | 16) == -13); // 0xFFFFFFF3 | 0x00000010 + +// bitwise or equal +a = 13; +a |= 2; +assert(a == 15); // 0x0000000D | 0x00000002 +a |= 16; +assert(a == 31); // 0x0000000F | 0x0000001F +b = -13; +b |= 8; +assert(b == -5); // 0xFFFFFFF3 | 0x00000008 +b |= 1; +assert(b == -5); // 0xFFFFFFFB | 0x00000001 + +// bitwise xor +a = 13; +assert((a ^ 10) == 7); // 0x0000000D ^ 0x0000000A = 0x000000007 +assert((a ^ 15) == 2); // 0x0000000D ^ 0x0000000F = 0x000000002 +b = -13; +assert((b ^ 10) == -7); // 0xFFFFFFF3 ^ 0x0000000A = 0xFFFFFFF9 +assert((b ^ 15) == -4); // 0xFFFFFFF3 ^ 0x0000000F = 0xFFFFFFFC + +// bitwise xor equal +a = 13; +a ^= 8; +assert(a == 5); // 0x0000000D ^ 0x00000008 = 0x000000005 +a ^= 16 +assert(a == 21); // 0x00000005 ^ 0x00000010 = 0x000000015 +b = -13; +b ^= 8; +assert(b == -5); // 0xFFFFFFF3 ^ 0x00000008 = 0xFFFFFFFB +b ^= 16; +assert(b == -21); // 0xFFFFFFFB ^ 0x00000010 = 0xFFFFFFEB + +// bitwise negation +assert(~1 == -2); // ~0x00000001 = 0xFFFFFFFE +assert(~(-1) == 0); // ~0xFFFFFFFF = 0x00000000 +assert(~(~5) == 5); // ~~0x00000005 = ~0xFFFFFFFA = 0xFFFFFFF5 +a = 13; +assert(~a == -14); // ~0x0000000D = 0xFFFFFFF2 +assert(~(~a) == 13); // ~~0x0000000D = ~0xFFFFFFF2 = 0x0000000D +assert(-(~a) == 14); // -~0x0000000D = -0xFFFFFFF2 = 0x0000000E + +// bitwise exception +assertErrorCode(()-> {~"8"}, "INVALID_UNARY_OPERAND") \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/operator/boolean.ql b/src/test/resources/testsuite/independent/operator/boolean.ql new file mode 100644 index 0000000..d7a72a3 --- /dev/null +++ b/src/test/resources/testsuite/independent/operator/boolean.ql @@ -0,0 +1,107 @@ +// comparison +assert(true) +assert(true != false) + +x = true +assert(x) +assert(x == true) +assert(x != false) + +x = false +assert(x == false) +assert(x != true) +assert(!x) + +y = false +assert(x == y) + +y = true +assert(x != y) + +// if branch +x = false +r = false +if (x) { + // ignore +} +else { + r = true +} +assert(r) + +x = true +r = false +if (x) { + r = true +} +else { + // ignore +} +assert(r) + +if (!x) { + r = false +} +else { + r = true +} +assert(r) + +// expression +x = 5 +value = x > 2 +assert(value) + +value = x < 2 +assert(value == false) + +// ops +boolean x = true; +boolean y = false; +assert((x & x) == true) +assert((x & y) == false) +assert((y & x) == false) +assert((y & y) == false) + +assert((x | x) == true) +assert((x | y) == true) +assert((y | x) == true) +assert((y | y) == false) + +assert((x ^ x) == false) +assert((x ^ y) == true) +assert((y ^ x) == true) +assert((y ^ y) == false) + +assert((!x) == false) +assert((!y) == true) + +// assign ops +boolean z = true; +z &= true +assert(z == true) +z &= false +assert(z == false) + +z = true +z |= true +assert(z == true) +z |= false +assert(z == true) +z = false +z |= false +assert(z == false) +z |= true +assert(z == true) + +z = true +z ^= true +assert(z == false) +z ^= true +assert(z == true) +z ^= false +assert(z == true) +z ^= true +assert(z == false) +z ^= false +assert(z == false) \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/operator/character.ql b/src/test/resources/testsuite/independent/operator/character.ql new file mode 100644 index 0000000..b2c9bd2 --- /dev/null +++ b/src/test/resources/testsuite/independent/operator/character.ql @@ -0,0 +1,18 @@ +assert(98 > (char)'a'); +assert(98 == (char)'b'); +assert(98 < (char)'c'); + +assert((char)'a' < 98); +assert((char)'b' == 98); +assert((char)'c' > 98); + +assert(98 != (char)'a'); +assert(98 <> (char)'a'); +assert((char)'a' != 98); +assert((char)'a' <> 98); + +assert((char)'a' < (char)'b'); +assert((char)'b' == (char)'b'); +assert((char)'c' > (char)'b'); + +assertErrorCode(() -> {'测试一下' > 1}, "INVALID_BINARY_OPERAND") \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/operator/comparable.ql b/src/test/resources/testsuite/independent/operator/comparable.ql new file mode 100644 index 0000000..53743d3 --- /dev/null +++ b/src/test/resources/testsuite/independent/operator/comparable.ql @@ -0,0 +1,13 @@ +import com.alibaba.qlexpress4.inport.Person; + +p1 = new Person(10) +p2 = new Person(20) +assert(p1 < p2) +assert(p1 <= p2) +assert(p1 != p2) +assert(p1 <> p2) + +assert(p2 > p1) +assert(p2 >= p1) +assert(p2 != p1) +assert(p2 <> p1) \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/operator/double.ql b/src/test/resources/testsuite/independent/operator/double.ql new file mode 100644 index 0000000..c430fdd --- /dev/null +++ b/src/test/resources/testsuite/independent/operator/double.ql @@ -0,0 +1,58 @@ +// plus +x = 2.1d + 2.1d +assert(x == 4.2d) + +x = 3d + 2.2d +assert(x == 5.2d) + +x = 2.2d + 4d +assert(x == 6.2d) + +y = x + 1d +assert(y == 7.2d) + +z = y + x + 1d + 2d +assert(z == 16.4d) + +// minus +x = 6d - 2.2d +assert(x == 3.8d) + +x = 5.8d - 2d +assert(x == 3.8d) + +y = x - 1d +assert(y == 2.8d) + +// multiply +x = 3d * 2.0d +assert(x == 6.0d) + +x = 3.0d * 2d +assert(x == 6.0d) + +x = 3.0d * 2.0d +assert(x == 6.0d) +y = x * 2d +assert(y == 12.0d) + +// divide +x = 80.0d / 4d +assert(x == 20.0d, "x = " + x) + +x = 80d / 4.0d +assert(x == 20.0d, "x = " + x) + +y = x / 2d +assert(y == 10.0d, "y = " + y) + +// remainder +x = 100d % 3 +assert(x == 1d) + +y = 11d +y %= 3d +assert(y == 2d) +y = -11d +y %= 3d +assert(y == -2d) diff --git a/src/test/resources/testsuite/independent/operator/equals.ql b/src/test/resources/testsuite/independent/operator/equals.ql new file mode 100644 index 0000000..6bcdd52 --- /dev/null +++ b/src/test/resources/testsuite/independent/operator/equals.ql @@ -0,0 +1,34 @@ +assert(0 == 0); +assert(512 == 512); +assert(512 == 512L); +assert(512 == 512F); +assert(512 == 512D); + +assert(512L == 512L); +assert(512L == 512F); +assert(512L == 512D); + +assert(512F == 512F); +assert(512F == 512D); + +assert(512D == 512D); + +assertFalse(512 == 513); + +assert((char)'a' == 97); +assert(97 == (char)'a'); +assertFalse((char)'b' == 97); +assertFalse(97 == (char)'b'); + +assert((char)'b' != 97); +assert(97 != (char)'b'); +assertFalse((char)'a' != 97); +assertFalse(97 != (char)'a'); + +assert((char)'b' <> 97); +assert(97 <> (char)'b'); +assertFalse((char)'a' <> 97); +assertFalse(97 <> (char)'a'); + +assert(null == null); +assertFalse(null != null); diff --git a/src/test/resources/testsuite/independent/operator/in_not_in.ql b/src/test/resources/testsuite/independent/operator/in_not_in.ql new file mode 100644 index 0000000..206931d --- /dev/null +++ b/src/test/resources/testsuite/independent/operator/in_not_in.ql @@ -0,0 +1,59 @@ +assert(null in null); +assertFalse(null in "abc"); +assertFalse(null in 123); + +assertFalse(null not_in null); +assert(null not_in "abc"); +assert(null not_in 123); + +assertFalse(null in [1, 2, 3]); +assertFalse(null in new int[]{1, 2, 3}); + +assert(null not_in [1, 2, 3]); +assert(null not_in new int[]{1, 2, 3}); + +assertFalse(null in ["abc", "def", "ghi"]); +assertFalse(null in new String[]{"abc", "def", "ghi"}); + +assert(null not_in ["abc", "def", "ghi"]); +assert(null not_in new String[]{"abc", "def", "ghi"}); + +assert(1 in [1, 2, 3]); +assert(1 in new int[]{1, 2, 3}); + +assertFalse(1 not_in [1, 2, 3]); +assertFalse(1 not_in new int[]{1, 2, 3}); + +assertFalse(1 in ["1", "2", "3"]); +assertFalse(1 in new String[]{"1", "2", "3"}); + +assert(1 not_in ["1", "2", "3"]); +assert(1 not_in new String[]{"1", "2", "3"}); + +assert("abc" in ["abc", "def", "ghi"]); +assert("abc" in new String[]{"abc", "def", "ghi"}); + +assertFalse("abc" not_in ["abc", "def", "gcpghi"]); +assertFalse("abc" not_in new String[]{"abc", "def", "ghi"}); + +assertFalse("bcd" in ["abc", "def", "ghi"]); +assertFalse("bcd" in new String[]{"abc", "def", "ghi"}); + +assert("bcd" not_in ["abc", "def", "ghi"]); +assert("bcd" not_in new String[]{"abc", "def", "ghi"}); + +assert("bc" in "abc"); +assert("bc" in "bcd"); +assert("bc" in "abcd"); +assertFalse("bc" in "ab"); +assertFalse("bc" in "cd"); +assertFalse("bc" in "abd"); +assertFalse("bc" in "acd"); + +assertFalse("bc" not_in "abc"); +assertFalse("bc" not_in "bcd"); +assertFalse("bc" not_in "abcd"); +assert("bc" not_in "ab"); +assert("bc" not_in "cd"); +assert("bc" not_in "abd"); +assert("bc" not_in "acd"); \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/operator/integer.ql b/src/test/resources/testsuite/independent/operator/integer.ql new file mode 100644 index 0000000..e61fa7e --- /dev/null +++ b/src/test/resources/testsuite/independent/operator/integer.ql @@ -0,0 +1,147 @@ +// plus +x = 2 + 2 +assert(x == 4) + +y = x + 1 +assert(y == 5) + +z = y + x + 1 + 2 +assert(z == 12) + +// unary plus +x = 3 +y = +x +assert(y == 3) + +// character plus +char c1 = 1; +char c2 = 2; + +x = c2 + 2 +assert(x == 4) + +x = 2 + c2 +assert(x == 4) + +x = c2 + c2 +assert(x == 4) + +y = x + c1 +assert(y == 5) + +y = c1 + x +assert(y == 5) + +z = y + x + c1 + 2 +assert(z == 12) + +z = y + x + 1 + c2 +assert(z == 12) + +z = y + x + c1 + c2 +assert(z == 12) + +// minus +x = 6 - 2 +assert(x == 4) + +x = 6 +x -= 2 +assert(x == 4) + +y = x - 1 +assert(y == 3) + +// unary minus +x = 3 +y = -x +assert(y == -3) + +// bitwise negate +x = 3 +y = ~x +assert(y == -4) + +// character minus +Character c1 = 1; +Character c2 = 2; +Character c6 = 6; + +x = c6 - 2 +assert(x == 4) + +x = 6 - c2 +assert(x == 4) + +x = c6 - c2 +assert(x == 4) + +y = x - c1 +assert(y == 3) + +// multiply +x = 3 * 2 +assert(x == 6) + +y = x * 2 +assert(y == 12) + +// divide +x = 80 / 4 +assert(x == 20.0) + +x = 80 +x /= 4 +assert(x == 20.0) + +y = x / 2 +assert(y == 10.0) + +// remainder +x = 100 % 3 +assert(x == 1) + +y = 11 +y %= 3 +assert(y == 2) + +y = -11 +y %= 3 +assert(y == -2) + +// and +x = 1 & 3 +assert(x == 1) + +// or +x = 1 | 3 +assert(x == 3) + +// shift operator +x = 8 >> 1 +assert(x == 4) +assert(x instanceof Integer) + +x = 8 << 2 +assert(x == 32) +assert(x instanceof Integer) + +x = 8L << 2 +assert(x == 32) +assert(x instanceof Long) + +x = -16 >> 4 +assert(x == -1) + +x = -16 >>> 4 +assert(x == 0xFFFFFFF) + +//Ensure that the type of the right operand (shift distance) is ignored when calculating the +//result. This is how java works, and for these operators, it makes sense to keep that behavior. +x = Integer.MAX_VALUE << 1L +assert(x == -2) +assert(x instanceof Integer) + +x = new Long(Integer.MAX_VALUE).longValue() << 1 +assert(x == 0xfffffffe) +assert(x instanceof Long) \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/operator/like.ql b/src/test/resources/testsuite/independent/operator/like.ql new file mode 100644 index 0000000..f7e9f4b --- /dev/null +++ b/src/test/resources/testsuite/independent/operator/like.ql @@ -0,0 +1,20 @@ +assert(null like null); +assertFalse(null not_like null); + +assertFalse("a" like null); +assert("a" not_like null); + +assert("1006" like "%6"); +assert("1006" like "1%"); +assert("ABCD" like "A%B%D"); + +assertFalse("1006" not_like "%6"); +assertFalse("1006" not_like "1%"); +assertFalse("ABCD" not_like "A%B%D"); + +// error code +assertErrorCode(() -> {"ABCD" like 200}, "INVALID_BINARY_OPERAND"); +assertErrorCode(() -> {200 like "200"}, "INVALID_BINARY_OPERAND"); + +assertErrorCode(() -> {"ABCD" not_like 200}, "INVALID_BINARY_OPERAND"); +assertErrorCode(() -> {200 not_like "200"}, "INVALID_BINARY_OPERAND"); \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/operator/logic.ql b/src/test/resources/testsuite/independent/operator/logic.ql new file mode 100644 index 0000000..c2c18bd --- /dev/null +++ b/src/test/resources/testsuite/independent/operator/logic.ql @@ -0,0 +1,14 @@ +assert(true && true); +assertFalse(true && false); +assertFalse(true && null); +assert(true and true); +assertFalse(true and false); +assertFalse(true and null); + +assertFalse(false && true); +assertFalse(false && false); +assertFalse(false && null); + +assertFalse(null && true); +assertFalse(null && false); +assertFalse(null && null); diff --git a/src/test/resources/testsuite/independent/operator/optional_chaining.ql b/src/test/resources/testsuite/independent/operator/optional_chaining.ql new file mode 100644 index 0000000..73c1f59 --- /dev/null +++ b/src/test/resources/testsuite/independent/operator/optional_chaining.ql @@ -0,0 +1,14 @@ +assert(a?.b?.c?.d == null); + +try { + assert(a?.b.c == null); + throw new RuntimeException(); +} catch (e) { + assert(e instanceof NullPointerException); +} + +mm = {cc: 123} + +assert(mm?.cc == 123); +assert(mm.dd?.ee == null); +assert(mm.dd?.test() == null); \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/operator/string.ql b/src/test/resources/testsuite/independent/operator/string.ql new file mode 100644 index 0000000..066f9c7 --- /dev/null +++ b/src/test/resources/testsuite/independent/operator/string.ql @@ -0,0 +1,19 @@ +x = "hello " + "there" +assert(x == "hello there") + +x = "hello " + 2 +assert(x == "hello 2") + +x = "hello " + 1.2 +assert(x == "hello 1.2") + +y = x + 1 +assert(y == "hello 1.21") + +x = "hello" + " " + "there" + " nice" + " day" +assert(x == "hello there nice day") + +assert("bc" > "ab") +assert("bc" > "ab") +assert("bcd" > "ab") +assert("bcd" > "abc") \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/scope/block_scope.ql b/src/test/resources/testsuite/independent/scope/block_scope.ql new file mode 100644 index 0000000..0be15cf --- /dev/null +++ b/src/test/resources/testsuite/independent/scope/block_scope.ql @@ -0,0 +1,6 @@ +{ + int b = 12; + a = 100 +} +assert(a + "-" + b == '100-null'); + diff --git a/src/test/resources/testsuite/independent/scope/global_variable.ql b/src/test/resources/testsuite/independent/scope/global_variable.ql new file mode 100644 index 0000000..f6de498 --- /dev/null +++ b/src/test/resources/testsuite/independent/scope/global_variable.ql @@ -0,0 +1,8 @@ +function setA(value) { + a = value; +} + +a = 10; +assert(a == 10); +setA(10000); +assert(a == 10000); \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/scope/lexical_scope.ql b/src/test/resources/testsuite/independent/scope/lexical_scope.ql new file mode 100644 index 0000000..5a82558 --- /dev/null +++ b/src/test/resources/testsuite/independent/scope/lexical_scope.ql @@ -0,0 +1,14 @@ +String a = "lexical first"; + +aFactory = () -> a; + +{ + String a = "runtime first-block"; + assert(aFactory() == "lexical first"); +} + +function testScope(a) { + assert(aFactory() == "lexical first"); +} + +testScope("runtime first-function"); \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/scope/scope_cover.ql b/src/test/resources/testsuite/independent/scope/scope_cover.ql new file mode 100644 index 0000000..fa62a6e --- /dev/null +++ b/src/test/resources/testsuite/independent/scope/scope_cover.ql @@ -0,0 +1,10 @@ +a = 10; + +assert(a == 10); + +{ + int a = 100; + assert(a == 100); +} + +assert(a == 10); \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/spread/arr_spread.ql b/src/test/resources/testsuite/independent/spread/arr_spread.ql new file mode 100644 index 0000000..4757bda --- /dev/null +++ b/src/test/resources/testsuite/independent/spread/arr_spread.ql @@ -0,0 +1,8 @@ +Map[] arr = new Map[]{{"a":1},{"a":2}}; +assert(arr*.a==[1,2]) + +Map[] arr1 = new Map[]{{"a":1},{"a":2}, null}; +assertErrorCode(() -> arr1*.a, "NULL_FIELD_ACCESS") + +Map[] b = new Map[]{{"get100": () -> 100}, null}; +assertErrorCode(() -> b*.get100(), "NULL_METHOD_ACCESS") \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/spread/list_spread.ql b/src/test/resources/testsuite/independent/spread/list_spread.ql new file mode 100644 index 0000000..5e1d8b3 --- /dev/null +++ b/src/test/resources/testsuite/independent/spread/list_spread.ql @@ -0,0 +1,46 @@ +// tag::spreadExample[] +list = [ + { + "name": "Li", + "age": 10 + }, + { + "name": "Wang", + "age": 15 + } +] + +// get field from list +assert(list*.age==[10,15]) + +mm = { + "aaa": 1, + "bbb": 2 +} + +// get map key value list +assert(mm*.key==["aaa", "bbb"]) +assert(mm*.value==[1, 2]) +// end::spreadExample[] + +methodMaps = [ + { + "getNum": () -> 100, + }, + { + "getNum": () -> 101, + }, + { + "getNum": () -> 102, + } +] + +assert(methodMaps*.getNum()==[100,101,102]) + +a = [{"c":2}, null] +assertErrorCode(() -> a*.c, "NULL_FIELD_ACCESS") +assertErrorCode(() -> notExist*.c, "NONTRAVERSABLE_OBJECT") + +b = [{"get100": () -> 100}, null] +assertErrorCode(() -> b*.get100(), "NULL_METHOD_ACCESS") +assertErrorCode(() -> notExist*.get100(), "NONTRAVERSABLE_OBJECT") \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/spread/spread_avoid_null.ql b/src/test/resources/testsuite/independent/spread/spread_avoid_null.ql new file mode 100644 index 0000000..a84c23b --- /dev/null +++ b/src/test/resources/testsuite/independent/spread/spread_avoid_null.ql @@ -0,0 +1,18 @@ +/* +{ + "qlOptions": QLOptions.builder().avoidNullPointer(true) +} +*/ +a = [{"c":2}, null] +assert(a*.c==[2, null]) +assert(notExist*.c==null) +assert(notExist*.c()==null) + +b = [{"get100": () -> 100}, null] +assert(b*.get100()==[100, null]) + +Map[] arr1 = new Map[]{{"a":1},{"a":2}, null}; +assert(arr1*.a==[1,2,null]) + +Map[] brr = new Map[]{{"get100": () -> 100}, null}; +assert(brr*.get100()==[100,null]) \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/string/char.ql b/src/test/resources/testsuite/independent/string/char.ql new file mode 100644 index 0000000..184e92f --- /dev/null +++ b/src/test/resources/testsuite/independent/string/char.ql @@ -0,0 +1,5 @@ +char a = 'a'; +char b = "a"; +assert(a instanceof Character); +assert(b instanceof Character); +assert(a == b); \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/string/interpolation.ql b/src/test/resources/testsuite/independent/string/interpolation.ql new file mode 100644 index 0000000..acd4e12 --- /dev/null +++ b/src/test/resources/testsuite/independent/string/interpolation.ql @@ -0,0 +1,37 @@ +a = 123; +b = "test" + +assert("Hello ${a} ${b } ccc" == "Hello 123 test ccc"); +// $ escape +assert("Hello \${a bb cc" == 'Hello ${a bb cc') +// selector variable +assert(${a} == 123) + +assert("${a-1}" == "122") + +assert("m xx ${ + if (b like 't%') { + 'YYY' + } +}" == "m xx YYY") + +assert("m xx ${ + if (b like 't%') { + "YYY" + } +}" == "m xx YYY") + +// nest interpolation +assert("m xx ${ + if (b like 't%') { + "YY${b}Y" + } +}" == "m xx YYtestY") + +assert("m xx ${ + if (b like 'mm%') { + 'YYY' + } +}" == "m xx null") + + diff --git a/src/test/resources/testsuite/independent/string/invalid_char.ql b/src/test/resources/testsuite/independent/string/invalid_char.ql new file mode 100644 index 0000000..4dd9a79 --- /dev/null +++ b/src/test/resources/testsuite/independent/string/invalid_char.ql @@ -0,0 +1,7 @@ +/* +{ + "errCode": "INCOMPATIBLE_ASSIGNMENT_TYPE" +} +*/ +char a = 'aa'; +println(a); \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/string/literal.ql b/src/test/resources/testsuite/independent/string/literal.ql new file mode 100644 index 0000000..b8db3aa --- /dev/null +++ b/src/test/resources/testsuite/independent/string/literal.ql @@ -0,0 +1 @@ +assert('Hello World' == "Hello World") \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/string/string_escape.ql b/src/test/resources/testsuite/independent/string/string_escape.ql new file mode 100644 index 0000000..f324fa6 --- /dev/null +++ b/src/test/resources/testsuite/independent/string/string_escape.ql @@ -0,0 +1,13 @@ +assert('\' \\r \'' == "' \\r '") + +assert('hello +world' == "hello\nworld") + +a = "hello +qlexpress" +assert(a == "hello\nqlexpress") + +assert("hello + +qlexpress" == "hello +\nqlexpress") \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/string/string_not_close.ql b/src/test/resources/testsuite/independent/string/string_not_close.ql new file mode 100644 index 0000000..fc95ca6 --- /dev/null +++ b/src/test/resources/testsuite/independent/string/string_not_close.ql @@ -0,0 +1,6 @@ +/* +{ + "errCode": "SYNTAX_ERROR" +} +*/ +a = "abc \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/ternary/missing_colon.ql b/src/test/resources/testsuite/independent/ternary/missing_colon.ql new file mode 100644 index 0000000..3a3a4b6 --- /dev/null +++ b/src/test/resources/testsuite/independent/ternary/missing_colon.ql @@ -0,0 +1,6 @@ +/* +{ + "errCode": "SYNTAX_ERROR" +} +*/ +a = x > 10? 10; \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/ternary/ternary.ql b/src/test/resources/testsuite/independent/ternary/ternary.ql new file mode 100644 index 0000000..f5061e4 --- /dev/null +++ b/src/test/resources/testsuite/independent/ternary/ternary.ql @@ -0,0 +1,17 @@ +l = (x) -> x > 10? 11: 100; +assert(l(11) == 11); +assert(l(5) == 100); + +l1 = (x) -> x > 100? 101: x > 50? 51: 11; +assert(l1(120) == 101); +assert(l1(99) == 51); +assert(l1(15) == 11); + +l2 = x -> x <= 10? -9: x < 20? 19: 11; +assert(l2(1) == -9); +assert(l2(17) == 19); +assert(l2(29) == 11); + +l3 = true? a = 100: b = 200; +assert(a == 100); +assert(b == null); \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/timeout/timeout.ql b/src/test/resources/testsuite/independent/timeout/timeout.ql new file mode 100644 index 0000000..183b94e --- /dev/null +++ b/src/test/resources/testsuite/independent/timeout/timeout.ql @@ -0,0 +1,9 @@ +/* +{ + "qlOptions": QLOptions.builder().timeoutMillis(10), + "errCode": "SCRIPT_TIME_OUT" +} +*/ +while (true) { + 1+1 +} \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/trycatch/catch_order.ql b/src/test/resources/testsuite/independent/trycatch/catch_order.ql new file mode 100644 index 0000000..6462109 --- /dev/null +++ b/src/test/resources/testsuite/independent/trycatch/catch_order.ql @@ -0,0 +1,9 @@ +a = try { + throw 10; +} catch (int a) { + 100 +} catch (int b) { + 1000 +} + +assert(a == 100) \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/trycatch/missing_lbrace_at_try.ql b/src/test/resources/testsuite/independent/trycatch/missing_lbrace_at_try.ql new file mode 100644 index 0000000..b8907da --- /dev/null +++ b/src/test/resources/testsuite/independent/trycatch/missing_lbrace_at_try.ql @@ -0,0 +1,6 @@ +/* +{ + "errCode": "SYNTAX_ERROR" +} +*/ +try 1+1 \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/trycatch/missing_lbrace_at_try_finally.ql b/src/test/resources/testsuite/independent/trycatch/missing_lbrace_at_try_finally.ql new file mode 100644 index 0000000..21b1d2a --- /dev/null +++ b/src/test/resources/testsuite/independent/trycatch/missing_lbrace_at_try_finally.ql @@ -0,0 +1,10 @@ +/* +{ + "errCode": "SYNTAX_ERROR" +} +*/ +try { + 2+1 +} catch(Object o) { + +} finally } \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/trycatch/multi_exception_catch.ql b/src/test/resources/testsuite/independent/trycatch/multi_exception_catch.ql new file mode 100644 index 0000000..7ec3d3e --- /dev/null +++ b/src/test/resources/testsuite/independent/trycatch/multi_exception_catch.ql @@ -0,0 +1,18 @@ +function f(x) { + try { + throw x; + } catch (int | long i) { + assert(i == x); + } +} + +f(1); + +f(100L); + +try { + f(1.1d); + assert(false); +} catch (double d) { + assert(d == 1.1d); +} diff --git a/src/test/resources/testsuite/independent/trycatch/return_from_try.ql b/src/test/resources/testsuite/independent/trycatch/return_from_try.ql new file mode 100644 index 0000000..ebe0527 --- /dev/null +++ b/src/test/resources/testsuite/independent/trycatch/return_from_try.ql @@ -0,0 +1,31 @@ +function tryTest() { + try { + return 10; + } catch (ignore) { + } + return 1000; +} + +assert(tryTest() == 10) + +function catchTest() { + try { + throw 10; + } catch (ignore) { + return 1000; + } + return 10000; +} + +assert(catchTest()==1000) + +function returnInsideFinally() { + try { + return 30; + } catch (ignore) { + } finally { + return 9000; + } +} + +assert(returnInsideFinally() == 30) \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/trycatch/throw_number.ql b/src/test/resources/testsuite/independent/trycatch/throw_number.ql new file mode 100644 index 0000000..65b0785 --- /dev/null +++ b/src/test/resources/testsuite/independent/trycatch/throw_number.ql @@ -0,0 +1,6 @@ +try { + throw 11; + assert(false); +} catch(int i) { + assert(i == 11); +} \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/trycatch/try_catch_expr.ql b/src/test/resources/testsuite/independent/trycatch/try_catch_expr.ql new file mode 100644 index 0000000..92fcfaf --- /dev/null +++ b/src/test/resources/testsuite/independent/trycatch/try_catch_expr.ql @@ -0,0 +1,16 @@ +a = 1 + try { + 100 + 1/0 +} catch(Object e) { + 11 +}; + +assert(a == 12); + +b = 1 + try { + 100 + 1/0 +} catch(Object e) { + 11 +} finally { + 1000 +}; +assert(b == 12); \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/trycatch/try_catch_final_scope.ql b/src/test/resources/testsuite/independent/trycatch/try_catch_final_scope.ql new file mode 100644 index 0000000..66514c9 --- /dev/null +++ b/src/test/resources/testsuite/independent/trycatch/try_catch_final_scope.ql @@ -0,0 +1,11 @@ +int a = 10; + +try { + int a = 1000; + throw new NullPointerException(); + assert(false); +} catch (Object o) { + assert(a == 10); +} finally { + assert(a == 10); +} \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/while/break_continue.ql b/src/test/resources/testsuite/independent/while/break_continue.ql new file mode 100644 index 0000000..cb81ad6 --- /dev/null +++ b/src/test/resources/testsuite/independent/while/break_continue.ql @@ -0,0 +1,18 @@ +i = 0; +while (i < 5) { + if (++i == 2) { + break; + } +} +assert(i == 2); + +sum = 0; +i = 0; +while (i < 5) { + if (i == 2) { + i += 1; + continue; + } + sum += i++; +} +assert(sum == 8); \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/while/condition_not_bool.ql b/src/test/resources/testsuite/independent/while/condition_not_bool.ql new file mode 100644 index 0000000..d8b74d4 --- /dev/null +++ b/src/test/resources/testsuite/independent/while/condition_not_bool.ql @@ -0,0 +1,8 @@ +/* +{ + "errCode": "WHILE_CONDITION_BOOL_REQUIRED" +} +*/ +while (1) { + true; +} \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/while/missing_lparen.ql b/src/test/resources/testsuite/independent/while/missing_lparen.ql new file mode 100644 index 0000000..90b5b4d --- /dev/null +++ b/src/test/resources/testsuite/independent/while/missing_lparen.ql @@ -0,0 +1,7 @@ +/* +{ + "errCode": "SYNTAX_ERROR" +} +*/ +while i < 5 { +} \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/while/missing_rparen.ql b/src/test/resources/testsuite/independent/while/missing_rparen.ql new file mode 100644 index 0000000..fed14c7 --- /dev/null +++ b/src/test/resources/testsuite/independent/while/missing_rparen.ql @@ -0,0 +1,9 @@ +/* +{ + "errCode": "SYNTAX_ERROR" +} +*/ +i = 0; +while (i < 5 { + i++; +} \ No newline at end of file diff --git a/src/test/resources/testsuite/independent/while/while.ql b/src/test/resources/testsuite/independent/while/while.ql new file mode 100644 index 0000000..68d13e2 --- /dev/null +++ b/src/test/resources/testsuite/independent/while/while.ql @@ -0,0 +1,8 @@ +i = 0; +sum = 0; +// m not in scope +while (i < 4 && m == null) { + int m = 10; + sum += (i++); +} +assert(sum==6); \ No newline at end of file diff --git a/src/test/resources/testsuite/java/array/arr_index_out_of_bound.ql b/src/test/resources/testsuite/java/array/arr_index_out_of_bound.ql new file mode 100644 index 0000000..2603964 --- /dev/null +++ b/src/test/resources/testsuite/java/array/arr_index_out_of_bound.ql @@ -0,0 +1,7 @@ +/* +{ + "errCode": "INDEX_OUT_BOUND" +} +*/ +int[] is = new int[0]; +is[1] \ No newline at end of file diff --git a/src/test/resources/testsuite/java/array/arr_literal.ql b/src/test/resources/testsuite/java/array/arr_literal.ql new file mode 100644 index 0000000..4cfb356 --- /dev/null +++ b/src/test/resources/testsuite/java/array/arr_literal.ql @@ -0,0 +1,3 @@ +int[] ii = new int[]{1,2,3}; +assert(ii[2] == 3); +assert(ii[-1] == ii[2]); \ No newline at end of file diff --git a/src/test/resources/testsuite/java/array/arr_with_init_item.ql b/src/test/resources/testsuite/java/array/arr_with_init_item.ql new file mode 100644 index 0000000..3fe35b2 --- /dev/null +++ b/src/test/resources/testsuite/java/array/arr_with_init_item.ql @@ -0,0 +1,6 @@ +int[] os = new int[] {1,2,3,4}; +assert(os[1] == 2); + +int[][] os = new int[][] {new int[] {1,2}, new int[] {3,4}}; +assert(os[0][1] == 2); +assert(os[1][0] == 3); \ No newline at end of file diff --git a/src/test/resources/testsuite/java/array/array_item_type_convert.ql b/src/test/resources/testsuite/java/array/array_item_type_convert.ql new file mode 100644 index 0000000..349741f --- /dev/null +++ b/src/test/resources/testsuite/java/array/array_item_type_convert.ql @@ -0,0 +1,4 @@ +int[] is = new int[] {1L, 5.67}; +assert(is[1] == 5); +is[1] = 4.56d; +assert(is[1] == 4); \ No newline at end of file diff --git a/src/test/resources/testsuite/java/array/array_slice.ql b/src/test/resources/testsuite/java/array/array_slice.ql new file mode 100644 index 0000000..895bf4f --- /dev/null +++ b/src/test/resources/testsuite/java/array/array_slice.ql @@ -0,0 +1,11 @@ +a = new int[] {1, 2, 3, 4}; +assert(a[1:2][0] == 2); +b = a[-88:100]; +assert(b.length == 4); +assert(b[0] == 1); +assert(b[1] == 2); +assert(b[2] == 3); +assert(b[3] == 4); +assert(a[:1].length == 1); +assert(a[:1][0] == 1); +assert(a[3:][0] == 4); \ No newline at end of file diff --git a/src/test/resources/testsuite/java/array/comma_absent.ql b/src/test/resources/testsuite/java/array/comma_absent.ql new file mode 100644 index 0000000..b8c926e --- /dev/null +++ b/src/test/resources/testsuite/java/array/comma_absent.ql @@ -0,0 +1,6 @@ +/* +{ + "errCode": "SYNTAX_ERROR" +} +*/ +new int[] {1 2} \ No newline at end of file diff --git a/src/test/resources/testsuite/java/array/invalid_arr_define.ql b/src/test/resources/testsuite/java/array/invalid_arr_define.ql new file mode 100644 index 0000000..4ab654b --- /dev/null +++ b/src/test/resources/testsuite/java/array/invalid_arr_define.ql @@ -0,0 +1,6 @@ +/* +{ + "errCode": "SYNTAX_ERROR" +} +*/ +int[][] a = new int[][]; \ No newline at end of file diff --git a/src/test/resources/testsuite/java/array/invalid_arr_item.ql b/src/test/resources/testsuite/java/array/invalid_arr_item.ql new file mode 100644 index 0000000..97a3792 --- /dev/null +++ b/src/test/resources/testsuite/java/array/invalid_arr_item.ql @@ -0,0 +1,6 @@ +/* +{ + "errCode": "INCOMPATIBLE_ARRAY_ITEM_TYPE" +} +*/ +List[] s = new List[] {true, false}; \ No newline at end of file diff --git a/src/test/resources/testsuite/java/array/invalid_arr_size_define.ql b/src/test/resources/testsuite/java/array/invalid_arr_size_define.ql new file mode 100644 index 0000000..6432676 --- /dev/null +++ b/src/test/resources/testsuite/java/array/invalid_arr_size_define.ql @@ -0,0 +1,6 @@ +/* +{ + "errCode": "SYNTAX_ERROR" +} +*/ +new int[][10][] \ No newline at end of file diff --git a/src/test/resources/testsuite/java/array/invalid_arr_size_type.ql b/src/test/resources/testsuite/java/array/invalid_arr_size_type.ql new file mode 100644 index 0000000..2684cbe --- /dev/null +++ b/src/test/resources/testsuite/java/array/invalid_arr_size_type.ql @@ -0,0 +1,7 @@ +/* +{ + "errCode": "ARRAY_SIZE_NUM_REQUIRED" +} +*/ +a = 'aaa'; +new int[a]; \ No newline at end of file diff --git a/src/test/resources/testsuite/java/array/multi_dim_array.ql b/src/test/resources/testsuite/java/array/multi_dim_array.ql new file mode 100644 index 0000000..270e2f1 --- /dev/null +++ b/src/test/resources/testsuite/java/array/multi_dim_array.ql @@ -0,0 +1,4 @@ +long[][] ls = new long[2][3]; +assert(ls[0][0] == null); +assert(ls.length == 2); +assert(ls[1].length == 3); \ No newline at end of file diff --git a/src/test/resources/testsuite/java/array/type_arr.ql b/src/test/resources/testsuite/java/array/type_arr.ql new file mode 100644 index 0000000..634754d --- /dev/null +++ b/src/test/resources/testsuite/java/array/type_arr.ql @@ -0,0 +1,2 @@ +Object[] os = new Object[10]; +os.length == 10; \ No newline at end of file diff --git a/src/test/resources/testsuite/java/cast/assignable_cast.ql b/src/test/resources/testsuite/java/cast/assignable_cast.ql new file mode 100644 index 0000000..b2e60d2 --- /dev/null +++ b/src/test/resources/testsuite/java/cast/assignable_cast.ql @@ -0,0 +1,5 @@ +Integer a = 1; +assert((Number) a == 1); + +Number b = 1; +assert((Integer) b == 1); \ No newline at end of file diff --git a/src/test/resources/testsuite/java/cast/define_local_cast.ql b/src/test/resources/testsuite/java/cast/define_local_cast.ql new file mode 100644 index 0000000..ccbc318 --- /dev/null +++ b/src/test/resources/testsuite/java/cast/define_local_cast.ql @@ -0,0 +1,2 @@ +double b = 12; +assert(b instanceof double) \ No newline at end of file diff --git a/src/test/resources/testsuite/java/cast/object_cast.ql b/src/test/resources/testsuite/java/cast/object_cast.ql new file mode 100644 index 0000000..ddb4e6c --- /dev/null +++ b/src/test/resources/testsuite/java/cast/object_cast.ql @@ -0,0 +1,2 @@ +Integer a = 1; +assert((Object) a == 1); \ No newline at end of file diff --git a/src/test/resources/testsuite/java/cast/string_cast.ql b/src/test/resources/testsuite/java/cast/string_cast.ql new file mode 100644 index 0000000..1053520 --- /dev/null +++ b/src/test/resources/testsuite/java/cast/string_cast.ql @@ -0,0 +1,3 @@ +Object a = null; +String b = (String) a; +assertFalse("null".equals(b)); \ No newline at end of file diff --git a/src/test/resources/testsuite/java/for/for_each_array.ql b/src/test/resources/testsuite/java/for/for_each_array.ql new file mode 100644 index 0000000..cfe72f3 --- /dev/null +++ b/src/test/resources/testsuite/java/for/for_each_array.ql @@ -0,0 +1,12 @@ +i = 0; +for (ele: new int[] {1,2,3}) { + assert(ele == (++i)); +} + +nestedArr = new int[][] {new int[] {1,2}, new int[] {3,4}}; +j = 0; +for (arr: nestedArr) { + for (int ele: arr) { + assert(ele == (++j)); + } +} diff --git a/src/test/resources/testsuite/java/generics/generics.ql b/src/test/resources/testsuite/java/generics/generics.ql new file mode 100644 index 0000000..7af7db5 --- /dev/null +++ b/src/test/resources/testsuite/java/generics/generics.ql @@ -0,0 +1,4 @@ +Map m; +Map> m2; +Map> m3; +Map<> m4; \ No newline at end of file diff --git a/src/test/resources/testsuite/java/generics/invalid_type_bound.ql b/src/test/resources/testsuite/java/generics/invalid_type_bound.ql new file mode 100644 index 0000000..ff7fb30 --- /dev/null +++ b/src/test/resources/testsuite/java/generics/invalid_type_bound.ql @@ -0,0 +1,6 @@ +/* +{ + "errCode": "SYNTAX_ERROR" +} +*/ +List l; \ No newline at end of file diff --git a/src/test/resources/testsuite/java/implicit/arithmetic.ql b/src/test/resources/testsuite/java/implicit/arithmetic.ql new file mode 100644 index 0000000..8c3f141 --- /dev/null +++ b/src/test/resources/testsuite/java/implicit/arithmetic.ql @@ -0,0 +1,16 @@ +//trans after arithmetic +byte b1 = 1; +byte b2 = 2; +int i = b1 + b2; +assert(i == 3); + +//trans in arithmetic +int a = 1; +String s = a + "q"; +assert(s.equals("1q")); + +//trans in arithmetic mixed +int imax = Integer.MAX_VALUE; +int imin = Integer.MIN_VALUE; +long s = imax - (long)imin; +assert(s == 4294967295l); diff --git a/src/test/resources/testsuite/java/implicit/assignment_basic.ql b/src/test/resources/testsuite/java/implicit/assignment_basic.ql new file mode 100644 index 0000000..e473433 --- /dev/null +++ b/src/test/resources/testsuite/java/implicit/assignment_basic.ql @@ -0,0 +1,32 @@ +byte a = 0; +assert(a == 0); + +//byte→short +short s = a; +assert(s == 0); + +//byte→char +char r = a; +assert(r == 0); + +//byte→int +int i = a; +assert(i == 0); + +//byte→float +float f = a; +assert(f == 0.0); + +//byte→double +double d = a; +assert(d == 0); + +//byte→long +long l = a; +assert(l == 0); + +//long→double +double d1 = l; +assert(d1 == 0); + + diff --git a/src/test/resources/testsuite/java/implicit/assignment_extend.ql b/src/test/resources/testsuite/java/implicit/assignment_extend.ql new file mode 100644 index 0000000..71cd786 --- /dev/null +++ b/src/test/resources/testsuite/java/implicit/assignment_extend.ql @@ -0,0 +1,43 @@ +//double→(Double)→long(lost precise) +double d2 = 0.1d; +long l2 = d2; +assert(l2 == 0l); + +//double→(Double)→int(lost precise && use max bound) +d2 = 11111111111.1d; +int i1 = d2; +assert(i1 == 2147483647); + +//double→(Double)→int(lost precise && use min bound) +d2 = -11111111111.1d; +int i2 = d2; +assert(i2 == -2147483648); + +//double->BigInteger +BigInteger big = d2; +assert(big == -11111111111); + +//long→BigInteger +BigInteger big = 11111111111l; +assert(big == 11111111111l); + +//long→BigDecimal +BigDecimal big = 11111111111l; +assert(big == 11111111111l); + +//long→BigDecimal +BigDecimal big = 11111111111l; +assert(big == 11111111111l); + + +//long→BigDecimal +BigDecimal big = 11111111111l; +long l1 = big; +assert(l1 == 11111111111l); + +//String→char +String s1 = "a"; +char c1 = s1; +assert(c1 == 97); + + diff --git a/src/test/resources/testsuite/java/implicit/function_param.ql b/src/test/resources/testsuite/java/implicit/function_param.ql new file mode 100644 index 0000000..37bcc1f --- /dev/null +++ b/src/test/resources/testsuite/java/implicit/function_param.ql @@ -0,0 +1,19 @@ +function add(int a, long b){ + return a + b; +}; + +//param trans +assert(add(1L,444L) == 445L); + +//return object +assert(add(Integer.MAX_VALUE,444L) == 2147484091L); + +//result trans +assert(add(1L,444L) == 445d); + +function convertChar(char a) { + return a; +} + +assert(convertChar('a')==(char)'a'); +assert(convertChar((String)'a')==(char)'a'); \ No newline at end of file diff --git a/src/test/resources/testsuite/java/implicit/incompatible_assignment_type.ql b/src/test/resources/testsuite/java/implicit/incompatible_assignment_type.ql new file mode 100644 index 0000000..a5d285f --- /dev/null +++ b/src/test/resources/testsuite/java/implicit/incompatible_assignment_type.ql @@ -0,0 +1,9 @@ +assertErrorCode(() -> { + String s = "as"; + char a = s; +}, "INCOMPATIBLE_ASSIGNMENT_TYPE"); + +assertErrorCode(() -> { + String s = "as"; + s = 1; +}, "INCOMPATIBLE_ASSIGNMENT_TYPE"); \ No newline at end of file diff --git a/src/test/resources/testsuite/java/implicit/packing.ql b/src/test/resources/testsuite/java/implicit/packing.ql new file mode 100644 index 0000000..2836b2e --- /dev/null +++ b/src/test/resources/testsuite/java/implicit/packing.ql @@ -0,0 +1,16 @@ +int i = 1; +Integer ig = i; +assert(ig == 1); +assert(ig instanceof Integer); + +int i1 = ig; +assert(i1 == 1); + +double d = 0.1d; +Double ds = d; +assert(ds == 0.1d); + +float f = 0.22f; +Float f1 = f; +assert(f1 == 0.22f); + diff --git a/src/test/resources/testsuite/java/implicit/pointer.ql b/src/test/resources/testsuite/java/implicit/pointer.ql new file mode 100644 index 0000000..a9acbfc --- /dev/null +++ b/src/test/resources/testsuite/java/implicit/pointer.ql @@ -0,0 +1,6 @@ +//pointer in array +int[] array = new int[]{1,2,3}; +Long[] newArray = new Long[]{array[0], array[1], array[2]}; +assert(newArray[0] == 1); +assert(newArray[1] == 2); +assert(newArray[2] == 3); \ No newline at end of file diff --git a/src/test/resources/testsuite/java/import/import_class.ql b/src/test/resources/testsuite/java/import/import_class.ql new file mode 100644 index 0000000..4a47225 --- /dev/null +++ b/src/test/resources/testsuite/java/import/import_class.ql @@ -0,0 +1,2 @@ +import com.alibaba.qlexpress4.inport.Sample; +assert(Sample.value == 1); \ No newline at end of file diff --git a/src/test/resources/testsuite/java/import/import_not_at_beginning.ql b/src/test/resources/testsuite/java/import/import_not_at_beginning.ql new file mode 100644 index 0000000..b758f98 --- /dev/null +++ b/src/test/resources/testsuite/java/import/import_not_at_beginning.ql @@ -0,0 +1,7 @@ +/* +{ + "errCode": "SYNTAX_ERROR" +} +*/ +a = 10; +import a.b.c; \ No newline at end of file diff --git a/src/test/resources/testsuite/java/import/import_not_end_with_semi.ql b/src/test/resources/testsuite/java/import/import_not_end_with_semi.ql new file mode 100644 index 0000000..21fda6b --- /dev/null +++ b/src/test/resources/testsuite/java/import/import_not_end_with_semi.ql @@ -0,0 +1,7 @@ +/* +{ + "errCode": "SYNTAX_ERROR" +} +*/ +import a.b +v = 1 \ No newline at end of file diff --git a/src/test/resources/testsuite/java/import/import_pack_not_end_with_semi.ql b/src/test/resources/testsuite/java/import/import_pack_not_end_with_semi.ql new file mode 100644 index 0000000..400570a --- /dev/null +++ b/src/test/resources/testsuite/java/import/import_pack_not_end_with_semi.ql @@ -0,0 +1,6 @@ +/* +{ + "errCode": "SYNTAX_ERROR" +} +*/ +import a.b.c.*- \ No newline at end of file diff --git a/src/test/resources/testsuite/java/import/import_package.ql b/src/test/resources/testsuite/java/import/import_package.ql new file mode 100644 index 0000000..9abeb2e --- /dev/null +++ b/src/test/resources/testsuite/java/import/import_package.ql @@ -0,0 +1,3 @@ +import com.alibaba.qlexpress4.inport.*; +assert(Sample.value == 1); +assert(Sample1.value == 10); diff --git a/src/test/resources/testsuite/java/import/import_star.ql b/src/test/resources/testsuite/java/import/import_star.ql new file mode 100644 index 0000000..d26e0fd --- /dev/null +++ b/src/test/resources/testsuite/java/import/import_star.ql @@ -0,0 +1,6 @@ +/* +{ + "errCode": "SYNTAX_ERROR" +} +*/ +import *; \ No newline at end of file diff --git a/src/test/resources/testsuite/java/import/incomplete_import.ql b/src/test/resources/testsuite/java/import/incomplete_import.ql new file mode 100644 index 0000000..c5f6b40 --- /dev/null +++ b/src/test/resources/testsuite/java/import/incomplete_import.ql @@ -0,0 +1,6 @@ +/* +{ + "errCode": "SYNTAX_ERROR" +} +*/ +import a.b. \ No newline at end of file diff --git a/src/test/resources/testsuite/java/import/invalid_package.ql b/src/test/resources/testsuite/java/import/invalid_package.ql new file mode 100644 index 0000000..ad66b99 --- /dev/null +++ b/src/test/resources/testsuite/java/import/invalid_package.ql @@ -0,0 +1,6 @@ +/* +{ + "errCode": "SYNTAX_ERROR" +} +*/ +import a.b.- \ No newline at end of file diff --git a/src/test/resources/testsuite/java/import/multi_import.ql b/src/test/resources/testsuite/java/import/multi_import.ql new file mode 100644 index 0000000..cfc0c12 --- /dev/null +++ b/src/test/resources/testsuite/java/import/multi_import.ql @@ -0,0 +1,4 @@ +import com.alibaba.qlexpress4.inport.Sample; +import com.alibaba.qlexpress4.inport.Sample1; +assert(Sample.value == 1); +assert(Sample1.value == 10); \ No newline at end of file diff --git a/src/test/resources/testsuite/java/import/not_support_import_static.ql b/src/test/resources/testsuite/java/import/not_support_import_static.ql new file mode 100644 index 0000000..2ccd74a --- /dev/null +++ b/src/test/resources/testsuite/java/import/not_support_import_static.ql @@ -0,0 +1,6 @@ +/* +{ + "errCode": "SYNTAX_ERROR" +} +*/ +import static ab.Assert.*; \ No newline at end of file diff --git a/src/test/resources/testsuite/java/lambda/java_functional_interface.ql b/src/test/resources/testsuite/java/lambda/java_functional_interface.ql new file mode 100644 index 0000000..aaac84e --- /dev/null +++ b/src/test/resources/testsuite/java/lambda/java_functional_interface.ql @@ -0,0 +1,16 @@ +Runnable r = () -> a = 8; +r.run(); +assert(a == 8); + +Supplier s = () -> "test"; +assert(s.get() == 'test'); + +Consumer c = (a) -> b = a + "-te"; +c.accept("ccc"); +assert(b == 'ccc-te'); + +Function f = a -> a + 3; +assert(f.apply(1) == 4); + +Function f1 = (a, b) -> a + b; +assert(f1.apply("test-") == "test-null"); \ No newline at end of file diff --git a/src/test/resources/testsuite/java/lambda/lambda_implicit.ql b/src/test/resources/testsuite/java/lambda/lambda_implicit.ql new file mode 100644 index 0000000..3872170 --- /dev/null +++ b/src/test/resources/testsuite/java/lambda/lambda_implicit.ql @@ -0,0 +1,5 @@ +a = { + func: (long c) -> c + 1 +}; + +assert(a.func(1) == 2L); \ No newline at end of file diff --git a/src/test/resources/testsuite/java/lambda/lambda_method.ql b/src/test/resources/testsuite/java/lambda/lambda_method.ql new file mode 100644 index 0000000..047f87d --- /dev/null +++ b/src/test/resources/testsuite/java/lambda/lambda_method.ql @@ -0,0 +1,5 @@ +a = { + func: (int c) -> c + 1 +}; + +assert(a.func(1) == 2); \ No newline at end of file diff --git a/src/test/resources/testsuite/java/lambda/user_functional_interface.ql b/src/test/resources/testsuite/java/lambda/user_functional_interface.ql new file mode 100644 index 0000000..013d43c --- /dev/null +++ b/src/test/resources/testsuite/java/lambda/user_functional_interface.ql @@ -0,0 +1,4 @@ +import com.alibaba.qlexpress4.test.lambda.UserFunctionalInterface; + +UserFunctionalInterface ufi = (a, b) -> a + b; +assert(ufi.lala(1,2) == 3); \ No newline at end of file diff --git a/src/test/resources/testsuite/java/map/classified_json.ql b/src/test/resources/testsuite/java/map/classified_json.ql new file mode 100644 index 0000000..fe190c4 --- /dev/null +++ b/src/test/resources/testsuite/java/map/classified_json.ql @@ -0,0 +1,42 @@ +// tag::classifiedJson[] +myHome = { + '@class': 'com.alibaba.qlexpress4.inport.MyHome', + 'sofa': 'a-sofa', + 'chair': 'b-chair', + 'myDesk': { + 'book1': 'Then Moon and Sixpence', + '@class': 'com.alibaba.qlexpress4.inport.MyDesk' + }, + // ignore field that don't exist + 'notexist': 1234 +} +assert(myHome.getSofa()=='a-sofa') +assert(myHome instanceof com.alibaba.qlexpress4.inport.MyHome) +assert(myHome.getMyDesk().getBook1()=='Then Moon and Sixpence') +assert(myHome.getMyDesk() instanceof com.alibaba.qlexpress4.inport.MyDesk) +// end::classifiedJson[] + +// @class override +myDesk = { + '@class': 'com.alibaba.qlexpress4.inport.MyHome', + 'book1': 'Then Moon and Sixpence', + '@class': 'com.alibaba.qlexpress4.inport.MyDesk' +} +assert(myDesk instanceof com.alibaba.qlexpress4.inport.MyDesk) + +// cls not exist +m = { + '@class': 'notexist', + 'book1': 'Then Moon and Sixpence' +} +assert(m["@class"]=='notexist') +assert(m instanceof Map) + +// invalid assignment +assertErrorCode(() -> { + '@class': 'com.alibaba.qlexpress4.inport.MyHome', + 'sofa': 'a-sofa', + 'chair': 'b-chair', + 'bed': 'c-bed' +}, "INVALID_ASSIGNMENT") + diff --git a/src/test/resources/testsuite/java/map/equal_to_hash_map.ql b/src/test/resources/testsuite/java/map/equal_to_hash_map.ql new file mode 100644 index 0000000..e5161e9 --- /dev/null +++ b/src/test/resources/testsuite/java/map/equal_to_hash_map.ql @@ -0,0 +1,13 @@ +dict = {a: 123, b: 234, c:[1,2,3]}; +map = new HashMap(); +map.put('a', 123); +map.put('b', 234); +map.put('c', [1,2,3]); +assert(dict == map); +assert(dict.get('a') == 123); +assert(dict.a == 123); +assert(dict['a'] == 123); +dict.d = 100; +dict['e'] = 200; +assert(dict.d == 100); +assert(dict.e == 200); \ No newline at end of file diff --git a/src/test/resources/testsuite/java/method/method_invoke.ql b/src/test/resources/testsuite/java/method/method_invoke.ql new file mode 100644 index 0000000..d605a62 --- /dev/null +++ b/src/test/resources/testsuite/java/method/method_invoke.ql @@ -0,0 +1,7 @@ +import com.alibaba.qlexpress4.test.method.TestChild; + +tc = new TestChild(); +assert(tc.get10() == 10); +assert(tc.get10('') == 11); +assert(tc.get1() == 1); +assert(tc.get100() == 100); \ No newline at end of file diff --git a/src/test/resources/testsuite/java/method_reference/class_method.ql b/src/test/resources/testsuite/java/method_reference/class_method.ql new file mode 100644 index 0000000..5fb6de9 --- /dev/null +++ b/src/test/resources/testsuite/java/method_reference/class_method.ql @@ -0,0 +1,8 @@ +// class static method + +a = Integer::max; +assert(a(1,2) == 2); + +// class member method +c = String::concat +assert(c("aa", "-bb") == "aa-bb") \ No newline at end of file diff --git a/src/test/resources/testsuite/java/method_reference/class_obj_method.ql b/src/test/resources/testsuite/java/method_reference/class_obj_method.ql new file mode 100644 index 0000000..84ae6b8 --- /dev/null +++ b/src/test/resources/testsuite/java/method_reference/class_obj_method.ql @@ -0,0 +1,2 @@ +refer = Integer.class::isArray; +assert(!refer()); \ No newline at end of file diff --git a/src/test/resources/testsuite/java/method_reference/method_not_found.ql b/src/test/resources/testsuite/java/method_reference/method_not_found.ql new file mode 100644 index 0000000..d8936d2 --- /dev/null +++ b/src/test/resources/testsuite/java/method_reference/method_not_found.ql @@ -0,0 +1,7 @@ +/* +{ + "errCode": "INVALID_ARGUMENT" +} +*/ +referNotExist = Integer.class::notExist; +referNotExist() \ No newline at end of file diff --git a/src/test/resources/testsuite/java/method_reference/object_method.ql b/src/test/resources/testsuite/java/method_reference/object_method.ql new file mode 100644 index 0000000..26673de --- /dev/null +++ b/src/test/resources/testsuite/java/method_reference/object_method.ql @@ -0,0 +1,5 @@ +import com.alibaba.qlexpress4.test.property.Sample; + +refer = new Sample(3)::getCount; +assert(refer() == 3); +assert(new Sample(3).getCount() == 3); \ No newline at end of file diff --git a/src/test/resources/testsuite/java/newexpr/new_resolver.ql b/src/test/resources/testsuite/java/newexpr/new_resolver.ql new file mode 100644 index 0000000..061e5f2 --- /dev/null +++ b/src/test/resources/testsuite/java/newexpr/new_resolver.ql @@ -0,0 +1,25 @@ +import com.alibaba.qlexpress4.test.constructor.HelloConstructor; +import com.alibaba.qlexpress4.test.constructor.HelloParent; +import com.alibaba.qlexpress4.test.constructor.HelloChild; + +h = new HelloConstructor(new HelloParent()); +assert(h.flag == 0); + +h = new HelloConstructor(new HelloChild()); +assert(h.flag == 1); + +h = new HelloConstructor("s", "s1"); +assert(h.flag == 2); + +h = new HelloConstructor("s"); +assert(h.flag == 3); + +h = new HelloConstructor(new HelloChild(), () -> 12); +assert(h.flag == 4); + +h = new HelloConstructor(new HelloParent(), () -> 12); +assert(h.flag == 5); + +// var args +h = new HelloConstructor(); +assert(h.flag == 2); \ No newline at end of file diff --git a/src/test/resources/testsuite/java/newexpr/noArgument.ql b/src/test/resources/testsuite/java/newexpr/noArgument.ql new file mode 100644 index 0000000..7fda9a6 --- /dev/null +++ b/src/test/resources/testsuite/java/newexpr/noArgument.ql @@ -0,0 +1,3 @@ +a = new HashMap(); +a['m'] = 12; +assert(a['m'] == 12) \ No newline at end of file diff --git a/src/test/resources/testsuite/java/newexpr/no_match_constructor.ql b/src/test/resources/testsuite/java/newexpr/no_match_constructor.ql new file mode 100644 index 0000000..c1f3997 --- /dev/null +++ b/src/test/resources/testsuite/java/newexpr/no_match_constructor.ql @@ -0,0 +1,8 @@ +/* +{ + "errCode": "NO_SUITABLE_CONSTRUCTOR" +} +*/ +import com.alibaba.qlexpress4.test.constructor.HelloConstructor; + +HelloConstructor h = new HelloConstructor(12, 13, "sss"); \ No newline at end of file diff --git a/src/test/resources/testsuite/java/number/long_max_value.ql b/src/test/resources/testsuite/java/number/long_max_value.ql new file mode 100644 index 0000000..ac5ce5f --- /dev/null +++ b/src/test/resources/testsuite/java/number/long_max_value.ql @@ -0,0 +1,2 @@ +a = Long.MIN_VALUE; +assert(a == -9223372036854775808L); \ No newline at end of file diff --git a/src/test/resources/testsuite/java/number/min_value_not_equal_to_hex.ql b/src/test/resources/testsuite/java/number/min_value_not_equal_to_hex.ql new file mode 100644 index 0000000..ace8503 --- /dev/null +++ b/src/test/resources/testsuite/java/number/min_value_not_equal_to_hex.ql @@ -0,0 +1,3 @@ +assert(Integer.MIN_VALUE != 0x80000000); +assert(Integer.MIN_VALUE == -0x80000000); +assert(Integer.MIN_VALUE == (int)0x80000000); \ No newline at end of file diff --git a/src/test/resources/testsuite/java/number/number_auto_type.ql b/src/test/resources/testsuite/java/number/number_auto_type.ql new file mode 100644 index 0000000..97b9b8c --- /dev/null +++ b/src/test/resources/testsuite/java/number/number_auto_type.ql @@ -0,0 +1,6 @@ +assert(2147483647 instanceof Integer); +assert(9223372036854775807 instanceof Long); +assert(18446744073709552000 instanceof BigInteger); +// 0.25 can be precisely presented with double +assert(0.25 instanceof Double); +assert(2.7976931348623157E308 instanceof BigDecimal); \ No newline at end of file diff --git a/src/test/resources/testsuite/java/number/number_invoke.ql b/src/test/resources/testsuite/java/number/number_invoke.ql new file mode 100644 index 0000000..75388e7 --- /dev/null +++ b/src/test/resources/testsuite/java/number/number_invoke.ql @@ -0,0 +1,2 @@ +assert(1.doubleValue() == 1.d) +assert(2.floatValue() == 2f) \ No newline at end of file diff --git a/src/test/resources/testsuite/java/property/array_length_get.ql b/src/test/resources/testsuite/java/property/array_length_get.ql new file mode 100644 index 0000000..10602f7 --- /dev/null +++ b/src/test/resources/testsuite/java/property/array_length_get.ql @@ -0,0 +1,2 @@ +Object[] a = new Object[3]; +assert(a.length == 3); \ No newline at end of file diff --git a/src/test/resources/testsuite/java/property/class_get.ql b/src/test/resources/testsuite/java/property/class_get.ql new file mode 100644 index 0000000..52cfb4a --- /dev/null +++ b/src/test/resources/testsuite/java/property/class_get.ql @@ -0,0 +1,2 @@ +a = Integer.class; +assert(a == Integer.class); \ No newline at end of file diff --git a/src/test/resources/testsuite/java/property/enum_get.ql b/src/test/resources/testsuite/java/property/enum_get.ql new file mode 100644 index 0000000..4942d2d --- /dev/null +++ b/src/test/resources/testsuite/java/property/enum_get.ql @@ -0,0 +1,4 @@ +import com.alibaba.qlexpress4.test.property.SampleEnum; + +SampleEnum s = SampleEnum.NORMAL; +assert(s.equals(SampleEnum.NORMAL)) \ No newline at end of file diff --git a/src/test/resources/testsuite/java/property/enum_get_not_exist.ql b/src/test/resources/testsuite/java/property/enum_get_not_exist.ql new file mode 100644 index 0000000..1256b43 --- /dev/null +++ b/src/test/resources/testsuite/java/property/enum_get_not_exist.ql @@ -0,0 +1,8 @@ +/* +{ + "errCode": "FIELD_NOT_FOUND" +} +*/ +import com.alibaba.qlexpress4.test.property.SampleEnum; + +SampleEnum s = SampleEnum.NOT_EXIST; \ No newline at end of file diff --git a/src/test/resources/testsuite/java/property/enum_member_field.ql b/src/test/resources/testsuite/java/property/enum_member_field.ql new file mode 100644 index 0000000..1753a45 --- /dev/null +++ b/src/test/resources/testsuite/java/property/enum_member_field.ql @@ -0,0 +1,4 @@ +s = com.alibaba.qlexpress4.test.property.SampleEnum.NORMAL; +assert(s.testField == 10); +assert(s.testStaticField == 1000); +assert(com.alibaba.qlexpress4.test.property.SampleEnum.testStaticField == 1000); \ No newline at end of file diff --git a/src/test/resources/testsuite/java/property/interface_const_field.ql b/src/test/resources/testsuite/java/property/interface_const_field.ql new file mode 100644 index 0000000..f552aad --- /dev/null +++ b/src/test/resources/testsuite/java/property/interface_const_field.ql @@ -0,0 +1,3 @@ +import com.alibaba.qlexpress4.test.property.SomeInter; + +assert(SomeInter.INTER_CONST_1=='test1') \ No newline at end of file diff --git a/src/test/resources/testsuite/java/property/jsonobject_vs_map_put.ql b/src/test/resources/testsuite/java/property/jsonobject_vs_map_put.ql new file mode 100644 index 0000000..b9958c8 --- /dev/null +++ b/src/test/resources/testsuite/java/property/jsonobject_vs_map_put.ql @@ -0,0 +1,27 @@ +// Test for JSONObject put method support +import java.util.HashMap; +import java.util.Map; +import com.alibaba.fastjson2.JSON; +import com.alibaba.qlexpress4.test.property.Parent; +import com.alibaba.fastjson2.JSONObject; + +// Test JSONObject put method (now expected to work) +p = new Parent(); +p.lockStatus = 1; +p.lockStatus2 = 1; +JSONObject reportData = new JSONObject(); +reportData.put("doorStatus", p.lockStatus); +reportData.put("doorStatus2", p.lockStatus2); +reportData.put("operateType", "0"); +reportData.put("openType", 1); + +String jsonString = JSON.toJSONString(reportData); + +assert(reportData.get("doorStatus") == 1); +assert(reportData.get("doorStatus2") == 1); + +assert(reportData.get("operateType").equals("0")); +assert(reportData.get("openType") == 1); + + +return reportData; \ No newline at end of file diff --git a/src/test/resources/testsuite/java/property/null_set_invoke.ql b/src/test/resources/testsuite/java/property/null_set_invoke.ql new file mode 100644 index 0000000..94beeb0 --- /dev/null +++ b/src/test/resources/testsuite/java/property/null_set_invoke.ql @@ -0,0 +1,11 @@ +import com.alibaba.qlexpress4.test.property.Parent; + +p = new Parent(); +p.setBirth(null); +assert(p.birth==null); + +p.birth = "2025-01-01"; +assert(p.birth=="2025-01-01"); + +p.birth = null; +assert(p.birth==null); diff --git a/src/test/resources/testsuite/java/property/private_member_attr_access_get.ql b/src/test/resources/testsuite/java/property/private_member_attr_access_get.ql new file mode 100644 index 0000000..63fc6e9 --- /dev/null +++ b/src/test/resources/testsuite/java/property/private_member_attr_access_get.ql @@ -0,0 +1,9 @@ +/* +{ + "initOptions": InitOptions.builder().allowPrivateAccess(true) +} +*/ +import com.alibaba.qlexpress4.test.property.SampleForPrivate; + +SampleForPrivate a = new SampleForPrivate(5); +assert(a.count == 5); \ No newline at end of file diff --git a/src/test/resources/testsuite/java/property/private_member_attr_access_set.ql b/src/test/resources/testsuite/java/property/private_member_attr_access_set.ql new file mode 100644 index 0000000..a388b5e --- /dev/null +++ b/src/test/resources/testsuite/java/property/private_member_attr_access_set.ql @@ -0,0 +1,10 @@ +/* +{ + "initOptions": InitOptions.builder().allowPrivateAccess(true) +} +*/ +import com.alibaba.qlexpress4.test.property.SampleForPrivate; + +SampleForPrivate a = new SampleForPrivate(4); +a.count = 5; +assert(a.count == 5); \ No newline at end of file diff --git a/src/test/resources/testsuite/java/property/private_member_attr_getter.ql b/src/test/resources/testsuite/java/property/private_member_attr_getter.ql new file mode 100644 index 0000000..07979e1 --- /dev/null +++ b/src/test/resources/testsuite/java/property/private_member_attr_getter.ql @@ -0,0 +1,4 @@ +import com.alibaba.qlexpress4.test.property.Sample; + +Sample a = new Sample(5); +assert(a.count == 5); \ No newline at end of file diff --git a/src/test/resources/testsuite/java/property/private_member_attr_not_access_get.ql b/src/test/resources/testsuite/java/property/private_member_attr_not_access_get.ql new file mode 100644 index 0000000..c821897 --- /dev/null +++ b/src/test/resources/testsuite/java/property/private_member_attr_not_access_get.ql @@ -0,0 +1,9 @@ +/* +{ + "errCode": "FIELD_NOT_FOUND" +} +*/ +import com.alibaba.qlexpress4.test.property.SampleForPrivate; + +SampleForPrivate a = new SampleForPrivate(5); +assert(a.count == 5); \ No newline at end of file diff --git a/src/test/resources/testsuite/java/property/private_member_attr_not_access_set.ql b/src/test/resources/testsuite/java/property/private_member_attr_not_access_set.ql new file mode 100644 index 0000000..2125328 --- /dev/null +++ b/src/test/resources/testsuite/java/property/private_member_attr_not_access_set.ql @@ -0,0 +1,10 @@ +/* +{ + "errCode": "FIELD_NOT_FOUND" +} +*/ +import com.alibaba.qlexpress4.test.property.SampleForPrivate; + +SampleForPrivate a = new SampleForPrivate(4); +a.count = 5; +assert(a.count == 5); \ No newline at end of file diff --git a/src/test/resources/testsuite/java/property/private_member_attr_setter.ql b/src/test/resources/testsuite/java/property/private_member_attr_setter.ql new file mode 100644 index 0000000..7d67017 --- /dev/null +++ b/src/test/resources/testsuite/java/property/private_member_attr_setter.ql @@ -0,0 +1,5 @@ +import com.alibaba.qlexpress4.test.property.Sample; + +Sample a = new Sample(4); +a.count = 5; +assert(a.count == 5); \ No newline at end of file diff --git a/src/test/resources/testsuite/java/property/private_member_set_not_accessible.ql b/src/test/resources/testsuite/java/property/private_member_set_not_accessible.ql new file mode 100644 index 0000000..41dbf21 --- /dev/null +++ b/src/test/resources/testsuite/java/property/private_member_set_not_accessible.ql @@ -0,0 +1,12 @@ +/* +{ + "errCode": "INVALID_ASSIGNMENT" +} +*/ +import com.alibaba.qlexpress4.test.property.TestEnum; + +v = TestEnum.SKT.value; +assert(v == -1); +v = 10; +assert(v == 10); +TestEnum.SKT.value = 100; \ No newline at end of file diff --git a/src/test/resources/testsuite/java/property/public_member_set.ql b/src/test/resources/testsuite/java/property/public_member_set.ql new file mode 100644 index 0000000..9a32c09 --- /dev/null +++ b/src/test/resources/testsuite/java/property/public_member_set.ql @@ -0,0 +1,5 @@ +import com.alibaba.qlexpress4.test.property.SampleSet; + +SampleSet a = new SampleSet(); +a.count = 5; +assert(a.count == 5); \ No newline at end of file diff --git a/src/test/resources/testsuite/java/property/public_static.ql b/src/test/resources/testsuite/java/property/public_static.ql new file mode 100644 index 0000000..132807d --- /dev/null +++ b/src/test/resources/testsuite/java/property/public_static.ql @@ -0,0 +1,2 @@ +a = Integer.MAX_VALUE; +assert(a == 0x7fffffff); \ No newline at end of file diff --git a/src/test/resources/testsuite/java/stream/java_stream.ql b/src/test/resources/testsuite/java/stream/java_stream.ql new file mode 100644 index 0000000..279926b --- /dev/null +++ b/src/test/resources/testsuite/java/stream/java_stream.ql @@ -0,0 +1,7 @@ +l = ["a-111", "a-222", "b-333", "c-888"] + +l2 = l.stream() + .filter(i -> i.startsWith("a-")) + .map(i -> i.split("-")[1]) + .collect(Collectors.toList()); +assert(l2 == ["111", "222"]); \ No newline at end of file diff --git a/src/test/resources/testsuite/java/stream/java_stream_method_ref.ql b/src/test/resources/testsuite/java/stream/java_stream_method_ref.ql new file mode 100644 index 0000000..24498f8 --- /dev/null +++ b/src/test/resources/testsuite/java/stream/java_stream_method_ref.ql @@ -0,0 +1,6 @@ +import com.alibaba.qlexpress4.test.stream.STObject; + +l = [new STObject("aa"), new STObject("bb")].stream().map(STObject::getPayload).collect(Collectors.toList()) + +println(l) +assert(l == ["aa", "bb"]) \ No newline at end of file diff --git a/src/test/resources/testsuite/java/trycatch/catch_java_exception.ql b/src/test/resources/testsuite/java/trycatch/catch_java_exception.ql new file mode 100644 index 0000000..92c4189 --- /dev/null +++ b/src/test/resources/testsuite/java/trycatch/catch_java_exception.ql @@ -0,0 +1,8 @@ +try { + BigDecimal divisor = new BigDecimal(3); + BigDecimal dividend = new BigDecimal(1); + dividend.divide(divisor); + assert(false); +} catch (ArithmeticException e) { + assert(e != null); +} diff --git a/src/test/resources/testsuite/java/trycatch/catch_operator_exception.ql b/src/test/resources/testsuite/java/trycatch/catch_operator_exception.ql new file mode 100644 index 0000000..b27bc53 --- /dev/null +++ b/src/test/resources/testsuite/java/trycatch/catch_operator_exception.ql @@ -0,0 +1,6 @@ +a = try { + 1/0 +} catch(ArithmeticException e) { + 2+2 +}; +assert(a == 4); \ No newline at end of file diff --git a/src/test/resources/testsuite/java/trycatch/catch_order.ql b/src/test/resources/testsuite/java/trycatch/catch_order.ql new file mode 100644 index 0000000..9161cbb --- /dev/null +++ b/src/test/resources/testsuite/java/trycatch/catch_order.ql @@ -0,0 +1,10 @@ +f = e -> try { + throw e; +} catch (NullPointerException n) { + 100 +} catch (Exception e) { + 10 +}; + +assert(f(new NullPointerException()) == 100); +assert(f(new Exception()) == 10); \ No newline at end of file diff --git a/src/test/resources/testsuite/java/trycatch/ql_npe.ql b/src/test/resources/testsuite/java/trycatch/ql_npe.ql new file mode 100644 index 0000000..ded4c4c --- /dev/null +++ b/src/test/resources/testsuite/java/trycatch/ql_npe.ql @@ -0,0 +1,22 @@ +f = npe -> try { + npe(); + return 10; +} catch(NullPointerException n) { + return 100; +}; + +assert(f(() -> {a=null;a.b;}) == 100); + +assert(f(() -> {a=null;a::b;}) == 100); + +assert(f(null) == 100); + +assert(f(() -> { + a= () -> null; + a()(); +}) == 100); + +assert(f(() -> { + a= null; + a.b(); +}) == 100); \ No newline at end of file

El%NOB|_wQ6MOqb#f$7P?z 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%0DTm70dEIRQ)8|>hgD8`zOit4WjlCZr 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 Z{||6sZ(jx?D_H;l002ovPDHLkV1irtmjwU- literal 0 HcmV?d00001 diff --git a/images/order_rules_cn.png b/images/order_rules_cn.png new file mode 100644 index 0000000000000000000000000000000000000000..ffe954c4a3970384780162619a993e46e809fcca GIT binary patch literal 57130 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 ^3n- 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

2OoPs_^nw5YSH2#ZeYlY<~40zy9F`Kj<9a#r+!~ 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