View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.jexl3;
18  
19  import org.apache.commons.jexl3.internal.Closure;
20  import org.apache.commons.jexl3.internal.Script;
21  import org.junit.Assert;
22  import org.junit.Test;
23  
24  import java.util.Arrays;
25  import java.util.List;
26  import java.util.Set;
27  import java.util.concurrent.Callable;
28  
29  /**
30   * Tests function/lambda/closure features.
31   */
32  @SuppressWarnings({"AssertEqualsBetweenInconvertibleTypes"})
33  public class LambdaTest extends JexlTestCase {
34  
35      public LambdaTest() {
36          super("LambdaTest");
37      }
38  
39      @Test
40      public void testScriptArguments() {
41          final JexlEngine jexl = createEngine();
42          final JexlScript s = jexl.createScript(" x + x ", "x");
43          final JexlScript s42 = jexl.createScript("s(21)", "s");
44          final Object result = s42.execute(null, s);
45          Assert.assertEquals(42, result);
46      }
47  
48      @Test
49      public void testScriptContext() {
50          final JexlEngine jexl = createEngine();
51          final JexlScript s = jexl.createScript("function(x) { x + x }");
52          final String fsstr = s.getParsedText(0);
53          Assert.assertEquals("(x)->{ x + x; }", fsstr);
54          Assert.assertEquals(42, s.execute(null, 21));
55          JexlScript s42 = jexl.createScript("s(21)");
56          final JexlContext ctxt = new JexlEvalContext();
57          ctxt.set("s", s);
58          Object result = s42.execute(ctxt);
59          Assert.assertEquals(42, result);
60          result = s42.execute(ctxt);
61          Assert.assertEquals(42, result);
62          s42 = jexl.createScript("x-> { x + x }");
63          result = s42.execute(ctxt, 21);
64          Assert.assertEquals(42, result);
65      }
66  
67      @Test
68      public void testLambda() {
69          final JexlEngine jexl = createEngine();
70          String strs = "var s = function(x) { x + x }; s(21)";
71          JexlScript s42 = jexl.createScript(strs);
72          Object result = s42.execute(null);
73          Assert.assertEquals(42, result);
74          strs = "var s = function(x, y) { x + y }; s(15, 27)";
75          s42 = jexl.createScript(strs);
76          result = s42.execute(null);
77          Assert.assertEquals(42, result);
78      }
79  
80      @Test
81      public void testLambdaClosure()  {
82          final JexlEngine jexl = createEngine();
83          String strs = "var t = 20; var s = function(x, y) { x + y + t}; s(15, 7)";
84          JexlScript s42 = jexl.createScript(strs);
85          Object result = s42.execute(null);
86          Assert.assertEquals(42, result);
87          strs = "var t = 19; var s = function(x, y) { var t = 20; x + y + t}; s(15, 7)";
88          s42 = jexl.createScript(strs);
89          result = s42.execute(null);
90          Assert.assertEquals(42, result);
91          strs = "var t = 20; var s = function(x, y) {x + y + t}; t = 54; s(15, 7)";
92          s42 = jexl.createScript(strs);
93          result = s42.execute(null);
94          Assert.assertEquals(42, result);
95          strs = "var t = 19; var s = function(x, y) { var t = 20; x + y + t}; t = 54; s(15, 7)";
96          s42 = jexl.createScript(strs);
97          result = s42.execute(null);
98          Assert.assertEquals(42, result);
99      }
100 
101     @Test
102     public void testLambdaLambda() {
103         final JexlEngine jexl = createEngine();
104         String strs = "var t = 19; ( (x, y)->{ var t = 20; x + y + t} )(15, 7);";
105         JexlScript s42 = jexl.createScript(strs);
106         Object result = s42.execute(null);
107         Assert.assertEquals(42, result);
108 
109         strs = "( (x, y)->{ ( (xx, yy)->{xx + yy } )(x, y) } )(15, 27)";
110         s42 = jexl.createScript(strs);
111         result = s42.execute(null);
112         Assert.assertEquals(42, result);
113 
114         strs = "var t = 19; var s = (x, y)->{ var t = 20; x + y + t}; t = 54; s(15, 7)";
115         s42 = jexl.createScript(strs);
116         result = s42.execute(null);
117         Assert.assertEquals(42, result);
118     }
119 
120     @Test
121     public void testNestLambda() {
122         final JexlEngine jexl = createEngine();
123         final String strs = "( (x)->{ (y)->{ x + y } })(15)(27)";
124         final JexlScript s42 = jexl.createScript(strs);
125         final Object result = s42.execute(null);
126         Assert.assertEquals(42, result);
127     }
128 
129     @Test
130     public void testNestLambada() throws Exception {
131         final JexlEngine jexl = createEngine();
132         final String strs = "(x)->{ (y)->{ x + y } }";
133         final JexlScript s42 = jexl.createScript(strs);
134         final JexlScript s42b = jexl.createScript(s42.toString());
135         Assert.assertEquals(s42.hashCode(), s42b.hashCode());
136         Assert.assertEquals(s42, s42b);
137         Object result = s42.execute(null, 15);
138         Assert.assertTrue(result instanceof JexlScript);
139         final Object resultb = s42.execute(null, 15);
140         Assert.assertEquals(result.hashCode(), resultb.hashCode());
141         Assert.assertEquals(result, resultb);
142         Assert.assertEquals(result, jexl.createScript(resultb.toString(), "x").execute(null, 15));
143         final JexlScript s15 = (JexlScript) result;
144         final Callable<Object> s15b = s15.callable(null, 27);
145         result = s15.execute(null, 27);
146         Assert.assertEquals(42, result);
147         result = s15b.call();
148         Assert.assertEquals(42, result);
149     }
150 
151     @Test
152     public void testCompareLambdaRecurse() throws Exception {
153         final JexlEngine jexl = createEngine();
154         final String factSrc = "function fact(x) { x < 2? 1 : x * fact(x - 1) }";
155         final JexlScript fact0 = jexl.createScript(factSrc);
156         final JexlScript fact1 = jexl.createScript(fact0.toString());
157         Assert.assertEquals(fact0, fact1);
158         Closure r0 = (Closure) fact0.execute(null);
159         Closure r1 = (Closure) fact1.execute(null);
160         Assert.assertEquals(720, r0.execute(null, 6));
161         Assert.assertEquals(720, r1.execute(null, 6));
162         Assert.assertEquals(r0, r1);
163         Assert.assertEquals(r1, r0);
164         // ensure we did not break anything through equals
165         Assert.assertEquals(720, r0.execute(null, 6));
166         Assert.assertEquals(720, r1.execute(null, 6));
167     }
168 
169     @Test
170     public void testHoistLambda() {
171         final JexlEngine jexl = createEngine();
172         final JexlEvalContext ctx = new JexlEvalContext();
173         ctx.getEngineOptions().setLexical(false);
174         JexlScript s42;
175         Object result;
176         JexlScript s15;
177         String[] localv;
178         Set<List<String>> hvars;
179         String strs;
180 
181         // hosted variables are NOT local variables
182         strs = "(x)->{ (y)->{ x + y } }";
183         s42 = jexl.createScript(strs);
184         result = s42.execute(ctx, 15);
185         Assert.assertTrue(result instanceof JexlScript);
186         s15 = (JexlScript) result;
187         localv = s15.getLocalVariables();
188         Assert.assertEquals(0, localv.length);
189         hvars = s15.getVariables();
190         Assert.assertEquals(1, hvars.size());
191 
192         // declaring a local that overrides captured
193         // in 3.1, such a local was considered local
194         // per 3.2, this local is considered captured
195         strs = "(x)->{ (y)->{ var z = 169; var x; x + y } }";
196         s42 = jexl.createScript(strs);
197         result = s42.execute(ctx, 15);
198         Assert.assertTrue(result instanceof JexlScript);
199         s15 = (JexlScript) result;
200         localv = s15.getLocalVariables();
201         Assert.assertNotNull(localv);
202         Assert.assertEquals(1, localv.length);
203         hvars = s15.getVariables();
204         Assert.assertEquals(1, hvars.size());
205         // evidence this is not (strictly) a local since it inherited a captured value
206         result = s15.execute(ctx, 27);
207         Assert.assertEquals(42, result);
208     }
209 
210     @Test
211     public void testRecurse() {
212         final JexlEngine jexl = createEngine();
213         final JexlContext jc = new MapContext();
214         final JexlScript script = jexl.createScript("var fact = (x)->{ if (x <= 1) 1; else x * fact(x - 1) }; fact(5)");
215         final int result = (Integer) script.execute(jc);
216         Assert.assertEquals(120, result);
217     }
218 
219     @Test
220     public void testRecurse1() {
221         final JexlEngine jexl = createEngine();
222         final JexlContext jc = new MapContext();
223         String src = "var fact = (x)-> x <= 1? 1 : x * fact(x - 1);\nfact(5);\n";
224         final JexlScript script = jexl.createScript(src);
225         final int result = (Integer) script.execute(jc);
226         Assert.assertEquals(120, result);
227         String parsed = script.getParsedText();
228         Assert.assertEquals(src, parsed);
229     }
230 
231     @Test
232     public void testRecurse2() {
233         final JexlEngine jexl = createEngine();
234         final JexlContext jc = new MapContext();
235         // adding some captured vars to get it confused
236         final JexlScript script = jexl.createScript(
237                 "var y = 1; var z = 1; "
238                 +"var fact = (x)->{ if (x <= y) z; else x * fact(x - 1) }; fact(6)");
239         final int result = (Integer) script.execute(jc);
240         Assert.assertEquals(720, result);
241     }
242 
243     @Test
244     public void testRecurse2b() {
245         final JexlEngine jexl = createEngine();
246         final JexlContext jc = new MapContext();
247         // adding some captured vars to get it confused
248         final JexlScript fact = jexl.createScript(
249                 "var y = 1; var z = 1; "
250                         +"var fact = (x)->{ if (x <= y) z; else x * fact(x - 1) };" +
251                         "fact");
252         Script func = (Script) fact.execute(jc);
253         String[] captured = func.getCapturedVariables();
254         Assert.assertEquals(3, captured.length);
255         Assert.assertTrue(Arrays.asList(captured).containsAll(Arrays.asList("z", "y", "fact")));
256         final int result = (Integer) func.execute(jc, 6);
257         Assert.assertEquals(720, result);
258     }
259 
260     @Test
261     public void testRecurse3() {
262         final JexlEngine jexl = createEngine();
263         final JexlContext jc = new MapContext();
264         // adding some captured vars to get it confused
265         final JexlScript script = jexl.createScript(
266                 "var y = 1; var z = 1;var foo = (x)->{y + z}; "
267                 +"var fact = (x)->{ if (x <= y) z; else x * fact(x - 1) }; fact(6)");
268         final int result = (Integer) script.execute(jc);
269         Assert.assertEquals(720, result);
270     }
271 
272     @Test
273     public void testIdentity() {
274         final JexlEngine jexl = createEngine();
275         JexlScript script;
276         Object result;
277 
278         script = jexl.createScript("(x)->{ x }");
279         Assert.assertArrayEquals(new String[]{"x"}, script.getParameters());
280         result = script.execute(null, 42);
281         Assert.assertEquals(42, result);
282     }
283 
284     @Test
285     public void testCurry1() {
286         final JexlEngine jexl = createEngine();
287         JexlScript script;
288         Object result;
289         String[] parms;
290 
291         final JexlScript base = jexl.createScript("(x, y, z)->{ x + y + z }");
292         parms = base.getUnboundParameters();
293         Assert.assertEquals(3, parms.length);
294         script = base.curry(5);
295         parms = script.getUnboundParameters();
296         Assert.assertEquals(2, parms.length);
297         script = script.curry(15);
298         parms = script.getUnboundParameters();
299         Assert.assertEquals(1, parms.length);
300         script = script.curry(22);
301         parms = script.getUnboundParameters();
302         Assert.assertEquals(0, parms.length);
303         result = script.execute(null);
304         Assert.assertEquals(42, result);
305     }
306 
307     @Test
308     public void testCurry2() {
309         final JexlEngine jexl = createEngine();
310         JexlScript script;
311         Object result;
312         String[] parms;
313 
314         final JexlScript base = jexl.createScript("(x, y, z)->{ x + y + z }");
315         script = base.curry(5, 15);
316         parms = script.getUnboundParameters();
317         Assert.assertEquals(1, parms.length);
318         script = script.curry(22);
319         result = script.execute(null);
320         Assert.assertEquals(42, result);
321     }
322 
323     @Test
324     public void testCurry3() {
325         final JexlEngine jexl = createEngine();
326         JexlScript script;
327         Object result;
328 
329         final JexlScript base = jexl.createScript("(x, y, z)->{ x + y + z }");
330         script = base.curry(5, 15);
331         result = script.execute(null, 22);
332         Assert.assertEquals(42, result);
333     }
334 
335     @Test
336     public void testCurry4() {
337         final JexlEngine jexl = createEngine();
338         JexlScript script;
339         Object result;
340 
341         final JexlScript base = jexl.createScript("(x, y, z)->{ x + y + z }");
342         script = base.curry(5);
343         result = script.execute(null, 15, 22);
344         Assert.assertEquals(42, result);
345     }
346 
347     @Test
348     public void testCurry5() {
349         final JexlEngine jexl = createEngine();
350         JexlScript script;
351         Object result;
352 
353         final JexlScript base = jexl.createScript("var t = x + y + z; return t", "x", "y", "z");
354         script = base.curry(5);
355         result = script.execute(null, 15, 22);
356         Assert.assertEquals(42, result);
357     }
358 
359     @Test
360     public void test270() {
361         final JexlEngine jexl = createEngine();
362         final JexlScript base = jexl.createScript("(x, y, z)->{ x + y + z }");
363         final String text = base.toString();
364         JexlScript script = base.curry(5, 15);
365         Assert.assertEquals(text, script.toString());
366 
367         final JexlContext ctxt = new JexlEvalContext();
368         ctxt.set("s", base);
369         script = jexl.createScript("return s");
370         Object result = script.execute(ctxt);
371         Assert.assertEquals(text, result.toString());
372 
373         script = jexl.createScript("return s.curry(1)");
374         result = script.execute(ctxt);
375         Assert.assertEquals(text, result.toString());
376     }
377 
378     @Test
379     public void test271a() {
380         final JexlEngine jexl = createEngine();
381         final JexlScript base = jexl.createScript("var base = 1; var x = (a)->{ var y = (b) -> {base + b}; return base + y(a)}; x(40)");
382         final Object result = base.execute(null);
383         Assert.assertEquals(42, result);
384     }
385 
386     @Test
387     public void test271b() {
388         final JexlEngine jexl = createEngine();
389         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)");
390         final Object result = base.execute(null);
391         Assert.assertEquals(8, result);
392     }
393 
394     @Test
395     public void test271c() {
396         final JexlEngine jexl = createEngine();
397         final JexlScript base = jexl.createScript("(x, y, z)->{ 2 + x + y + z };");
398         final JexlScript y = base.curry(1);
399         final Object result = y.execute(null, 2, 3);
400         Assert.assertEquals(8, result);
401     }
402 
403     @Test
404     public void test271d() {
405         final JexlEngine jexl = createEngine();
406         final JexlScript base = jexl.createScript("var base = 2; (x, y, z)->base + x + y + z;");
407         final JexlScript y = ((JexlScript) base.execute(null)).curry(1);
408         final Object result = y.execute(null, 2, 3);
409         Assert.assertEquals(8, result);
410     }
411 
412     // Redefining a captured var is not resolved correctly in left-hand side;
413     // declare the var in local frame, resolved in local frame instead of parent.
414     @Test
415     public void test271e() {
416         JexlEngine jexl = createEngine();
417         JexlScript base = jexl.createScript("var base = 1000; var f = (x, y)->{ var base = x + y + (base?:-1000); base; }; f(100, 20)");
418         Object result = base.execute(null);
419         Assert.assertEquals(1120, result);
420     }
421 
422     @Test public void testFatFact0() {
423         JexlFeatures features = new JexlFeatures();
424         features.fatArrow(true);
425         String src = "function (a) { const fact = (x)=>{ x <= 1? 1 : x * fact(x - 1) }; fact(a) }";
426         JexlEngine jexl = createEngine(features);
427         JexlScript script = jexl.createScript(src);
428         Object result = script.execute(null, 6);
429         Assert.assertEquals(720, result);
430     }
431 
432     @Test public void testFatFact1() {
433         String src = "function (a) { const fact = (x)=> x <= 1? 1 : x * fact(x - 1) ; fact(a) }";
434         JexlFeatures features = new JexlFeatures();
435         features.fatArrow(true);
436         JexlEngine jexl = createEngine(features);
437         JexlScript script = jexl.createScript(src);
438         Object result = script.execute(null, 6);
439         Assert.assertEquals(720, result);
440         features.fatArrow(false);
441         jexl = createEngine(features);
442         try {
443             script = jexl.createScript(src);
444         } catch(JexlException.Feature xfeature) {
445             Assert.assertTrue(xfeature.getMessage().contains("fat-arrow"));
446         }
447     }
448 
449     @Test public void testNamedFunc() {
450         String src = "(let a)->{ function fact(const x) { x <= 1? 1 : x * fact(x - 1); } fact(a); }";
451         JexlEngine jexl = createEngine();
452         JexlScript script = jexl.createScript(src);
453         Object result = script.execute(null, 6);
454         Assert.assertEquals(720, result);
455         String parsed = simpleWhitespace(script.getParsedText());
456         Assert.assertEquals(simpleWhitespace(src), parsed);
457     }
458 
459     @Test public void testNamedFuncIsConst() {
460         String src = "function foo(x) { x + x }; var foo ='nonononon'";
461         JexlEngine jexl = createEngine();
462         try {
463             JexlScript script = jexl.createScript(src);
464             Assert.fail("should fail, foo is already defined");
465         } catch(JexlException.Parsing xparse) {
466             Assert.assertTrue(xparse.getMessage().contains("foo"));
467         }
468     }
469     @Test
470     public void testFailParseFunc0() {
471         String src = "if (false) function foo(x) { x + x }; var foo = 1";
472         JexlEngine jexl = createEngine();
473         try {
474             JexlScript script = jexl.createScript(src);
475         } catch(JexlException.Parsing xparse) {
476             Assert.assertTrue(xparse.getMessage().contains("function"));
477         }
478     }
479 
480     @Test
481     public void testFailParseFunc1() {
482         String src = "if (false) let foo = (x) { x + x }; var foo = 1";
483         JexlEngine jexl = createEngine();
484         try {
485             JexlScript script = jexl.createScript(src);
486         } catch(JexlException.Parsing xparse) {
487             Assert.assertTrue(xparse.getMessage().contains("let"));
488         }
489     }
490 
491     @Test public void testLambdaExpr0() {
492         String src = "(x, y) -> x + y";
493         JexlEngine jexl = createEngine();
494         JexlScript script = jexl.createScript(src);
495         Object result = script.execute(null, 11, 31);
496         Assert.assertEquals(42, result);
497     }
498 
499     @Test public void testLambdaExpr1() {
500         String src = "x -> x + x";
501         JexlEngine jexl = createEngine();
502         JexlScript script = jexl.createScript(src);
503         Object result = script.execute(null, 21);
504         Assert.assertEquals(42, result);
505     }
506 
507     @Test public void testLambdaExpr10() {
508         String src = "(a)->{ var x = x -> x + x; x(a) }";
509         JexlEngine jexl = createEngine();
510         JexlScript script = jexl.createScript(src);
511         Object result = script.execute(null, 21);
512         Assert.assertEquals(42, result);
513     }
514 
515     @Test public void testLambdaExpr2() {
516         String src = "x -> { { x + x } }";
517         JexlEngine jexl = createEngine();
518         JexlScript script = jexl.createScript(src);
519         Object result = script.execute(null, 21);
520         Assert.assertTrue(result instanceof Set);
521         Set<?> set = (Set<?>) result;
522         Assert.assertEquals(1, set.size());
523         Assert.assertTrue(set.contains(42));
524     }
525 
526     @Test public void testLambdaExpr3() {
527         String src = "x -> ( { x + x } )";
528         JexlEngine jexl = createEngine();
529         JexlScript script = jexl.createScript(src);
530         Object result = script.execute(null, 21);
531         Assert.assertTrue(result instanceof Set);
532         Set<?> set = (Set<?>) result;
533         Assert.assertEquals(1, set.size());
534         Assert.assertTrue(set.contains(42));
535     }
536 }