diff --git a/src/main/java/com/googlecode/aviator/AviatorEvaluator.java b/src/main/java/com/googlecode/aviator/AviatorEvaluator.java index 8e516aa3..91b1cadf 100644 --- a/src/main/java/com/googlecode/aviator/AviatorEvaluator.java +++ b/src/main/java/com/googlecode/aviator/AviatorEvaluator.java @@ -430,7 +430,7 @@ public static AviatorFunction removeOpFunction(final OperatorType opType) { /** - * Check if the function is existed in aviator + * Check if the function exists in the global evaluator instance. * * @param name * @return diff --git a/src/main/java/com/googlecode/aviator/AviatorEvaluatorInstance.java b/src/main/java/com/googlecode/aviator/AviatorEvaluatorInstance.java index bb99aa04..8002bed7 100644 --- a/src/main/java/com/googlecode/aviator/AviatorEvaluatorInstance.java +++ b/src/main/java/com/googlecode/aviator/AviatorEvaluatorInstance.java @@ -1327,13 +1327,24 @@ public AviatorFunction removeOpFunction(final OperatorType opType) { } /** - * Check if the function is existed in aviator + * Check if the function exists in the evaluator. Note: it doesn't check the runtime defined + * functions. * * @param name * @return */ public boolean containsFunction(final String name) { - return this.funcMap.containsKey(name); + boolean exists = this.funcMap.containsKey(name); + if (!exists && this.functionLoaders != null) { + for (FunctionLoader loader : this.functionLoaders) { + if (loader != null && loader.onFunctionNotFound(name) != null) { + exists = true; + break; + } + } + } + + return exists; } /** diff --git a/src/main/java/com/googlecode/aviator/BaseExpression.java b/src/main/java/com/googlecode/aviator/BaseExpression.java index 6b2df0e4..835f2f85 100644 --- a/src/main/java/com/googlecode/aviator/BaseExpression.java +++ b/src/main/java/com/googlecode/aviator/BaseExpression.java @@ -42,8 +42,8 @@ public abstract class BaseExpression implements Expression { private static final long serialVersionUID = 2819544277750883372L; public static final String FUNC_PARAMS_VAR = "__funcs_args__"; - protected List varNames; - protected List varFullNames; + protected transient List varNames; + protected transient List varFullNames; private List vars; private String expression; protected transient AviatorEvaluatorInstance instance; @@ -53,6 +53,12 @@ public abstract class BaseExpression implements Expression { // cached compiled string segments for string interpolation. private transient ConcurrentHashMap> stringSegs = new ConcurrentHashMap>(); + // The function name list in expression + private List functionNames = Collections.emptyList(); + + // Already filtered function names, try to remove the runtime defined functions. + private transient List filteredFunctionNames = null; + protected String sourceFile; protected Map lambdaBootstraps; @@ -62,12 +68,12 @@ public String getSourceFile() { return this.sourceFile; } - public void setSourceFile(final String sourceFile) { + protected void setSourceFile(final String sourceFile) { this.sourceFile = sourceFile; } - public void setInstance(AviatorEvaluatorInstance instance) { + protected void setInstance(AviatorEvaluatorInstance instance) { this.instance = instance; } @@ -264,7 +270,7 @@ protected Object execute(Map map, boolean checkExecutionTimeout) } } - public void setFuncsArgs(final Map> funcsArgs) { + protected void setFuncsArgs(final Map> funcsArgs) { if (funcsArgs != null) { this.funcsArgs = Collections.unmodifiableMap(funcsArgs); } @@ -274,7 +280,7 @@ public Env getCompileEnv() { return this.compileEnv; } - public void setCompileEnv(final Env compileEnv) { + protected void setCompileEnv(final Env compileEnv) { this.compileEnv = compileEnv; this.compileEnv.setExpression(this); } @@ -289,7 +295,7 @@ public String getExpression() { return this.expression; } - public void setExpression(final String expression) { + protected void setExpression(final String expression) { this.expression = expression; } @@ -374,11 +380,57 @@ protected Env newEnv(final Map map) { return newEnv(map, false, true); } + public List getFunctionNames() { + populateFilteredFuncNames(); + + return filteredFunctionNames; + } + + private void populateFilteredFuncNames() { + if (this.filteredFunctionNames == null) { + Set validNames = new HashSet(this.functionNames.size()); + + for (String funcName : this.functionNames) { + // Remove internal functions + if (!funcName.startsWith("__") && !funcName.equals("with_meta")) { + validNames.add(funcName); + } + } + + Set definedFuncs = new HashSet(); + // Find all runtime defined functions + for (VariableMeta v : this.vars) { + // TODO: It's not precise, but could work + if (v.isInit()) { + definedFuncs.add(v.getName()); + } + } + + if (this.lambdaBootstraps != null) { + // Adds sub-expressions function names + for (LambdaFunctionBootstrap bootstrap : this.lambdaBootstraps.values()) { + validNames.addAll(bootstrap.getExpression().getFunctionNames()); + } + } + + // Remove runtime defined functions. + validNames.removeAll(definedFuncs); + + this.filteredFunctionNames = new ArrayList<>(validNames); + } + } + + protected void setFunctionNames(List functionNames) { + if (functionNames != null) { + this.functionNames = functionNames; + } + } + public Map getLambdaBootstraps() { return this.lambdaBootstraps; } - public void setLambdaBootstraps(final Map lambdaBootstraps) { + protected void setLambdaBootstraps(final Map lambdaBootstraps) { this.lambdaBootstraps = lambdaBootstraps; } @@ -400,6 +452,7 @@ public void customReadObject(ObjectInputStream input) throws ClassNotFoundExcept this.sourceFile = (String) input.readObject(); this.lambdaBootstraps = (Map) input.readObject(); this.stringSegs = new ConcurrentHashMap>(); + this.functionNames = (List) input.readObject(); } public void customWriteObject(ObjectOutputStream output) throws IOException { @@ -410,6 +463,7 @@ public void customWriteObject(ObjectOutputStream output) throws IOException { output.writeObject(this.symbolTable); output.writeObject(this.sourceFile); output.writeObject(this.lambdaBootstraps); + output.writeObject(this.functionNames); } } diff --git a/src/main/java/com/googlecode/aviator/Expression.java b/src/main/java/com/googlecode/aviator/Expression.java index b90bb69f..545632f3 100644 --- a/src/main/java/com/googlecode/aviator/Expression.java +++ b/src/main/java/com/googlecode/aviator/Expression.java @@ -29,18 +29,18 @@ public interface Expression extends Serializable { /** - * Execute expression with environment + * Execute an expression with an environment, returns the result. * * @param env Binding variable environment - * @return + * @return the result of execution */ Object execute(Map env); /** - * Execute expression with empty environment + * Execute an expression with an empty environment, returns the result. * - * @return + * @return the result of execution */ Object execute(); @@ -55,7 +55,7 @@ public interface Expression extends Serializable { /** * Returns this expression's all uninitialized global variable names in order when using - * AviatorEvaluator.EVAL mode,else returns empty set + * AviatorEvaluator.EVAL mode, otherwise returns an empty list. * * @see com.googlecode.aviator.AviatorEvaluator#EVAL * @return @@ -65,7 +65,7 @@ public interface Expression extends Serializable { /** * Returns this expression's all uninitialized global variable full names(contains dot) in order - * when using AviatorEvaluator.EVAL mode,else returns empty set + * when using AviatorEvaluator.EVAL mode, otherwise returns an empty list. * * @return */ @@ -89,4 +89,12 @@ public interface Expression extends Serializable { */ String addSymbol(String name); + /** + * Returns the function names in the expression when using AviatorEvaluator.EVAL mode, otherwise + * returns an empty list. + * + * @since 5.4.2 + * @return the function name list + */ + List getFunctionNames(); } diff --git a/src/main/java/com/googlecode/aviator/ExpressionAccessor.java b/src/main/java/com/googlecode/aviator/ExpressionAccessor.java new file mode 100644 index 00000000..5d0421ca --- /dev/null +++ b/src/main/java/com/googlecode/aviator/ExpressionAccessor.java @@ -0,0 +1,61 @@ +/** + * Copyright (C) 2010 dennis zhuang (killme2008@gmail.com) + * + * This library is free software; you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation; either version + * 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + **/ +package com.googlecode.aviator; + +import java.util.List; +import java.util.Map; +import com.googlecode.aviator.runtime.FunctionArgument; +import com.googlecode.aviator.runtime.LambdaFunctionBootstrap; +import com.googlecode.aviator.utils.Env; + +/** + * Base expression default methods accessor + * + * @author dennis + * + */ +public class ExpressionAccessor { + + public static void setSourceFile(final BaseExpression exp, String sourceFile) { + exp.setSourceFile(sourceFile); + } + + public static void setInstance(final BaseExpression exp, AviatorEvaluatorInstance instance) { + exp.setInstance(instance); + } + + public static void setCompileEnv(final BaseExpression exp, final Env compileEnv) { + exp.setCompileEnv(compileEnv); + } + + public static void setExpression(final BaseExpression exp, final String expression) { + exp.setExpression(expression); + } + + public static void setFuncsArgs(final BaseExpression exp, + final Map> funcsArgs) { + exp.setFuncsArgs(funcsArgs); + } + + public static void setLambdaBootstraps(final BaseExpression exp, + final Map lambdaBootstraps) { + exp.setLambdaBootstraps(lambdaBootstraps); + } + + public static void setFunctionNames(final BaseExpression exp, List functionNames) { + exp.setFunctionNames(functionNames); + } +} diff --git a/src/main/java/com/googlecode/aviator/code/BaseEvalCodeGenerator.java b/src/main/java/com/googlecode/aviator/code/BaseEvalCodeGenerator.java index c888f25a..39b1b555 100644 --- a/src/main/java/com/googlecode/aviator/code/BaseEvalCodeGenerator.java +++ b/src/main/java/com/googlecode/aviator/code/BaseEvalCodeGenerator.java @@ -43,6 +43,8 @@ public abstract class BaseEvalCodeGenerator implements EvalCodeGenerator { */ protected final Env compileEnv; + protected Map methodTokens = Collections.emptyMap(); + protected Map> getFuncsArgs() { if (this.funcsArgs == null) { this.funcsArgs = new HashMap<>(); @@ -54,6 +56,11 @@ protected int getNextFuncInvocationId() { return this.funcInvocationId++; } + @Override + public void initMethods(Map methods) { + this.methodTokens = methods; + } + @Override public void setParser(final Parser parser) { this.parser = parser; diff --git a/src/main/java/com/googlecode/aviator/code/OptimizeCodeGenerator.java b/src/main/java/com/googlecode/aviator/code/OptimizeCodeGenerator.java index b8c93ffb..29ef7f05 100644 --- a/src/main/java/com/googlecode/aviator/code/OptimizeCodeGenerator.java +++ b/src/main/java/com/googlecode/aviator/code/OptimizeCodeGenerator.java @@ -28,6 +28,7 @@ import com.googlecode.aviator.AviatorEvaluatorInstance; import com.googlecode.aviator.BaseExpression; import com.googlecode.aviator.Expression; +import com.googlecode.aviator.ExpressionAccessor; import com.googlecode.aviator.Feature; import com.googlecode.aviator.LiteralExpression; import com.googlecode.aviator.exception.CompileExpressionErrorException; @@ -448,8 +449,8 @@ public Expression getResult(final boolean unboxObject) { if (exp instanceof BaseExpression) { - ((BaseExpression) exp).setCompileEnv(getCompileEnv()); - ((BaseExpression) exp).setSourceFile(this.sourceFile); + ExpressionAccessor.setCompileEnv((BaseExpression) exp, getCompileEnv()); + ExpressionAccessor.setSourceFile((BaseExpression) exp, this.sourceFile); } return exp; } diff --git a/src/main/java/com/googlecode/aviator/code/asm/ASMCodeGenerator.java b/src/main/java/com/googlecode/aviator/code/asm/ASMCodeGenerator.java index 49733ebd..de5e5171 100644 --- a/src/main/java/com/googlecode/aviator/code/asm/ASMCodeGenerator.java +++ b/src/main/java/com/googlecode/aviator/code/asm/ASMCodeGenerator.java @@ -58,6 +58,7 @@ import com.googlecode.aviator.AviatorEvaluatorInstance; import com.googlecode.aviator.ClassExpression; import com.googlecode.aviator.Expression; +import com.googlecode.aviator.ExpressionAccessor; import com.googlecode.aviator.Options; import com.googlecode.aviator.asm.ClassWriter; import com.googlecode.aviator.asm.Label; @@ -127,8 +128,6 @@ public class ASMCodeGenerator extends BaseEvalCodeGenerator { private Map/* constant token */, String/* field name */> constantPool = Collections.emptyMap(); - private Map methodTokens = Collections.emptyMap(); - private final Map> labelNameIndexMap = new IdentityHashMap<>(); private static final Label START_LABEL = new Label(); @@ -730,9 +729,10 @@ public Expression getResult(final boolean unboxObject) { defineClass.getConstructor(AviatorEvaluatorInstance.class, List.class, SymbolTable.class); ClassExpression exp = (ClassExpression) constructor.newInstance(this.instance, new ArrayList(this.variables.values()), this.symbolTable); - exp.setLambdaBootstraps(this.lambdaBootstraps); - exp.setFuncsArgs(this.funcsArgs); - exp.setSourceFile(this.sourceFile); + ExpressionAccessor.setLambdaBootstraps(exp, this.lambdaBootstraps); + ExpressionAccessor.setFuncsArgs(exp, this.funcsArgs); + ExpressionAccessor.setSourceFile(exp, this.sourceFile); + ExpressionAccessor.setFunctionNames(exp, new ArrayList<>(this.methodTokens.keySet())); if (enableSerializable) { exp.setClassBytes(bytes); } @@ -1048,7 +1048,7 @@ public void initConstants(final Set> constants) { @Override public void initMethods(final Map methods) { - this.methodTokens = methods; + super.initMethods(methods); this.innerMethodMap = new HashMap<>(methods.size()); for (String outterMethodName : methods.keySet()) { // Use inner method name instead of outter method name diff --git a/src/main/java/com/googlecode/aviator/code/interpreter/InterpretCodeGenerator.java b/src/main/java/com/googlecode/aviator/code/interpreter/InterpretCodeGenerator.java index d0139f55..e680d500 100644 --- a/src/main/java/com/googlecode/aviator/code/interpreter/InterpretCodeGenerator.java +++ b/src/main/java/com/googlecode/aviator/code/interpreter/InterpretCodeGenerator.java @@ -11,6 +11,7 @@ import java.util.Stack; import com.googlecode.aviator.AviatorEvaluatorInstance; import com.googlecode.aviator.Expression; +import com.googlecode.aviator.ExpressionAccessor; import com.googlecode.aviator.InterpretExpression; import com.googlecode.aviator.Options; import com.googlecode.aviator.code.BaseEvalCodeGenerator; @@ -117,9 +118,6 @@ public void initConstants(final Set> constants) { this.constantPool = constants; } - @Override - public void initMethods(final Map methods) {} - @Override public void genNewLambdaCode(final LambdaFunctionBootstrap bootstrap) { emit(new NewLambdaIR(bootstrap.getName())); @@ -358,9 +356,10 @@ public Expression getResult(final boolean unboxObject) { final InterpretExpression exp = new InterpretExpression(this.instance, new ArrayList(this.variables.values()), this.constantPool, this.symbolTable, instruments, unboxObject); - exp.setLambdaBootstraps(this.lambdaBootstraps); - exp.setSourceFile(this.sourceFile); - exp.setFuncsArgs(this.funcsArgs); + ExpressionAccessor.setLambdaBootstraps(exp, this.lambdaBootstraps); + ExpressionAccessor.setSourceFile(exp, this.sourceFile); + ExpressionAccessor.setFuncsArgs(exp, this.funcsArgs); + ExpressionAccessor.setFunctionNames(exp, new ArrayList(this.methodTokens.keySet())); return exp; } diff --git a/src/main/java/com/googlecode/aviator/parser/ExpressionParser.java b/src/main/java/com/googlecode/aviator/parser/ExpressionParser.java index fa6e28dd..98ff2327 100644 --- a/src/main/java/com/googlecode/aviator/parser/ExpressionParser.java +++ b/src/main/java/com/googlecode/aviator/parser/ExpressionParser.java @@ -437,11 +437,11 @@ public void equality() { } - statement(); + StatementType stmtType = statement(); // try to find var(prevToken) in right statement, it's not initialized if presents. if (isVar) { - checkVarIsInit(prevToken); + checkVarIsInit(prevToken, stmtType); } ensureFeatureEnabled(Feature.Assignment); @@ -464,7 +464,7 @@ public void equality() { - private void checkVarIsInit(final Token prevToken) { + private void checkVarIsInit(final Token prevToken, StatementType stmtType) { boolean isInit = true; for (Token t : this.prevTokens) { if (t == prevToken) { @@ -1262,10 +1262,11 @@ private void letStatement() { reportSyntaxError("expect '='"); } move(true); - if (statement() == StatementType.Empty) { + StatementType stmtType = statement(); + if (stmtType == StatementType.Empty) { reportSyntaxError("invalid value to define"); } - checkVarIsInit(var); + checkVarIsInit(var, stmtType); ensureFeatureEnabled(Feature.Assignment); getCodeGeneratorWithTimes().onAssignment(currentToken().withMeta(Constants.DEFINE_META, true)); if (!expectChar(';')) { diff --git a/src/main/java/com/googlecode/aviator/serialize/AviatorObjectInputStream.java b/src/main/java/com/googlecode/aviator/serialize/AviatorObjectInputStream.java index 9a1816d6..88420363 100644 --- a/src/main/java/com/googlecode/aviator/serialize/AviatorObjectInputStream.java +++ b/src/main/java/com/googlecode/aviator/serialize/AviatorObjectInputStream.java @@ -11,6 +11,7 @@ import com.googlecode.aviator.BaseExpression; import com.googlecode.aviator.ClassExpression; import com.googlecode.aviator.Expression; +import com.googlecode.aviator.ExpressionAccessor; import com.googlecode.aviator.code.asm.ClassDefiner; import com.googlecode.aviator.lexer.SymbolTable; import com.googlecode.aviator.lexer.token.Variable; @@ -82,7 +83,7 @@ protected Object resolveObject(Object obj) throws IOException { } private void configureExpression(BaseExpression exp) { - exp.setInstance(this.instance); + ExpressionAccessor.setInstance(exp, this.instance); if (exp instanceof ClassExpression) { ((ClassExpression) exp).setClassBytes(this.classBytesCache.get(exp.getClass().getName())); } diff --git a/src/test/java/com/googlecode/aviator/LambdaUnitTest.java b/src/test/java/com/googlecode/aviator/LambdaUnitTest.java index dd573985..5a8b3333 100644 --- a/src/test/java/com/googlecode/aviator/LambdaUnitTest.java +++ b/src/test/java/com/googlecode/aviator/LambdaUnitTest.java @@ -1,11 +1,15 @@ package com.googlecode.aviator; import static com.googlecode.aviator.TestUtils.assertEquals; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.junit.Test; import com.googlecode.aviator.exception.ExpressionRuntimeException; @@ -149,4 +153,33 @@ public void testLambdaWithHighOrderFunction() { assertEquals(AviatorEvaluator.execute( "reduce(filter(a, lambda(x) -> x > 49 end), lambda(r,e) -> r + e end, 0)", env), sum); } + + @Test + public void testGetFunctionNames() { + List names = compileAndGetFuncNames("reduce(a, lambda(r,e) -> r + e end, 0)"); + assertEquals(Arrays.asList("reduce"), names); + + names = compileAndGetFuncNames( + "reduce(filter(a, lambda(x) -> x > 49 end), lambda(r,e) -> r + e end, 0)"); + assertEquals(Arrays.asList("filter", "reduce"), names); + + names = compileAndGetFuncNames( + "let x = lambda(x) -> x > 49 end; reduce(filter(a, x), lambda(r,e) -> r + e end, 0)"); + assertEquals(Arrays.asList("filter", "reduce"), names); + + names = compileAndGetFuncNames( + "let y = lambda(x) -> x > 49 end; y(); reduce(filter(a, y), lambda(r,e) -> r + e end, 0)"); + assertEquals(Arrays.asList("filter", "reduce"), names); + + names = compileAndGetFuncNames( + "let y = fn(x) { x > 49 }; y(); let z = lambda(r,e) -> r + e end; reduce(filter(a, y), z, 0)"); + assertEquals(Arrays.asList("filter", "reduce"), names); + } + + private List compileAndGetFuncNames(String script) { + Expression exp = AviatorEvaluator.compile(script); + List names = exp.getFunctionNames(); + Collections.sort(names); + return names; + } } diff --git a/src/test/java/com/googlecode/aviator/scripts/TestScripts.java b/src/test/java/com/googlecode/aviator/scripts/TestScripts.java index 092a987e..f3f2ab1d 100644 --- a/src/test/java/com/googlecode/aviator/scripts/TestScripts.java +++ b/src/test/java/com/googlecode/aviator/scripts/TestScripts.java @@ -93,6 +93,13 @@ public Object testLib(final String name, final Object... args) { return null; } + @Test + public void testGetFunctionNames() throws Exception { + final String file = TestScripts.class.getResource("/scripts/qsort.av").getFile(); + Expression exp = this.instance.compileScript(file, true); + assertEquals(Arrays.asList("count"), exp.getFunctionNames()); + } + @Test public void testLibs() { testLib("aviator"); diff --git a/src/test/java/com/googlecode/aviator/test/function/FunctionTest.java b/src/test/java/com/googlecode/aviator/test/function/FunctionTest.java index 2f16f397..2c88f597 100644 --- a/src/test/java/com/googlecode/aviator/test/function/FunctionTest.java +++ b/src/test/java/com/googlecode/aviator/test/function/FunctionTest.java @@ -26,6 +26,7 @@ import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -746,58 +747,71 @@ public void run() { } @Test - public void testGetVariableNamesComplex() { + public void testGetVariableNamesAndFunctionNamesComplex() { // lambda Expression exp = this.instance.compile("a = 1; add = lambda(x) -> x + a end; add(b)"); List vars = exp.getVariableNames(); - System.out.println(vars); + List funcs = getFuncs(exp); assertEquals(1, vars.size()); - assertEquals("b", vars.get(0)); + assertEquals(0, funcs.size()); // lambda closure over exp = this.instance.compile("add = lambda(x) -> x + a end; add(b)"); vars = exp.getVariableNames(); + funcs = getFuncs(exp); assertEquals(2, vars.size()); assertEquals("a", vars.get(0)); assertEquals("b", vars.get(1)); + assertEquals(0, funcs.size()); // if.. else exp = this.instance .compile("b=2; if(a > 1) { a + b } elsif( a > 10) { return a + c; } else { return 10; }"); vars = exp.getVariableNames(); + funcs = getFuncs(exp); assertEquals(2, vars.size()); assertEquals("a", vars.get(0)); assertEquals("c", vars.get(1)); + assertEquals(0, funcs.size()); // for..loop exp = this.instance .compile("let list = seq.list(1, 2, 3); for x in list { sum = sum + x }; return sum;"); vars = exp.getVariableNames(); + funcs = getFuncs(exp); assertEquals(1, vars.size()); assertEquals("sum", vars.get(0)); + assertEquals(1, funcs.size()); + assertEquals(Arrays.asList("seq.list"), funcs); // let statement in block exp = this.instance.compile("{let a = 1; let b = b + 1; p(a+b);} c-a"); + funcs = getFuncs(exp); vars = exp.getVariableNames(); assertEquals(3, vars.size()); assertEquals("b", vars.get(0)); assertEquals("c", vars.get(1)); assertEquals("a", vars.get(2)); + assertEquals(Arrays.asList("p"), funcs); // redfine variable exp = this.instance.compile("{let a = 1; let b = 2; let b = b + 1; p(a+b);} c-a"); vars = exp.getVariableNames(); + funcs = getFuncs(exp); assertEquals(2, vars.size()); assertEquals("c", vars.get(0)); assertEquals("a", vars.get(1)); + assertEquals(Arrays.asList("p"), funcs); // high-order function exp = this.instance.compile("map(list, lambda(v) -> v + u end)"); vars = exp.getVariableNames(); + funcs = getFuncs(exp); assertEquals(2, vars.size()); assertEquals("list", vars.get(0)); assertEquals("u", vars.get(1)); + assertEquals(Arrays.asList("map"), funcs); // a complex script exp = this.instance.compile("let n = 0; let index =0 ; " + "for i in a {" @@ -805,25 +819,37 @@ public void testGetVariableNamesComplex() { + "let m = date.month(b, i); " + "if c[index] == '03' && m <= 12 {" + " n = n + 1; " + "}" + "index = index + 1;" + " } return n;"); vars = exp.getVariableNames(); + funcs = getFuncs(exp); assertEquals(3, vars.size()); assertEquals("a", vars.get(0)); assertEquals("b", vars.get(1)); assertEquals("c", vars.get(2)); + assertEquals(Arrays.asList("date.month", "string_to_date"), funcs); exp = this.instance.compile("a = seq.map(); add = lambda() -> a.b + a.c end; add()"); vars = exp.getVariableNames(); + funcs = getFuncs(exp); assertEquals(0, vars.size()); vars = exp.getVariableFullNames(); assertEquals(2, vars.size()); assertEquals("a.b", vars.get(0)); assertEquals("a.c", vars.get(1)); + assertEquals(Arrays.asList("seq.map"), funcs); exp = this.instance.compile("a = seq.map(); a.c = 2; add = lambda() -> a.b + a.c end; add()"); + funcs = getFuncs(exp); vars = exp.getVariableNames(); assertEquals(0, vars.size()); vars = exp.getVariableFullNames(); assertEquals(1, vars.size()); assertEquals("a.b", vars.get(0)); + assertEquals(Arrays.asList("seq.map"), funcs); + } + + private List getFuncs(Expression exp) { + List funcs = exp.getFunctionNames(); + Collections.sort(funcs); + return funcs; } @Test