This is an automated email from the ASF dual-hosted git repository. henrib pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-jexl.git
The following commit(s) were added to refs/heads/master by this push: new e5f4f5f8 JEXL-426: create new reference for local variables redefining captured symbols; - added test; e5f4f5f8 is described below commit e5f4f5f8934152d9b75c245ffe526867c951a89e Author: Henrib <hbies...@gmail.com> AuthorDate: Thu May 29 17:36:00 2025 +0200 JEXL-426: create new reference for local variables redefining captured symbols; - added test; --- .../org/apache/commons/jexl3/JexlFeatures.java | 2 +- .../org/apache/commons/jexl3/internal/Frame.java | 17 +-- .../commons/jexl3/internal/LexicalFrame.java | 4 +- .../commons/jexl3/internal/LexicalScope.java | 2 +- .../org/apache/commons/jexl3/internal/Scope.java | 7 +- .../java/org/apache/commons/jexl3/LambdaTest.java | 134 ++++++++++----------- 6 files changed, 82 insertions(+), 84 deletions(-) diff --git a/src/main/java/org/apache/commons/jexl3/JexlFeatures.java b/src/main/java/org/apache/commons/jexl3/JexlFeatures.java index df37da69..3d933e13 100644 --- a/src/main/java/org/apache/commons/jexl3/JexlFeatures.java +++ b/src/main/java/org/apache/commons/jexl3/JexlFeatures.java @@ -362,7 +362,7 @@ public final class JexlFeatures { /** * Sets whether lambda captured-variables are references or values. - * <p>When variables are pass-by-reference, side-effects are visible from inner lexical scopes + * <p>When variables are pass-by-reference, side effects are visible from inner lexical scopes * to outer-scope.</p> * <p> * When disabled, lambda-captured variables use pass-by-value semantic, diff --git a/src/main/java/org/apache/commons/jexl3/internal/Frame.java b/src/main/java/org/apache/commons/jexl3/internal/Frame.java index 273482d0..22228849 100644 --- a/src/main/java/org/apache/commons/jexl3/internal/Frame.java +++ b/src/main/java/org/apache/commons/jexl3/internal/Frame.java @@ -78,9 +78,10 @@ public class Frame { /** * Captures a value. * @param s the offset in this frame + * @param lexical true if this captured symbol is redefined locally * @return the stacked value */ - Object capture(final int s) { + Object capture(final int s, final boolean lexical) { return stack[s]; } @@ -163,15 +164,17 @@ class ReferenceFrame extends Frame { } @Override - CaptureReference capture(final int s) { + Object capture(final int s, final boolean lexical) { final Object o = stack[s]; if (o instanceof CaptureReference) { - return (CaptureReference) o; + final CaptureReference ref = (CaptureReference) o; + // if the captured symbol is lexical, it is redefined locally; a new reference is needed to share it with callees + // otherwise share the reference + return lexical ? (stack[s] = new CaptureReference(ref.get())) : ref; + } else { + // capture the register, wrap the value in a reference to share it with callees + return stack[s] = new CaptureReference(o); } - // change the type of the captured register, wrap the value in a reference - final CaptureReference captured = new CaptureReference(o); - stack[s] = captured; - return captured; } @Override diff --git a/src/main/java/org/apache/commons/jexl3/internal/LexicalFrame.java b/src/main/java/org/apache/commons/jexl3/internal/LexicalFrame.java index fd2397c6..81ac5da0 100644 --- a/src/main/java/org/apache/commons/jexl3/internal/LexicalFrame.java +++ b/src/main/java/org/apache/commons/jexl3/internal/LexicalFrame.java @@ -21,7 +21,7 @@ import java.util.Deque; /** * The set of valued symbols defined in a lexical frame. - * <p>The symbol identifiers are determined by the functional scope. Since the frame contains values of + * <p>The functional scope determines the symbol identifiers. Since the frame contains values of * all symbols in the functional scope, the lexical frame preserves values of symbols reused for local * definition. */ @@ -83,7 +83,7 @@ public class LexicalFrame extends LexicalScope { * * @param symbol the symbol to define * @param capture whether this redefines a captured symbol - * @return true if symbol is defined, false otherwise + * @return true if the symbol is defined, false otherwise */ public boolean defineSymbol(final int symbol, final boolean capture) { final boolean declared = addSymbol(symbol); diff --git a/src/main/java/org/apache/commons/jexl3/internal/LexicalScope.java b/src/main/java/org/apache/commons/jexl3/internal/LexicalScope.java index 9e032076..e53da2f5 100644 --- a/src/main/java/org/apache/commons/jexl3/internal/LexicalScope.java +++ b/src/main/java/org/apache/commons/jexl3/internal/LexicalScope.java @@ -20,7 +20,7 @@ import java.util.BitSet; /** * The set of symbols declared in a lexical scope. - * <p>The symbol identifiers are determined by the functional scope.</p> + * <p>The functional scope determines the symbol identifiers.</p> * <p>We use 2 bits per symbol s; bit (s*2)+0 sets the actual symbol as lexical (let), bit (s*2)+1 as a const. * There are actually only 2 used states: 1 and 3</p> */ diff --git a/src/main/java/org/apache/commons/jexl3/internal/Scope.java b/src/main/java/org/apache/commons/jexl3/internal/Scope.java index 66d596fe..de3d0e42 100644 --- a/src/main/java/org/apache/commons/jexl3/internal/Scope.java +++ b/src/main/java/org/apache/commons/jexl3/internal/Scope.java @@ -62,7 +62,7 @@ public final class Scope { private int vars; /** * The map of named variables aka script parameters and local variables. - * Each parameter is associated to a symbol and is materialized as an offset in the stacked array used + * Each parameter is associated with a symbol and is materialized as an offset in the stacked array used * during evaluation. */ private Map<String, Integer> namedVariables; @@ -95,7 +95,7 @@ public final class Scope { } /** - * Marks a symbol as a lexical, declared through let or const. + * Marks a symbol as lexical, declared through let or const. * @param s the symbol * @return true if the symbol was not already present in the lexical set */ @@ -124,7 +124,8 @@ public final class Scope { for (final Map.Entry<Integer, Integer> capture : capturedVariables.entrySet()) { final Integer target = capture.getKey(); final Integer source = capture.getValue(); - final Object arg = frame.capture(source); + final boolean lexical = lexicalVariables != null && lexicalVariables.hasSymbol(target); + final Object arg = frame.capture(source, lexical); arguments[target] = arg; } newFrame = frame.newFrame(this, arguments, 0); diff --git a/src/test/java/org/apache/commons/jexl3/LambdaTest.java b/src/test/java/org/apache/commons/jexl3/LambdaTest.java index 91b3f99a..3938add5 100644 --- a/src/test/java/org/apache/commons/jexl3/LambdaTest.java +++ b/src/test/java/org/apache/commons/jexl3/LambdaTest.java @@ -30,20 +30,20 @@ import java.util.concurrent.Callable; import org.apache.commons.jexl3.internal.Closure; import org.apache.commons.jexl3.internal.Script; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** * Tests function/lambda/closure features. */ @SuppressWarnings({"AssertEqualsBetweenInconvertibleTypes"}) -public class LambdaTest extends JexlTestCase { +class LambdaTest extends JexlTestCase { public LambdaTest() { super("LambdaTest"); } - @Test - public void test270() { + @Test void test270() { final JexlEngine jexl = createEngine(); final JexlScript base = jexl.createScript("(x, y, z)->{ x + y + z }"); final String text = base.toString(); @@ -61,24 +61,21 @@ public class LambdaTest extends JexlTestCase { assertEquals(text, result.toString()); } - @Test - public void test271a() { + @Test void test271a() { final JexlEngine jexl = createEngine(); final JexlScript base = jexl.createScript("var base = 1; var x = (a)->{ var y = (b) -> {base + b}; return base + y(a)}; x(40)"); final Object result = base.execute(null); assertEquals(42, result); } - @Test - public void test271b() { + @Test void test271b() { final JexlEngine jexl = createEngine(); final JexlScript base = jexl.createScript("var base = 2; var sum = (x, y, z)->{ base + x + y + z }; var y = sum.curry(1); y(2,3)"); final Object result = base.execute(null); assertEquals(8, result); } - @Test - public void test271c() { + @Test void test271c() { final JexlEngine jexl = createEngine(); final JexlScript base = jexl.createScript("(x, y, z)->{ 2 + x + y + z };"); final JexlScript y = base.curry(1); @@ -86,8 +83,7 @@ public class LambdaTest extends JexlTestCase { assertEquals(8, result); } - @Test - public void test271d() { + @Test void test271d() { final JexlEngine jexl = createEngine(); final JexlScript base = jexl.createScript("var base = 2; (x, y, z)->base + x + y + z;"); final JexlScript y = ((JexlScript) base.execute(null)).curry(1); @@ -97,16 +93,14 @@ public class LambdaTest extends JexlTestCase { // Redefining a captured var is not resolved correctly in left-hand side; // declare the var in local frame, resolved in local frame instead of parent. - @Test - public void test271e() { + @Test void test271e() { final JexlEngine jexl = createEngine(); final JexlScript base = jexl.createScript("var base = 1000; var f = (x, y)->{ var base = x + y + (base?:-1000); base; }; f(100, 20)"); final Object result = base.execute(null); assertEquals(1120, result); } - @Test - public void test405a() { + @Test void test405a() { final JexlEngine jexl = new JexlBuilder() .cache(4).strict(true).safe(false) .create(); @@ -119,8 +113,7 @@ public class LambdaTest extends JexlTestCase { assertEquals(0, result); } - @Test - public void test405b() { + @Test void test405b() { final JexlEngine jexl = new JexlBuilder() .cache(4).strict(true).safe(false) .create(); @@ -133,8 +126,7 @@ public class LambdaTest extends JexlTestCase { assertEquals(0, result); } - @Test - public void testCompareLambdaRecurse() throws Exception { + @Test void testCompareLambdaRecurse() throws Exception { final JexlEngine jexl = createEngine(); final String factSrc = "function fact(x) { x < 2? 1 : x * fact(x - 1) }"; final JexlScript fact0 = jexl.createScript(factSrc); @@ -151,8 +143,7 @@ public class LambdaTest extends JexlTestCase { assertEquals(720, r1.execute(null, 6)); } - @Test - public void testCurry1() { + @Test void testCurry1() { final JexlEngine jexl = createEngine(); JexlScript script; Object result; @@ -174,8 +165,7 @@ public class LambdaTest extends JexlTestCase { assertEquals(42, result); } - @Test - public void testCurry2() { + @Test void testCurry2() { final JexlEngine jexl = createEngine(); JexlScript script; Object result; @@ -190,8 +180,7 @@ public class LambdaTest extends JexlTestCase { assertEquals(42, result); } - @Test - public void testCurry3() { + @Test void testCurry3() { final JexlEngine jexl = createEngine(); JexlScript script; Object result; @@ -202,8 +191,7 @@ public class LambdaTest extends JexlTestCase { assertEquals(42, result); } - @Test - public void testCurry4() { + @Test void testCurry4() { final JexlEngine jexl = createEngine(); JexlScript script; Object result; @@ -214,8 +202,7 @@ public class LambdaTest extends JexlTestCase { assertEquals(42, result); } - @Test - public void testCurry5() { + @Test void testCurry5() { final JexlEngine jexl = createEngine(); JexlScript script; Object result; @@ -226,23 +213,21 @@ public class LambdaTest extends JexlTestCase { assertEquals(42, result); } - @Test - public void testFailParseFunc0() { + @Test void testFailParseFunc0() { final String src = "if (false) function foo(x) { x + x }; var foo = 1"; final JexlEngine jexl = createEngine(); final JexlException.Parsing xparse = assertThrows(JexlException.Parsing.class, () -> jexl.createScript(src)); assertTrue(xparse.getMessage().contains("function")); } - @Test - public void testFailParseFunc1() { + @Test void testFailParseFunc1() { final String src = "if (false) let foo = (x) { x + x }; var foo = 1"; final JexlEngine jexl = createEngine(); final JexlException.Parsing xparse = assertThrows(JexlException.Parsing.class, () -> jexl.createScript(src)); assertTrue(xparse.getMessage().contains("let")); } - @Test public void testFatFact0() { + @Test void testFatFact0() { final JexlFeatures features = new JexlFeatures(); features.fatArrow(true); final String src = "function (a) { const fact = x =>{ x <= 1? 1 : x * fact(x - 1) }; fact(a) }"; @@ -252,7 +237,7 @@ public class LambdaTest extends JexlTestCase { assertEquals(720, result); } - @Test public void testFatFact1() { + @Test void testFatFact1() { final String src = "function (a) { const fact = (x)=> x <= 1? 1 : x * fact(x - 1) ; fact(a) }"; final JexlFeatures features = new JexlFeatures(); features.fatArrow(true); @@ -266,8 +251,7 @@ public class LambdaTest extends JexlTestCase { assertTrue(xfeature.getMessage().contains("fat-arrow")); } - @Test - public void testHoistLambda() { + @Test void testHoistLambda() { final JexlEngine jexl = createEngine(); final JexlEvalContext ctx = new JexlEvalContext(); ctx.getEngineOptions().setLexical(false); @@ -307,8 +291,7 @@ public class LambdaTest extends JexlTestCase { assertEquals(42, result); } - @Test - public void testIdentity() { + @Test void testIdentity() { final JexlEngine jexl = createEngine(); JexlScript script; Object result; @@ -319,8 +302,7 @@ public class LambdaTest extends JexlTestCase { assertEquals(42, result); } - @Test - public void testLambda() { + @Test void testLambda() { final JexlEngine jexl = createEngine(); String strs = "var s = function(x) { x + x }; s(21)"; JexlScript s42 = jexl.createScript(strs); @@ -332,8 +314,7 @@ public class LambdaTest extends JexlTestCase { assertEquals(42, result); } - @Test - public void testLambdaClosure() { + @Test void testLambdaClosure() { final JexlEngine jexl = createEngine(); String strs = "var t = 20; var s = function(x, y) { x + y + t}; s(15, 7)"; JexlScript s42 = jexl.createScript(strs); @@ -353,7 +334,7 @@ public class LambdaTest extends JexlTestCase { assertEquals(42, result); } - @Test public void testLambdaExpr0() { + @Test void testLambdaExpr0() { final String src = "(x, y) -> x + y"; final JexlEngine jexl = createEngine(); final JexlScript script = jexl.createScript(src); @@ -361,7 +342,7 @@ public class LambdaTest extends JexlTestCase { assertEquals(42, result); } - @Test public void testLambdaExpr1() { + @Test void testLambdaExpr1() { final String src = "x -> x + x"; final JexlEngine jexl = createEngine(); final JexlScript script = jexl.createScript(src); @@ -369,7 +350,7 @@ public class LambdaTest extends JexlTestCase { assertEquals(42, result); } - @Test public void testLambdaExpr10() { + @Test void testLambdaExpr10() { final String src = "(a)->{ var x = x -> x + x; x(a) }"; final JexlEngine jexl = createEngine(); final JexlScript script = jexl.createScript(src); @@ -377,7 +358,7 @@ public class LambdaTest extends JexlTestCase { assertEquals(42, result); } - @Test public void testLambdaExpr2() { + @Test void testLambdaExpr2() { final String src = "x -> { { x + x } }"; final JexlEngine jexl = createEngine(); final JexlScript script = jexl.createScript(src); @@ -388,7 +369,7 @@ public class LambdaTest extends JexlTestCase { assertTrue(set.contains(42)); } - @Test public void testLambdaExpr3() { + @Test void testLambdaExpr3() { final String src = "x -> ( { x + x } )"; final JexlEngine jexl = createEngine(); final JexlScript script = jexl.createScript(src); @@ -399,8 +380,7 @@ public class LambdaTest extends JexlTestCase { assertTrue(set.contains(42)); } - @Test - public void testLambdaLambda() { + @Test void testLambdaLambda() { final JexlEngine jexl = createEngine(); String strs = "var t = 19; ( (x, y)->{ var t = 20; x + y + t} )(15, 7);"; JexlScript s42 = jexl.createScript(strs); @@ -418,7 +398,7 @@ public class LambdaTest extends JexlTestCase { assertEquals(42, result); } - @Test public void testNamedFunc() { + @Test void testNamedFunc() { final String src = "(let a)->{ function fact(const x) { x <= 1? 1 : x * fact(x - 1); } fact(a); }"; final JexlEngine jexl = createEngine(); final JexlScript script = jexl.createScript(src); @@ -428,15 +408,14 @@ public class LambdaTest extends JexlTestCase { assertEquals(simpleWhitespace(src), parsed); } - @Test public void testNamedFuncIsConst() { + @Test void testNamedFuncIsConst() { final String src = "function foo(x) { x + x }; var foo ='nonononon'"; final JexlEngine jexl = createEngine(); final JexlException.Parsing xparse = assertThrows(JexlException.Parsing.class, () -> jexl.createScript(src)); assertTrue(xparse.getMessage().contains("foo")); } - @Test - public void testNestLambada() throws Exception { + @Test void testNestLambada() throws Exception { final JexlEngine jexl = createEngine(); final String strs = "(x)->{ (y)->{ x + y } }"; final JexlScript s42 = jexl.createScript(strs); @@ -457,8 +436,7 @@ public class LambdaTest extends JexlTestCase { assertEquals(42, result); } - @Test - public void testNestLambda() { + @Test void testNestLambda() { final JexlEngine jexl = createEngine(); final String strs = "( (x)->{ (y)->{ x + y } })(15)(27)"; final JexlScript s42 = jexl.createScript(strs); @@ -466,8 +444,7 @@ public class LambdaTest extends JexlTestCase { assertEquals(42, result); } - @Test - public void testRecurse() { + @Test void testRecurse() { final JexlEngine jexl = createEngine(); final JexlContext jc = new MapContext(); final JexlScript script = jexl.createScript("var fact = (x)->{ if (x <= 1) 1; else x * fact(x - 1) }; fact(5)"); @@ -475,8 +452,7 @@ public class LambdaTest extends JexlTestCase { assertEquals(120, result); } - @Test - public void testRecurse1() { + @Test void testRecurse1() { final JexlEngine jexl = createEngine(); final JexlContext jc = new MapContext(); final String src = "var fact = (x)-> x <= 1? 1 : x * fact(x - 1);\nfact(5);\n"; @@ -487,8 +463,7 @@ public class LambdaTest extends JexlTestCase { assertEquals(src, parsed); } - @Test - public void testRecurse2() { + @Test void testRecurse2() { final JexlEngine jexl = createEngine(); final JexlContext jc = new MapContext(); // adding some captured vars to get it confused @@ -499,8 +474,7 @@ public class LambdaTest extends JexlTestCase { assertEquals(720, result); } - @Test - public void testRecurse2b() { + @Test void testRecurse2b() { final JexlEngine jexl = createEngine(); final JexlContext jc = new MapContext(); // adding some captured vars to get it confused @@ -516,8 +490,7 @@ public class LambdaTest extends JexlTestCase { assertEquals(720, result); } - @Test - public void testRecurse3() { + @Test void testRecurse3() { final JexlEngine jexl = createEngine(); final JexlContext jc = new MapContext(); // adding some captured vars to get it confused @@ -528,8 +501,7 @@ public class LambdaTest extends JexlTestCase { assertEquals(720, result); } - @Test - public void testScriptArguments() { + @Test void testScriptArguments() { final JexlEngine jexl = createEngine(); final JexlScript s = jexl.createScript(" x + x ", "x"); final JexlScript s42 = jexl.createScript("s(21)", "s"); @@ -537,8 +509,7 @@ public class LambdaTest extends JexlTestCase { assertEquals(42, result); } - @Test - public void testScriptContext() { + @Test void testScriptContext() { final JexlEngine jexl = createEngine(); final JexlScript s = jexl.createScript("function(x) { x + x }"); final String fsstr = s.getParsedText(0); @@ -618,4 +589,27 @@ public class LambdaTest extends JexlTestCase { result = script.execute(null, result); assertEquals(142, result); } + + @Test void testRefCapture5() { + JexlFeatures f426 = new JexlFeatures().referenceCapture(true); + JexlEngine jexl = new JexlBuilder().features(f426).create(); + final String src = "let z = 32; let x = 40; function foo() { x += 2; }; function bar() { let x = -169; foo(); x;}; bar();"; + final JexlScript script = jexl.createScript(src); + assertNotNull(script); + final Object result = script.execute(null); + Assertions.assertEquals(-169, result); + } + + @Test void testRefCapture6() { + JexlFeatures f426 = new JexlFeatures().referenceCapture(true); + JexlEngine jexl = new JexlBuilder().features(f426).create(); + final String src = "let x = 40; function foo() { x += 2; }; function bar() { x = -169; () -> { foo(); }}; bar();"; + JexlScript script = jexl.createScript(src); + assertNotNull(script); + Object result = script.execute(null); + assertInstanceOf(JexlScript.class, result); + script = jexl.createScript("f()", "f"); + result = script.execute(null, result); + Assertions.assertEquals(-167, result); + } }