From afc9c4648510ce4279d470a4bf9aad356418d01c Mon Sep 17 00:00:00 2001 From: dennis Date: Thu, 10 Jan 2019 21:08:32 +0800 Subject: [PATCH] (fix) Throw exception when assignment to captured variables, issue #101 --- .../runtime/function/LambdaFunction.java | 12 +++++++- .../aviator/runtime/op/OperationRuntime.java | 2 +- .../aviator/runtime/type/AviatorJavaType.java | 1 + .../com/googlecode/aviator/utils/Env.java | 28 ++++++++++++------- .../googlecode/aviator/LambdaUnitTest.java | 12 ++++++++ .../aviator/example/LambdaExample.java | 15 +++++++++- 6 files changed, 57 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/googlecode/aviator/runtime/function/LambdaFunction.java b/src/main/java/com/googlecode/aviator/runtime/function/LambdaFunction.java index cef5fd54..758ef48f 100644 --- a/src/main/java/com/googlecode/aviator/runtime/function/LambdaFunction.java +++ b/src/main/java/com/googlecode/aviator/runtime/function/LambdaFunction.java @@ -1,7 +1,10 @@ package com.googlecode.aviator.runtime.function; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; +import com.googlecode.aviator.BaseExpression; import com.googlecode.aviator.Expression; import com.googlecode.aviator.runtime.type.AviatorObject; import com.googlecode.aviator.utils.Env; @@ -26,7 +29,14 @@ public abstract class LambdaFunction extends AbstractFunction { public LambdaFunction(List arguments, Expression expression, Env context) { super(); this.arguments = arguments; - this.context = context.clone(); + this.context = context; + Set argumentSet = new HashSet<>(this.arguments); + for (String var : expression.getVariableNames()) { + if (!var.contains(".") && !argumentSet.contains(var)) { + // mark the var is captured. + context.capture(var, ((BaseExpression) expression).getExpression()); + } + } this.expression = expression; } diff --git a/src/main/java/com/googlecode/aviator/runtime/op/OperationRuntime.java b/src/main/java/com/googlecode/aviator/runtime/op/OperationRuntime.java index 4a5045e5..16a19cd6 100644 --- a/src/main/java/com/googlecode/aviator/runtime/op/OperationRuntime.java +++ b/src/main/java/com/googlecode/aviator/runtime/op/OperationRuntime.java @@ -166,7 +166,7 @@ private static void trace(Map env, OperatorType opType, AviatorO case 3: RuntimeUtils.printTrace(env, TRACE_PREFIX + args[0].desc(env) + WHITE_SPACE + "?" + WHITE_SPACE + args[0].desc(env) - + WHITE_SPACE + ":" + WHITE_SPACE + args[1].desc(env) + " => " + result.desc(env)); + + WHITE_SPACE + ":" + WHITE_SPACE + args[1].desc(env) + " => " + result.desc(env)); break; } } diff --git a/src/main/java/com/googlecode/aviator/runtime/type/AviatorJavaType.java b/src/main/java/com/googlecode/aviator/runtime/type/AviatorJavaType.java index 21a8e0c4..93296b33 100644 --- a/src/main/java/com/googlecode/aviator/runtime/type/AviatorJavaType.java +++ b/src/main/java/com/googlecode/aviator/runtime/type/AviatorJavaType.java @@ -259,6 +259,7 @@ public AviatorObject setValue(AviatorObject value, Map env) { if (this.name.contains(".")) { throw new IllegalArgumentException("Can't assignment value to `" + this.name + "`"); } + Object v = value.getValue(env); env.put(this.name, v); return new AviatorRuntimeJavaType(v); diff --git a/src/main/java/com/googlecode/aviator/utils/Env.java b/src/main/java/com/googlecode/aviator/utils/Env.java index 24d12b68..a89bd6ee 100644 --- a/src/main/java/com/googlecode/aviator/utils/Env.java +++ b/src/main/java/com/googlecode/aviator/utils/Env.java @@ -31,6 +31,7 @@ import java.util.Map; import java.util.Set; import com.googlecode.aviator.AviatorEvaluatorInstance; +import com.googlecode.aviator.exception.ExpressionRuntimeException; import com.googlecode.aviator.lexer.token.Variable; /** @@ -68,6 +69,15 @@ public void setInstance(AviatorEvaluatorInstance instance) { public static final Map EMPTY_ENV = Collections.emptyMap(); + private Map capturedVars; + + public void capture(String var, String expression) { + if (capturedVars == null) { + capturedVars = new HashMap<>(); + } + capturedVars.put(var, expression); + } + /** * Constructs an env instance with empty state. */ @@ -75,14 +85,6 @@ public Env() { this(EMPTY_ENV); } - @Override - public Env clone() { - Env ret = new Env(this.mDefaults == EMPTY_ENV ? EMPTY_ENV : new HashMap<>(this.mDefaults), - new HashMap<>(this.mOverrides)); - ret.instance = this.instance; - return ret; - } - /** * Constructor. * @@ -210,6 +212,12 @@ public Set keySet() { */ @Override public Object put(String key, Object value) { + String capturedExp = null; + if (this.capturedVars != null && this.capturedVars.containsKey(key)) { + throw new ExpressionRuntimeException("Can't assignment value to captured variable.The `" + key + + "` is already captured by lambda."); + } + Object prior; Map overrides = getmOverrides(false); if (overrides.containsKey(key)) { @@ -282,8 +290,8 @@ public Collection values() { public String toString() { StringBuffer buf = new StringBuffer(32 * size()); buf.append(super.toString()).append("{"). // - append(Variable.INSTANCE_VAR).append("=").append(this.instance).append(", ").// - append(Variable.ENV_VAR).append("=").append(""); + append(Variable.INSTANCE_VAR).append("=").append(this.instance).append(", ").// + append(Variable.ENV_VAR).append("=").append(""); Iterator it = keySet().iterator(); boolean hasNext = it.hasNext(); diff --git a/src/test/java/com/googlecode/aviator/LambdaUnitTest.java b/src/test/java/com/googlecode/aviator/LambdaUnitTest.java index a68b7777..3d268503 100644 --- a/src/test/java/com/googlecode/aviator/LambdaUnitTest.java +++ b/src/test/java/com/googlecode/aviator/LambdaUnitTest.java @@ -57,6 +57,18 @@ public void testLambdaClosure() { assertEquals(19, AviatorEvaluator.execute("test(4)(5)(6) + a", env)); } + @Test(expected = ExpressionRuntimeException.class) + public void testIssue101() { + String exp = "a=1; b = lambda(x) -> a+ x end ; a=4 ; b(5)"; + AviatorEvaluator.execute(exp); // output 6 + } + + @Test + public void testIssue101_assignment() { + String exp = "a=1; c=a; b = lambda(x) -> a+ x end ; c=4 ; b(5)"; + assertEquals(6, AviatorEvaluator.execute(exp)); + } + public static class Foo { private int a; diff --git a/src/test/java/com/googlecode/aviator/example/LambdaExample.java b/src/test/java/com/googlecode/aviator/example/LambdaExample.java index 0b8a18f3..0afc4019 100644 --- a/src/test/java/com/googlecode/aviator/example/LambdaExample.java +++ b/src/test/java/com/googlecode/aviator/example/LambdaExample.java @@ -1,14 +1,27 @@ package com.googlecode.aviator.example; +import java.util.HashMap; +import java.util.Map; import com.googlecode.aviator.AviatorEvaluator; public class LambdaExample { public static void main(String[] args) { - String exp = "a=1; b = lambda(x) -> a+ x end ; a=4 ; b(5)"; + String exp = "a=1; b = lambda(x) -> a+ x end ; b(5)"; System.out.println(AviatorEvaluator.execute(exp)); // output 6 + Map env = new HashMap(); + env.put("x", 1); + env.put("y", 2); + env.put("z", 3); + + AviatorEvaluator.defineFunction("test", + "lambda(x) -> lambda(y) -> lambda(z) -> x + y + z end end end"); + System.out.println(AviatorEvaluator.execute("test(4)(5)(6)", env)); // output 15 + + env.put("a", 4); + System.out.println(AviatorEvaluator.execute("test(4)(5)(6) + a", env)); // output 19 } }