1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.jexl3;
18
19 import static org.apache.commons.jexl3.JexlTestCase.createEngine;
20 import static org.junit.jupiter.api.Assertions.assertEquals;
21 import static org.junit.jupiter.api.Assertions.assertFalse;
22 import static org.junit.jupiter.api.Assertions.assertInstanceOf;
23 import static org.junit.jupiter.api.Assertions.assertNotEquals;
24 import static org.junit.jupiter.api.Assertions.assertNotNull;
25 import static org.junit.jupiter.api.Assertions.assertNull;
26 import static org.junit.jupiter.api.Assertions.assertThrows;
27 import static org.junit.jupiter.api.Assertions.assertTrue;
28 import static org.junit.jupiter.api.Assertions.fail;
29
30 import java.io.StringReader;
31 import java.io.StringWriter;
32 import java.util.ArrayList;
33 import java.util.Arrays;
34 import java.util.BitSet;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.Set;
38 import java.util.concurrent.Callable;
39
40 import org.apache.commons.jexl3.internal.LexicalScope;
41 import org.apache.commons.jexl3.internal.Script;
42 import org.junit.jupiter.api.Test;
43
44
45
46
47 class LexicalTest {
48
49 public static class DebugContext extends MapContext {
50 public DebugContext() {
51 }
52 public Object debug(final Object arg) {
53 return arg;
54 }
55 }
56
57 public static class OptAnnotationContext extends JexlEvalContext implements JexlContext.AnnotationProcessor {
58 @Override
59 public Object processAnnotation(final String name, final Object[] args, final Callable<Object> statement) throws Exception {
60
61 if ("scale".equals(name)) {
62 final JexlOptions options = getEngineOptions();
63 final int scale = options.getMathScale();
64 final int newScale = (Integer) args[0];
65 options.setMathScale(newScale);
66 try {
67 return statement.call();
68 } finally {
69 options.setMathScale(scale);
70 }
71 }
72 return statement.call();
73 }
74 }
75
76
77
78
79 public static class TestContext extends JexlEvalContext {
80 public TestContext() {}
81 public TestContext(final Map<String, Object> map) {
82 super(map);
83 }
84
85
86
87
88
89
90
91
92 public Object tryCatch(final JexlScript tryFn, final JexlScript catchFn, final Object... args) {
93 Object result;
94 try {
95 result = tryFn.execute(this, args);
96 } catch (final Throwable xthrow) {
97 result = catchFn != null ? catchFn.execute(this, xthrow) : xthrow;
98 }
99 return result;
100 }
101 }
102
103 public static class VarContext extends MapContext implements JexlContext.PragmaProcessor, JexlContext.OptionsHandle {
104 private JexlOptions options = new JexlOptions();
105
106 @Override
107 public JexlOptions getEngineOptions() {
108 return options;
109 }
110
111 @Override
112 public void processPragma(final String key, final Object value) {
113 if ("jexl.options".equals(key) && "canonical".equals(value)) {
114 options.setStrict(true);
115 options.setLexical(true);
116 options.setLexicalShade(true);
117 options.setSafe(false);
118 }
119 }
120
121 JexlOptions snatchOptions() {
122 final JexlOptions o = options;
123 options = new JexlOptions();
124 return o;
125 }
126 }
127
128 private void checkParse(final JexlFeatures f, final List<String> srcs, final boolean expected) {
129 final JexlEngine jexl = new JexlBuilder().features(f).strict(true).create();
130 for(final String src : srcs) {
131 if (!src.isEmpty()) {
132 try {
133 final JexlScript script = jexl.createScript(src);
134 if (!expected) {
135 fail(src);
136 }
137 } catch (final JexlException.Parsing xlexical) {
138 if (expected) {
139 fail(src);
140 }
141
142 }
143 }
144 }
145
146 }
147
148 private void checkParse(final List<String> srcs, final boolean expected) {
149 checkParse(null, srcs, expected);
150 }
151
152 void runLexical0(final boolean feature) {
153 final JexlFeatures f = new JexlFeatures();
154 f.lexical(feature);
155 final JexlEngine jexl = new JexlBuilder().strict(true).features(f).create();
156 final JexlEvalContext ctxt = new JexlEvalContext();
157 final JexlOptions options = ctxt.getEngineOptions();
158
159 options.setLexical(true);
160 runLexical0(jexl, ctxt, "var x = 0; var x = 1;", feature);
161 runLexical0(jexl, ctxt, "var x = 0; for(var y : null) { let y = 1; }", feature);
162 runLexical0(jexl, ctxt, "var x = 0; for(var x : null) {};", feature);
163 runLexical0(jexl, ctxt, "(x)->{ var x = 0; x; }", feature);
164 runLexical0(jexl, ctxt, "var x; if (true) { if (true) { var x = 0; x; } }", feature);
165 runLexical0(jexl, ctxt, "if (a) { var y = (x)->{ var x = 0; x; }; y(2) }", feature);
166 final JexlException xany = assertThrows(JexlException.class, () -> {
167 final JexlScript script = jexl.createScript("(x)->{ for(var x : null) { x; } }");
168 if (!feature) {
169 script.execute(ctxt, 42);
170 }
171 });
172 assertNotNull(xany.toString());
173
174 final JexlScript script = jexl.createScript("var x = 32; y = (()->{ for(var x : [42]) { x; }})(); x");
175 if (!feature) {
176 assertEquals(32, script.execute(ctxt, 42));
177 assertEquals(42, ctxt.get("y"));
178 }
179 }
180
181 private void runLexical0(final JexlEngine jexl, final JexlEvalContext ctxt, final String source, final boolean feature) {
182 final JexlException xany = assertThrows(JexlException.class, () -> {
183 final JexlScript script = jexl.createScript(source);
184 if (!feature) {
185 script.execute(ctxt);
186 }
187 });
188 assertNotNull(xany.toString());
189 }
190
191 void runLexical1(final boolean shade) {
192 final JexlEngine jexl = new JexlBuilder().strict(true).create();
193 final JexlEvalContext ctxt = new JexlEvalContext();
194 Object result;
195 ctxt.set("x", 4242);
196 final JexlOptions options = ctxt.getEngineOptions();
197
198 options.setLexical(true);
199 options.setLexicalShade(shade);
200 JexlScript script;
201 try {
202
203 script = jexl.createScript("{ var x = 0; } x");
204 script.execute(ctxt);
205 if (shade) {
206 fail("local shade means 'x' should be undefined");
207 }
208 } catch (final JexlException xany) {
209 if (!shade) {
210 throw xany;
211 }
212 }
213 try {
214
215 script = jexl.createScript("{ var x = 0; } x = 42");
216 script.execute(ctxt);
217 if (shade) {
218 fail("local shade means 'x = 42' should be undefined");
219 }
220 } catch (final JexlException xany) {
221 if (!shade) {
222 throw xany;
223 }
224 }
225 try {
226
227 script = jexl.createScript("{ var x = 0; } y = 42");
228 script.execute(ctxt);
229 if (shade) {
230 fail("local shade means 'y = 42' should be undefined (y is undefined)");
231 }
232 } catch (final JexlException xany) {
233 if (!shade) {
234 throw xany;
235 }
236 }
237
238 script = jexl.createScript("var x = 32; (()->{ for(var x : null) { x; }})();");
239
240 script.execute(ctxt, 42);
241
242
243 ctxt.set("y", 4242);
244 try {
245
246 script = jexl.createScript("{ var y = 0; } y = 42");
247 result = script.execute(ctxt);
248 if (!shade) {
249 assertEquals(42, result);
250 } else {
251 fail("local shade means 'y = 42' should be undefined");
252 }
253 } catch (final JexlException xany) {
254 if (!shade) {
255 throw xany;
256 }
257 }
258 }
259
260 protected void runLexical2(final boolean lexical) {
261 final JexlEngine jexl = new JexlBuilder().strict(true).lexical(lexical).create();
262 final JexlContext ctxt = new MapContext();
263 final JexlScript script = jexl.createScript("{var x = 42}; {var x; return x; }");
264 final Object result = script.execute(ctxt);
265 if (lexical) {
266 assertNull(result);
267 } else {
268 assertEquals(42, result);
269 }
270 }
271
272 void runTestScope(final LexicalScope scope, final int init, final int count, final int step) {
273 final int size = (count - init) / step;
274 for(int i = init; i < count; i += step) {
275 assertTrue(scope.addSymbol(i));
276 if (i % (step + 1) == 1) {
277 assertTrue(scope.addConstant(i));
278 }
279 assertFalse(scope.addSymbol(i));
280 }
281 for(int i = init; i < count; i += step) {
282 assertTrue(scope.hasSymbol(i));
283 for(int s = 1; s < step; ++s) {
284 assertFalse(scope.hasSymbol(i + s));
285 }
286 if (i % (step + 1) == 1) {
287 assertTrue(scope.isConstant(i));
288 }
289 }
290 assertEquals(size, scope.getSymbolCount());
291 final BitSet collect = new BitSet();
292 scope.clearSymbols(b -> collect.set(b));
293 for(int i = init; i < count; i += step) {
294 assertTrue(collect.get(i), "missing " + i);
295 }
296 assertEquals(0, scope.getSymbolCount());
297 }
298
299 private JexlFeatures runVarLoop(final boolean flag, final String src) {
300 final VarContext vars = new VarContext();
301 final JexlOptions options = vars.getEngineOptions();
302 options.setLexical(true);
303 options.setLexicalShade(true);
304 options.setSafe(false);
305 final JexlFeatures features = new JexlFeatures();
306 if (flag) {
307 features.lexical(true).lexicalShade(true);
308 }
309 final JexlEngine jexl = new JexlBuilder().features(features).create();
310 final JexlScript script = jexl.createScript(src);
311 final List<Integer> out = new ArrayList<>(10);
312 vars.set("$out", out);
313 final Object result = script.execute(vars);
314 assertEquals(true, result);
315 assertEquals(10, out.size());
316 return features;
317 }
318
319 @Test
320 void testAnnotation() {
321 final JexlFeatures f = new JexlFeatures();
322 f.lexical(true);
323 final JexlEngine jexl = new JexlBuilder().strict(true).features(f).create();
324 final JexlScript script = jexl.createScript("@scale(13) @test var i = 42");
325 final JexlContext jc = new OptAnnotationContext();
326 final Object result = script.execute(jc);
327 assertEquals(42, result);
328 }
329
330 @Test
331 void testCaptured0() {
332 final JexlFeatures f = new JexlFeatures();
333 f.lexical(true);
334 final JexlEngine jexl = new JexlBuilder().strict(true).features(f).create();
335 final JexlScript script = jexl.createScript(
336 "var x = 10; (b->{ x + b })(32)");
337 final JexlContext jc = new MapContext();
338 final Object result = script.execute(jc);
339 assertEquals(42, result);
340 }
341
342 @Test
343 void testCaptured1() {
344 final JexlFeatures f = new JexlFeatures();
345 f.lexical(true);
346 final JexlEngine jexl = new JexlBuilder().strict(true).features(f).create();
347 final JexlScript script = jexl.createScript(
348 "{ var x = 10; } (b->{ x + b })(32)");
349 final JexlContext jc = new MapContext();
350 jc.set("x", 11);
351 final Object result = script.execute(jc);
352 assertEquals(43, result);
353 }
354
355 @Test
356 void testConst0a() {
357 final JexlFeatures f = new JexlFeatures();
358 final JexlEngine jexl = new JexlBuilder().strict(true).create();
359 final JexlScript script = jexl.createScript(
360 "{ const x = 10; x + 1 }; { let x = 20; x = 22}");
361 final JexlContext jc = new MapContext();
362 final Object result = script.execute(jc);
363 assertEquals(22, result);
364 }
365
366 @Test
367 void testConst0b() {
368 final JexlFeatures f = new JexlFeatures();
369 final JexlEngine jexl = new JexlBuilder().strict(true).create();
370 final JexlScript script = jexl.createScript(
371 "{ const x = 10; }{ const x = 20; }");
372 final JexlContext jc = new MapContext();
373 final Object result = script.execute(jc);
374 assertEquals(20, result);
375 }
376
377 @Test
378 void testConst1() {
379 final JexlFeatures f = new JexlFeatures();
380 final JexlEngine jexl = new JexlBuilder().strict(true).create();
381 final JexlException.Parsing xparse = assertThrows(JexlException.Parsing.class, () -> jexl.createScript("const foo; foo"),
382 "should fail, const foo must be followed by assign.");
383 assertTrue(xparse.getMessage().contains("const"));
384
385 }
386
387 @Test
388 void testConst2a() {
389 final JexlEngine jexl = new JexlBuilder().strict(true).create();
390 for (final String op : Arrays.asList("=", "+=", "-=", "/=", "*=", "%=", "<<=", ">>=", ">>>=", "^=", "&=", "|=")) {
391 final JexlException.Parsing xparse = assertThrows(JexlException.Parsing.class, () -> jexl.createScript("const foo = 42; foo " + op + " 1;"),
392 "should fail, const precludes assignment");
393 assertTrue(xparse.getMessage().contains("foo"));
394 }
395 }
396
397 @Test
398 void testConst2b() {
399 final JexlEngine jexl = new JexlBuilder().strict(true).create();
400 for (final String op : Arrays.asList("=", "+=", "-=", "/=", "*=", "%=", "<<=", ">>=", ">>>=", "^=", "&=", "|=")) {
401 final JexlException.Parsing xparse = assertThrows(JexlException.Parsing.class,
402 () -> jexl.createScript("const foo = 42; if (true) { foo " + op + " 1; }"), "should fail, const precludes assignment");
403 assertTrue(xparse.getMessage().contains("foo"));
404 }
405 }
406
407 @Test
408 void testConst2c() {
409 final JexlEngine jexl = new JexlBuilder().strict(true).create();
410 for (final String op : Arrays.asList("=", "+=", "-=", "/=", "*=", "%=", "<<=", ">>=", ">>>=", "^=", "&=", "|=")) {
411 final JexlScript script = jexl.createScript("{ const foo = 42; } { let foo = 0; foo " + op + " 1; }");
412 assertNotNull(script);
413 }
414 }
415
416 @Test
417 void testConst3a() {
418 final JexlEngine jexl = new JexlBuilder().create();
419
420 final List<String> srcs = Arrays.asList(
421 "const f = ()->{ var foo = 3; foo = 5; }",
422 "const y = '42'; const f = (let y)->{ var foo = 3; foo = 5; }",
423 "const foo = '34'; const f = ()->{ var foo = 3; foo = 5; };",
424 "const bar = '34'; const f = ()->{ var f = 3; f = 5; };",
425 "const bar = '34'; const f = ()->{ var bar = 3; z ->{ bar += z; } };");
426
427 for (final String src : srcs) {
428 final JexlScript script = jexl.createScript(src);
429 final Object result = script.execute(null);
430 assertNotNull(result, src);
431 }
432 }
433
434 @Test
435 void testConst3b() {
436 final JexlEngine jexl = new JexlBuilder().create();
437
438 final List<String> srcs = Arrays.asList(
439 "const f = ()->{ var foo = 3; f = 5; }",
440 "const y = '42'; const f = (let z)->{ y += z; }",
441 "const foo = '34'; const f = ()->{ foo = 3; };",
442 "const bar = '34'; const f = ()->{ bar = 3; z ->{ bar += z; } };",
443 "let bar = '34'; const f = ()->{ const bar = 3; z ->{ bar += z; } };");
444
445 for (final String src : srcs) {
446 final JexlException.Assignment xassign = assertThrows(JexlException.Assignment.class, () -> jexl.createScript(src), src);
447 assertNotNull(xassign, src);
448 }
449 }
450
451 @Test
452 void testConstCaptures() {
453
454 final List<String> srcsFalse = Arrays.asList(
455 "const x = 0; x = 1;",
456 "const x = 0; x *= 1;",
457 "const x = 0; var x = 1;",
458 "const x = 0; if (true) { var x = 1;}" ,
459 "const x = 0; if (true) { x = 1;}" ,
460 "const x = 0; if (true) { var f = y -> { x = y + 1; x } }" ,
461 "const x = 0; if (true) { var f = y -> { z -> { x = y + 1; x } } }" ,
462 "const x = 0; if (true) { if (false) { y -> { x = y + 1; x } } }" ,
463 "const x = 0; if (true) { if (false) { y -> { z -> { x = y + 1; x } } }" ,
464 ""
465 );
466
467 checkParse(srcsFalse, false);
468
469 final List<String> srcsTrue = Arrays.asList(
470 "const x = 0; if (true) { var f = x -> x + 1;}" ,
471 "const x = 0; if (true) { var f = y -> { var x = y + 1; x } }" ,
472 "const x = 0; if (true) { var f = y -> { const x = y + 1; x } }" ,
473 "const x = 0; if (true) { var f = y -> { z -> { let x = y + 1; x } } }" ,
474 "");
475
476 checkParse(srcsTrue, true);
477 }
478
479 @Test
480 void testContextualOptions0() {
481 final JexlFeatures f = new JexlFeatures();
482 final JexlEngine jexl = new JexlBuilder().features(f).strict(true).create();
483 final JexlEvalContext ctxt = new JexlEvalContext();
484 final JexlOptions options = ctxt.getEngineOptions();
485 options.setSharedInstance(false);
486 options.setLexical(true);
487 options.setLexicalShade(true);
488 ctxt.set("options", options);
489 final JexlScript script = jexl.createScript("{var x = 42;} options.lexical = false; options.lexicalShade=false; x");
490 assertThrows(JexlException.class, () -> script.execute(ctxt), "setting options.lexical should have no effect during execution");
491 }
492
493 @Test
494 void testContextualOptions1() {
495 final JexlFeatures f = new JexlFeatures();
496 final JexlEngine jexl = new JexlBuilder().features(f).strict(true).create();
497 final JexlEvalContext ctxt = new TestContext();
498 final JexlOptions options = ctxt.getEngineOptions();
499 options.setSharedInstance(true);
500 options.setLexical(true);
501 options.setLexicalShade(true);
502 ctxt.set("options", options);
503
504 final JexlScript runner = jexl.createScript(
505 "options.lexical = flag; options.lexicalShade = flag;"
506 + "tryCatch(test, catcher, 42);",
507 "flag", "test", "catcher");
508
509 final JexlScript tested = jexl.createScript("(y)->{ {var x = y;} x }");
510 final JexlScript catchFn = jexl.createScript("(xany)-> { xany }");
511 Object result;
512
513 result = runner.execute(ctxt, false, tested, catchFn);
514
515 assertEquals(42, result);
516
517 result = runner.execute(ctxt, true, tested, catchFn);
518
519 assertInstanceOf(JexlException.Variable.class, result);
520 }
521
522 @Test
523 void testForVariable0a() {
524 final JexlFeatures f = new JexlFeatures();
525 f.lexical(true);
526 f.lexicalShade(true);
527 final JexlEngine jexl = createEngine(f);
528 assertThrows(JexlException.class, () -> jexl.createScript("for(let x : 1..3) { let c = 0}; return x", "Should not have been parsed"));
529 }
530
531 @Test
532 void testForVariable0b() {
533 final JexlFeatures f = new JexlFeatures();
534 f.lexical(true);
535 f.lexicalShade(true);
536 final JexlEngine jexl = createEngine(f);
537 assertThrows(JexlException.class, () -> jexl.createScript("for(var x : 1..3) { var c = 0}; return x", "Should not have been parsed"));
538 }
539
540 @Test
541 void testForVariable1a() {
542 final JexlFeatures f = new JexlFeatures();
543 f.lexical(true);
544 f.lexicalShade(true);
545 final JexlEngine jexl = createEngine(f);
546 assertThrows(JexlException.class, () -> jexl.createScript("for(var x : 1..3) { var c = 0} for(var x : 1..3) { var c = 0}; return x"),
547 "Should not have been parsed");
548 }
549
550 @Test
551 void testForVariable1b() {
552 final JexlFeatures f = new JexlFeatures();
553 f.lexical(true);
554 f.lexicalShade(true);
555 final JexlEngine jexl = createEngine(f);
556 assertThrows(JexlException.class, () -> jexl.createScript("for(let x : 1..3) { let c = 0} for(let x : 1..3) { var c = 0}; return x"),
557 "Should not have been parsed");
558 }
559
560 @Test
561 void testInnerAccess0() {
562 final JexlFeatures f = new JexlFeatures();
563 f.lexical(true);
564 final JexlEngine jexl = new JexlBuilder().strict(true).features(f).create();
565
566 final JexlScript script = jexl.createScript(
567 "var x = 32; ("
568 + "()->{ for(var x : null) { var c = 0; {return x; }} })"
569 + "();");
570
571 assertNull(script.execute(null));
572 }
573
574 @Test
575 void testInnerAccess1a() {
576 final JexlEngine jexl = new JexlBuilder().strict(true).lexical(true).create();
577 final JexlScript script = jexl.createScript("var x = 32; (()->{ for(var x : null) { var c = 0; {return x; }} })();");
578 assertNotNull(script);
579 }
580
581 @Test
582 void testInnerAccess1b() {
583 final JexlEngine jexl = new JexlBuilder().strict(true).create();
584 final JexlScript script = jexl.createScript("let x = 32; (()->{ for(let x : null) { let c = 0; { return x; } } } )(); ");
585 assertNotNull(script);
586 final String dbg = JexlTestCase.toString(script);
587 final String src = script.getSourceText();
588 assertTrue(JexlTestCase.equalsIgnoreWhiteSpace(src, dbg));
589 }
590
591 @Test
592 void testInternalLexicalFeatures() {
593 final String str = "42";
594 final JexlFeatures f = new JexlFeatures();
595 f.lexical(true);
596 f.lexicalShade(true);
597 final JexlEngine jexl = new JexlBuilder().features(f).create();
598 final JexlScript e = jexl.createScript(str);
599 final VarContext vars = new VarContext();
600 final JexlOptions opts = vars.getEngineOptions();
601
602 opts.setSharedInstance(true);
603 final Script script = (Script) e;
604 final JexlFeatures features = script.getFeatures();
605 assertTrue(features.isLexical());
606 assertTrue(features.isLexicalShade());
607 final Object result = e.execute(vars);
608 assertEquals(42, result);
609 assertTrue(opts.isLexical());
610 assertTrue(opts.isLexicalShade());
611 }
612
613 @Test
614 void testLet0() {
615 final JexlFeatures f = new JexlFeatures();
616 final JexlEngine jexl = new JexlBuilder().strict(true).create();
617 final JexlScript script = jexl.createScript(
618 "{ let x = 10; } (b->{ x + b })(32)");
619 final JexlContext jc = new MapContext();
620 jc.set("x", 11);
621 final Object result = script.execute(jc);
622 assertEquals(43, result);
623
624 }
625
626 @Test
627 void testLetFail() {
628 final List<String> srcs = Arrays.asList(
629 "let x = 0; var x = 1;",
630 "var x = 0; let x = 1;",
631 "let x = 0; let x = 1;",
632 "var x = 0; const f = (var x) -> { let x = 1; } f()",
633 "var x = 0; const f = (let x) -> { let x = 1; } f()",
634 "var x = 0; const f = (let x) -> { var x = 1; } f()",
635 ""
636 );
637 checkParse(srcs, false);
638 }
639
640 @Test
641 void testLetSucceed() {
642 final List<String> srcs = Arrays.asList(
643 "var x = 1; var x = 0;",
644 "{ let x = 0; } var x = 1;",
645 "var x = 0; var f = () -> { let x = 1; } f()",
646
647 "var x = 0; var f = (let x) -> { x = 1; } f()",
648 "var x = 0; let f = (let x) -> { x = 1; } f()",
649 "var x = 0; const f = (let x) -> { x = 1; } f()",
650 ""
651 );
652 checkParse(srcs, true);
653 }
654
655 @Test
656 void testLexical0a() {
657 runLexical0(false);
658 }
659
660 @Test
661 void testLexical0b() {
662 runLexical0(true);
663 }
664
665 @Test
666 void testLexical1() {
667 final JexlEngine jexl = new JexlBuilder().strict(true).create();
668 final JexlEvalContext ctxt = new JexlEvalContext();
669 final JexlOptions options = ctxt.getEngineOptions();
670
671 options.setLexical(true);
672 Object result;
673
674 final JexlScript script = jexl.createScript("var x = 0; for(var y : [1]) { var x = 42; return x; };");
675 JexlException xany = assertThrows(JexlException.Variable.class, () -> script.execute(ctxt));
676 assertNotNull(xany.toString());
677
678 final JexlScript script1 = jexl.createScript("(x)->{ if (x) { var x = 7 * (x + x); x; } }");
679 xany = assertThrows(JexlException.Variable.class, () -> script.execute(ctxt, 3));
680 assertNotNull(xany.toString());
681
682 final JexlScript script3 = jexl.createScript("{ var x = 0; } var x = 42; x");
683 result = script3.execute(ctxt, 21);
684 assertEquals(42, result);
685 }
686
687 @Test
688 void testLexical1a() {
689 runLexical1(false);
690 }
691
692 @Test
693 void testLexical1b() {
694 runLexical1(true);
695 }
696
697 @Test
698 void testLexical2a() {
699 runLexical2(true);
700 }
701
702 @Test
703 void testLexical2b() {
704 runLexical2(false);
705 }
706
707 @Test
708 void testLexical3() {
709 final String str = "var s = {}; for (var i : [1]) s.add(i); s";
710 final JexlEngine jexl = new JexlBuilder().strict(true).lexical(true).create();
711 JexlScript e = jexl.createScript(str);
712 final JexlContext jc = new MapContext();
713 Object o = e.execute(jc);
714 assertTrue(((Set)o).contains(1));
715
716 e = jexl.createScript(str);
717 o = e.execute(jc);
718 assertTrue(((Set)o).contains(1));
719 }
720
721 @Test
722 void testLexical4() {
723 final JexlEngine Jexl = new JexlBuilder().silent(false).strict(true).lexical(true).create();
724 final JxltEngine Jxlt = Jexl.createJxltEngine();
725 final JexlContext ctxt = new MapContext();
726 final String rpt
727 = "<report>\n"
728 + "\n$$var y = 1; var x = 2;"
729 + "\n${x + y}"
730 + "\n</report>\n";
731 final JxltEngine.Template t = Jxlt.createTemplate("$$", new StringReader(rpt));
732 final StringWriter strw = new StringWriter();
733 t.evaluate(ctxt, strw);
734 final String output = strw.toString();
735 final String ctl = "<report>\n\n3\n</report>\n";
736 assertEquals(ctl, output);
737 }
738
739 @Test
740 void testLexical5() {
741 final JexlEngine jexl = new JexlBuilder().strict(true).lexical(true).create();
742 final JexlContext ctxt = new DebugContext();
743 Object result;
744 final JexlScript script = jexl.createScript("var x = 42; var y = () -> { {var x = debug(-42); }; return x; }; y()");
745 result = script.execute(ctxt);
746 assertEquals(42, result);
747 }
748
749 @Test
750 void testLexical6a() {
751 final String str = "i = 0; { var i = 32; }; i";
752 final JexlEngine jexl = new JexlBuilder().strict(true).lexical(true).create();
753 final JexlScript e = jexl.createScript(str);
754 final JexlContext ctxt = new MapContext();
755 final Object o = e.execute(ctxt);
756 assertEquals(0, o);
757 }
758
759 @Test
760 void testLexical6a1() {
761 final String str = "i = 0; { var i = 32; }; i";
762 final JexlFeatures f = new JexlFeatures();
763 f.lexical(true);
764 final JexlEngine jexl = createEngine(f);
765 final JexlScript e = jexl.createScript(str);
766 final JexlContext ctxt = new MapContext();
767 final Object o = e.execute(ctxt);
768 assertEquals(0, o);
769 }
770
771 @Test
772 void testLexical6b() {
773 final String str = "i = 0; { var i = 32; }; i";
774 final JexlEngine jexl = new JexlBuilder().strict(true).lexical(true).lexicalShade(true).create();
775 final JexlScript e = jexl.createScript(str);
776 final JexlContext ctxt = new MapContext();
777 final JexlException xany = assertThrows(JexlException.class, () -> e.execute(ctxt), "i should be shaded");
778 assertNotNull(xany.toString());
779 }
780
781 @Test
782 void testLexical6c() {
783 final String str = "i = 0; for (var i : [42]) i; i";
784 final JexlEngine jexl = new JexlBuilder().strict(true).lexical(true).lexicalShade(false).create();
785 final JexlScript e = jexl.createScript(str);
786 final JexlContext ctxt = new MapContext();
787 final Object o = e.execute(ctxt);
788 assertEquals(0, o);
789 }
790
791 @Test
792 void testLexical6d() {
793 final String str = "i = 0; for (var i : [42]) i; i";
794 final JexlEngine jexl = new JexlBuilder().strict(true).lexical(true).lexicalShade(true).create();
795 final JexlScript e = jexl.createScript(str);
796 final JexlContext ctxt = new MapContext();
797 final JexlException xany = assertThrows(JexlException.class, () -> e.execute(ctxt), "i should be shaded");
798 assertNotNull(xany.toString());
799 }
800
801 @Test void testManyConst() {
802 final String text = "const x = 1, y = 41; x + y";
803 final JexlEngine jexl = new JexlBuilder().safe(true).create();
804 final JexlScript script = jexl.createScript(text);
805 final Object result = script.execute(null);
806 assertEquals(42, result);
807 final String s0 = script.getParsedText();
808 final String s1 = script.getSourceText();
809 assertNotEquals(s0, s1);
810 }
811
812 @Test void testManyLet() {
813 final String text = "let x = 1, y = 41, z; x + y";
814 final JexlEngine jexl = new JexlBuilder().safe(true).create();
815 final JexlScript script = jexl.createScript(text);
816 final Object result = script.execute(null);
817 assertEquals(42, result);
818 final String s0 = script.getParsedText();
819 final String s1 = script.getSourceText();
820 assertNotEquals(s0, s1);
821 }
822
823 @Test
824 void testNamed() {
825 final JexlFeatures f = new JexlFeatures();
826 f.lexical(true);
827 final JexlEngine jexl = new JexlBuilder().strict(true).features(f).create();
828 final JexlScript script = jexl.createScript("var i = (x, y, z)->{return x + y + z}; i(22,18,2)");
829 final JexlContext jc = new MapContext();
830 final Object result = script.execute(jc);
831 assertEquals(42, result);
832 }
833
834 @Test
835 void testOptionsPragma() {
836 try {
837 JexlOptions.setDefaultFlags("+safe", "-lexical", "-lexicalShade");
838 final VarContext vars = new VarContext();
839 final JexlEngine jexl = new JexlBuilder().create();
840 int n42;
841 JexlOptions o;
842
843 n42 = (Integer) jexl.createScript("#pragma jexl.options none\n-42").execute(vars);
844 assertEquals(-42, n42);
845 o = vars.snatchOptions();
846 assertNotNull(o);
847 assertTrue(o.isStrict());
848 assertTrue(o.isSafe());
849 assertTrue(o.isCancellable());
850 assertFalse(o.isLexical());
851 assertFalse(o.isLexicalShade());
852
853 n42 = (Integer) jexl.createScript("#pragma jexl.options canonical\n42").execute(vars);
854 assertEquals(42, n42);
855 o = vars.snatchOptions();
856 assertNotNull(o);
857 assertTrue(o.isStrict());
858 assertFalse(o.isSafe());
859 assertTrue(o.isCancellable());
860 assertTrue(o.isLexical());
861 assertTrue(o.isLexicalShade());
862 assertFalse(o.isSharedInstance());
863 } finally {
864 JexlOptions.setDefaultFlags("-safe", "+lexical");
865 }
866 }
867
868 @Test
869 void testParameter0() {
870 final String str = "function(u) {}";
871 final JexlEngine jexl = new JexlBuilder().create();
872 JexlScript e = jexl.createScript(str);
873 assertEquals(1, e.getParameters().length);
874 e = jexl.createScript(new JexlInfo("TestScript", 1, 1), str);
875 assertEquals(1, e.getParameters().length);
876 }
877
878 @Test
879 void testParameter1() {
880 final JexlEngine jexl = new JexlBuilder().strict(true).lexical(true).create();
881 final JexlContext jc = new MapContext();
882 final String strs = "var s = function(x) { for (var i : 1..3) {if (i > 2) return x}}; s(42)";
883 final JexlScript s42 = jexl.createScript(strs);
884 final Object result = s42.execute(jc);
885 assertEquals(42, result);
886 }
887
888 @Test
889 void testPragmaNoop() {
890
891 final String str = "#pragma jexl.options 'no effect'\ni = -42; for (var i : [42]) i; i";
892 final JexlEngine jexl = new JexlBuilder().lexical(false).strict(true).create();
893 final JexlScript e = jexl.createScript(str);
894 final JexlContext ctxt = new MapContext();
895 final Object result = e.execute(ctxt);
896 assertEquals(42, result);
897 }
898
899 @Test
900 void testPragmaOptions() {
901
902 final String str = "#pragma jexl.options '+strict +lexical +lexicalShade -safe'\n"
903 + "i = 0; for (var i : [42]) i; i";
904 final JexlEngine jexl = new JexlBuilder().strict(false).create();
905 final JexlScript e = jexl.createScript(str);
906 final JexlContext ctxt = new MapContext();
907 final JexlException xany = assertThrows(JexlException.class, () -> e.execute(ctxt), "i should be shaded");
908 assertNotNull(xany.toString());
909 }
910
911 @Test
912 void testScopeFrame() {
913 final LexicalScope scope = new LexicalScope();
914 runTestScope(scope, 0, 128, 2);
915 runTestScope(scope, 33, 55, 1);
916 runTestScope(scope, 15, 99, 3);
917 runTestScope(scope, 3, 123, 5);
918 }
919
920 @Test
921 void testSingleStatementDeclFail() {
922 final List<String> srcs = Arrays.asList(
923 "if (true) let x ;",
924 "if (true) let x = 1;",
925 "if (true) var x = 1;",
926 "if (true) { 1 } else let x ;",
927 "if (true) { 1 } else let x = 1;",
928 "if (true) { 1 } else var x = 1;",
929 "while (true) let x ;",
930 "while (true) let x = 1;",
931 "while (true) var x = 1;",
932 "do let x ; while (true)",
933 "do let x = 1; while (true)",
934 "do var x = 1; while (true)",
935 "for (let i:ii) let x ;",
936 "for (let i:ii) let x = 1;",
937 "for (let i:ii) var x = 1;",
938 ""
939 );
940 final JexlFeatures f= new JexlFeatures();
941 f.lexical(true).lexicalShade(true);
942 checkParse(f, srcs, false);
943 }
944
945 @Test
946 void testSingleStatementVarSucceed() {
947 final List<String> srcs = Arrays.asList(
948 "if (true) var x = 1;",
949 "if (true) { 1 } else var x = 1;",
950 "while (true) var x = 1;",
951 "do var x = 1 while (true)",
952 "for (let i:ii) var x = 1;",
953 ""
954 );
955 checkParse(srcs, true);
956 }
957
958 @Test
959 void testUndeclaredVariable() {
960 final JexlFeatures f = new JexlFeatures();
961 f.lexical(true);
962 f.lexicalShade(true);
963 final JexlEngine jexl = createEngine(f);
964 assertThrows(JexlException.class, () -> jexl.createScript("{var x = 0}; return x"), "Should not have been parsed");
965 }
966
967 @Test
968 void testVarFail() {
969 final List<String> srcs = Arrays.asList(
970 "var x = 0; var x = 1;",
971 "var x = 0; let x = 1;",
972 "let x = 0; var x = 1;",
973 "var x = 0; const f = (var x) -> { let x = 1; } f()",
974 "var x = 0; const f = (let x) -> { var x = 1; } f()",
975 "var x = 0; const f = (var x) -> { var x = 1; } f()",
976 ""
977 );
978 final JexlFeatures f= new JexlFeatures();
979 f.lexical(true).lexicalShade(true);
980 checkParse(f, srcs, false);
981 }
982
983 @Test
984 void testVarLoop0() {
985 final String src0 = "var count = 10;\n"
986 + "for (var i : 0 .. count-1) {\n"
987 + " $out.add(i);\n"
988 + "}";
989 final String src1 = "var count = [0,1,2,3,4,5,6,7,8,9];\n"
990 + "for (var i : count) {\n"
991 + " $out.add(i);\n"
992 + "}";
993 final String src2 = "var count = 10;\n" +
994 " var outer = 0;\n"
995 + "for (var i : 0 .. count-1) {\n"
996 + " $out.add(i);\n"
997 + " outer = i;"
998 + "}\n"
999 + "outer == 9";
1000 final JexlFeatures ff0 = runVarLoop(false, src0);
1001 final JexlFeatures ft0= runVarLoop(true, src0);
1002 final JexlFeatures ff1 = runVarLoop(false, src1);
1003 final JexlFeatures ft1= runVarLoop(true, src1);
1004 final JexlFeatures ff2 = runVarLoop(false, src2);
1005 final JexlFeatures ft2= runVarLoop(true, src2);
1006
1007
1008 assertEquals(ff0, ff1);
1009 assertEquals(ft0, ft1);
1010 assertNotEquals(ff0, ft0);
1011 final String sff0 = ff0.toString();
1012 final String sff1 = ff1.toString();
1013 assertEquals(sff0, sff1);
1014 final String sft1 = ft1.toString();
1015 assertNotEquals(sff0, sft1);
1016 }
1017 }