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    *      https://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.parser;
18  
19  import java.io.BufferedReader;
20  import java.io.IOException;
21  import java.io.StringReader;
22  import java.util.ArrayDeque;
23  import java.util.ArrayList;
24  import java.util.Arrays;
25  import java.util.Deque;
26  import java.util.HashSet;
27  import java.util.IdentityHashMap;
28  import java.util.LinkedHashSet;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.Set;
32  import java.util.TreeMap;
33  import java.util.concurrent.atomic.AtomicInteger;
34  import java.util.concurrent.atomic.AtomicReference;
35  
36  import org.apache.commons.jexl3.JexlEngine;
37  import org.apache.commons.jexl3.JexlException;
38  import org.apache.commons.jexl3.JexlFeatures;
39  import org.apache.commons.jexl3.JexlInfo;
40  import org.apache.commons.jexl3.JxltEngine;
41  import org.apache.commons.jexl3.internal.LexicalScope;
42  import org.apache.commons.jexl3.internal.Scope;
43  import org.apache.commons.jexl3.internal.TemplateEngine;
44  import org.apache.commons.jexl3.introspection.JexlUberspect;
45  
46  /**
47   * The base class for parsing, manages the parameter/local variable frame.
48   */
49  public abstract class JexlParser extends StringParser implements JexlScriptParser {
50  
51      /**
52       * A lexical unit is the container defining local symbols and their
53       * visibility boundaries.
54       */
55      public interface LexicalUnit {
56  
57          /**
58           * Declares a local symbol.
59           *
60           * @param symbol the symbol index in the scope
61           * @return true if declaration was successful, false if symbol was already declared
62           */
63          boolean declareSymbol(int symbol);
64  
65          /**
66           * @return the set of symbols identifiers declared in this unit
67           */
68          LexicalScope getLexicalScope();
69  
70          /**
71           * @return the number of local variables declared in this unit
72           */
73          int getSymbolCount();
74  
75          /**
76           * Checks whether a symbol is declared in this lexical unit.
77           *
78           * @param symbol the symbol
79           * @return true if declared, false otherwise
80           */
81          boolean hasSymbol(int symbol);
82  
83          boolean isConstant(int symbol);
84  
85          void setConstant(int symbol);
86      }
87  
88      /**
89       * The name of the options pragma.
90       */
91      public static final String PRAGMA_OPTIONS = "jexl.options";
92  
93      /**
94       * The prefix of a namespace pragma.
95       */
96      public static final String PRAGMA_JEXLNS = "jexl.namespace.";
97  
98      /**
99       * The prefix of a module pragma.
100      */
101     public static final String PRAGMA_MODULE = "jexl.module.";
102 
103     /**
104      * The import pragma.
105      */
106     public static final String PRAGMA_IMPORT = "jexl.import";
107 
108     /**
109      * The set of assignment operators as classes.
110      */
111     private static final Set<Class<? extends JexlNode>> ASSIGN_NODES = new HashSet<>(
112         Arrays.asList(
113             ASTAssignment.class,
114             ASTSetAddNode.class,
115             ASTSetSubNode.class,
116             ASTSetMultNode.class,
117             ASTSetDivNode.class,
118             ASTSetModNode.class,
119             ASTSetAndNode.class,
120             ASTSetOrNode.class,
121             ASTSetXorNode.class,
122             ASTSetShiftLeftNode.class,
123             ASTSetShiftRightNode.class,
124             ASTSetShiftRightUnsignedNode.class,
125             ASTIncrementGetNode.class,
126             ASTDecrementGetNode.class,
127             ASTGetDecrementNode.class,
128             ASTGetIncrementNode.class
129         )
130     );
131 
132     /**
133      * Pick the most significant token for error reporting.
134      *
135      * @param tokens the tokens to choose from
136      * @return the token
137      */
138     protected static Token errorToken(final Token... tokens) {
139         for (final Token token : tokens) {
140             if (token != null && token.image != null && !token.image.isEmpty()) {
141                 return token;
142             }
143         }
144         return null;
145     }
146 
147     /**
148      * Assigns the content of a token to another token.
149      *
150      * @param src the source token, if null, the destination token is returned
151      * @param dest the destination token, if null, the source token is returned
152      * @return the destination token with the content of the source token
153      */
154     static Token assignToken(final Token src, final Token dest) {
155         if (dest == null) {
156             return src;
157         }
158         if (src != null) {
159             dest.beginLine = src.beginLine;
160             dest.beginColumn = src.beginColumn;
161             dest.endLine = src.endLine;
162             dest.endColumn = src.endColumn;
163             dest.image = src.image;
164             dest.kind = src.kind;
165             dest.next = src.next;
166             dest.specialToken = src.specialToken;
167         }
168         return dest;
169     }
170 
171     /**
172      * Reads a given source line.
173      *
174      * @param src the source
175      * @param lineno the line number
176      * @return the line
177      */
178     protected static String readSourceLine(final String src, final int lineno) {
179         String msg = "";
180         if (src != null && lineno >= 0) {
181             try {
182                 final BufferedReader reader = new BufferedReader(new StringReader(src));
183                 for (int l = 0; l < lineno; ++l) {
184                     msg = reader.readLine();
185                 }
186             } catch (final IOException xio) {
187                 // ignore, very unlikely but then again...
188             }
189         }
190         return msg;
191     }
192 
193     /**
194      * Utility function to create '.' separated string from a list of string.
195      *
196      * @param lstr the list of strings
197      * @return the dotted version
198      */
199     protected static String stringify(final Iterable<String> lstr) {
200         return String.join(".", lstr);
201     }
202 
203     /**
204      * The associated controller.
205      */
206     protected final FeatureController featureController;
207 
208     /**
209      * The basic source info.
210      */
211     protected JexlInfo info;
212 
213     /**
214      * The source being processed.
215      */
216     protected String source;
217 
218     /**
219      * The map of named registers aka script parameters.
220      * <p>Each parameter is associated with a register and is materialized
221      * as an offset in the registers array used during evaluation.</p>
222      */
223     protected final AtomicReference<Scope> scopeReference;
224 
225     /**
226      * When parsing inner functions/lambda, need to stack the scope (sic).
227      */
228     protected final Deque<Scope> scopes;
229 
230     /**
231      * The list of pragma declarations.
232      */
233     protected Map<String, Object> pragmas;
234 
235     /**
236      * The optional class name and constant resolver.
237      */
238     protected final AtomicReference<JexlUberspect.ClassConstantResolver> fqcnResolver;
239 
240     /**
241      * The list of imports.
242      * <p>Imports are used to resolve simple class names into fully qualified class names.</p>
243      */
244     protected final List<String> imports;
245 
246 
247     void addImport(final String importName) {
248         if (importName != null && !importName.isEmpty() && !imports.contains(importName)) {
249             imports.add(importName);
250         }
251     }
252 
253     Object resolveConstant(final String name) {
254         JexlUberspect.ClassConstantResolver resolver = fqcnResolver.get();
255         if (resolver == null) {
256             final JexlEngine engine = JexlEngine.getThreadEngine();
257             if (engine instanceof JexlUberspect.ConstantResolverFactory) {
258                 resolver = ((JexlUberspect.ConstantResolverFactory) engine).createConstantResolver(imports);
259                 fqcnResolver.set(resolver);
260             }
261         }
262         return resolver != null
263             ? resolver.resolveConstant(name)
264             : JexlEngine.TRY_FAILED;
265     }
266 
267     /**
268      * Whether automatic semicolon insertion is enabled.
269      */
270     protected boolean autoSemicolon = true;
271 
272     /**
273      * The known namespaces.
274      */
275     protected Set<String> namespaces;
276 
277     /**
278      * The number of nested loops.
279      */
280     protected AtomicInteger loopCount;
281 
282     /**
283      * Stack of parsing loop counts.
284      */
285     protected final Deque<Integer> loopCounts;
286 
287     /**
288      * The current lexical block.
289      */
290     protected final AtomicReference<LexicalUnit> blockReference;
291 
292     /**
293      * Stack of lexical blocks.
294      */
295     protected final Deque<LexicalUnit> blocks;
296 
297     /**
298      * The map of lexical to functional blocks.
299      */
300     protected final Map<LexicalUnit, Scope> blockScopes;
301 
302     /**
303      * The parent parser if any.
304      */
305     protected final JexlParser parent;
306 
307     /**
308      * Creates a new parser.
309      * <p>
310      * This constructor is protected so that it can only be used by subclasses.
311      * </p>
312      */
313     protected JexlParser() {
314         this(null);
315     }
316 
317     /**
318      * Creates a new inner-parser.
319      * <p>
320      * This is the constructor used to create a parser for template expressions.
321      * </p>
322      */
323     protected JexlParser(final JexlParser parser) {
324         this.info = null;
325         this.source = null;
326         if (parser != null) {
327             parent = parser;
328             featureController = parser.featureController;
329             scopeReference = parser.scopeReference;
330             scopes = parser.scopes;
331             pragmas = parser.pragmas;
332             namespaces = parser.namespaces;
333             loopCount = parser.loopCount;
334             loopCounts = parser.loopCounts;
335             blockReference = parser.blockReference;
336             blocks = parser.blocks;
337             blockScopes = parser.blockScopes;
338             fqcnResolver = parser.fqcnResolver;
339             imports = parser.imports;
340             autoSemicolon = parser.autoSemicolon;
341         } else {
342             parent = null;
343             featureController = new FeatureController(JexlEngine.DEFAULT_FEATURES);
344             scopeReference = new AtomicReference<>();
345             blockReference = new AtomicReference<>();
346             fqcnResolver = new AtomicReference<>();
347             loopCount = new AtomicInteger();
348             scopes = new ArrayDeque<>();
349             loopCounts = new ArrayDeque<>();
350             blocks = new ArrayDeque<>();
351             blockScopes = new IdentityHashMap<>();
352             imports = new ArrayList<>();
353         }
354     }
355 
356     /**
357      * The name of the null case constant.
358      */
359     public static final Object NIL = new Object() {
360         @Override
361         public String toString() {
362             return "null";
363         }
364     };
365 
366     /**
367      * The name of the default case constant.
368      */
369     public static final Object DFLT = new Object() {
370         @Override
371         public String toString() {
372             return "default";
373         }
374     };
375 
376     /**
377      * The name of the default NaN constant.
378      */
379     public static final Object NAN = new Object() {
380         @Override
381         public String toString() {
382             return "NaN";
383         }
384     };
385 
386     /**
387      * Encode a value to a switch predicate.
388      *
389      * @param value the value.
390      * @return the encoded value, which is either the value itself, or NAN (for NaN) or NIL (for null).
391      */
392     static Object switchCode(final Object value) {
393         if (value == null) {
394             return NIL;
395         }
396         if (value instanceof Double && ((Double) value).isNaN()) {
397             return NAN;
398         }
399         return value;
400     }
401 
402     /**
403      * Constructs a set of constants amenable to switch expression.
404      */
405     protected SwitchSet switchSet() {
406         return new SwitchSet();
407     }
408 
409     protected class SwitchSet extends LinkedHashSet<Object> {
410         @Override
411         public boolean add(final Object value) {
412             final Object code = switchCode(value);
413             if (!super.add(code)) {
414                 throw new JexlException.Parsing(info, "duplicate constant value: " + value);
415             }
416             return true;
417         }
418     }
419 
420     /**
421      * Internal, for debug purpose only.
422      *
423      * @param registers sets whether this parser recognizes the register syntax
424      */
425     public void allowRegisters(final boolean registers) {
426         featureController.setFeatures(new JexlFeatures(featureController.getFeatures()).register(registers));
427     }
428 
429     /**
430      * Tests whether a given variable name is allowed.
431      *
432      * @param image the name.
433      * @return true if allowed, false if reserved.
434      */
435     protected boolean allowVariable(final String image) {
436         final JexlFeatures features = getFeatures();
437         if (!features.supportsLocalVar()) {
438             return false;
439         }
440         if (features.isReservedName(image)) {
441             return false;
442         }
443         return true;
444     }
445 
446     /**
447      * Check fat vs thin arrow syntax feature.
448      *
449      * @param token the arrow token.
450      */
451     protected void checkLambda(final Token token) {
452         final String arrow = token.image;
453         if ("->".equals(arrow)) {
454             if (!getFeatures().supportsThinArrow()) {
455                 throwFeatureException(JexlFeatures.THIN_ARROW, token);
456             }
457             return;
458         }
459         if ("=>".equals(arrow) && !getFeatures().supportsFatArrow()) {
460             throwFeatureException(JexlFeatures.FAT_ARROW, token);
461         }
462     }
463 
464     /**
465      * Checks whether an identifier is a local variable or argument, ie a symbol, stored in a register.
466      *
467      * @param identifier the identifier.
468      * @param name      the identifier name.
469      * @return the image.
470      */
471     protected String checkVariable(final ASTIdentifier identifier, final String name) {
472         final Scope scope = scopeReference.get();
473         if (scope != null) {
474             final Integer symbol = scope.getSymbol(name);
475             if (symbol != null) {
476                 identifier.setLexical(scope.isLexical(symbol));
477                 boolean declared = true;
478                 if (scope.isCapturedSymbol(symbol)) {
479                     // captured are declared in all cases
480                     identifier.setCaptured(true);
481                 } else {
482                     LexicalUnit unit = getUnit();
483                     declared = unit.hasSymbol(symbol);
484                     // one of the lexical blocks above should declare it
485                     if (!declared) {
486                         for (final LexicalUnit u : blocks) {
487                             if (u.hasSymbol(symbol)) {
488                                 unit = u;
489                                 declared = true;
490                                 break;
491                             }
492                         }
493                     }
494                     if (declared) {
495                         // track if const is defined or not
496                         if (unit.isConstant(symbol)) {
497                             identifier.setConstant(true);
498                         }
499                     } else if (info instanceof JexlNode.Info) {
500                         declared = isSymbolDeclared((JexlNode.Info) info, symbol);
501                     }
502                 }
503                 identifier.setSymbol(symbol, name);
504                 if (!declared) {
505                     if (getFeatures().isLexicalShade()) {
506                         // cannot reuse a local as a global
507                         throw new JexlException.Parsing(info, name + ": variable is not declared").clean();
508                     }
509                     identifier.setShaded(true);
510                 }
511             }
512         }
513         return name;
514     }
515 
516     /**
517      * Cleanup.
518      *
519      * @param features the feature set to restore if any.
520      */
521     protected void cleanup(final JexlFeatures features) {
522         info = null;
523         source = null;
524         if (parent == null) {
525             scopeReference.set(null);
526             scopes.clear();
527             pragmas = null;
528             namespaces = null;
529             fqcnResolver.set(null);
530             imports.clear();
531             loopCounts.clear();
532             loopCount.set(0);
533             blocks.clear();
534             blockReference.set(null);
535             blockScopes.clear();
536             setFeatures(features);
537         }
538     }
539 
540     /**
541      * Disables pragma feature if pragma-anywhere feature is disabled.
542      */
543     protected void controlPragmaAnywhere() {
544         final JexlFeatures features = getFeatures();
545         if (features.supportsPragma() && !features.supportsPragmaAnywhere()) {
546             featureController.setFeatures(new JexlFeatures(featureController.getFeatures()).pragma(false));
547         }
548     }
549 
550     /**
551      * Declares a local function.
552      *
553      * @param variable the identifier used to declare.
554      * @param token      the variable name token.
555      */
556     protected void declareFunction(final ASTVar variable, final Token token) {
557         final String name = token.image;
558         // function foo() ... <=> const foo = ()->...
559         Scope scope = scopeReference.get();
560         if (scope == null) {
561             scope = new Scope(null);
562             scopeReference.set(scope);
563         }
564         final int symbol = scope.declareVariable(name);
565         variable.setSymbol(symbol, name);
566         variable.setLexical(true);
567         if (scope.isCapturedSymbol(symbol)) {
568             variable.setCaptured(true);
569         }
570         // function is const fun...
571         if (declareSymbol(symbol)) {
572             scope.addLexical(symbol);
573             final LexicalUnit block = getUnit();
574             block.setConstant(symbol);
575         } else {
576             if (getFeatures().isLexical()) {
577                 throw new JexlException(variable, name + ": variable is already declared");
578             }
579             variable.setRedefined(true);
580         }
581     }
582 
583     /**
584      * Declares a local parameter.
585      * <p>
586      * This method creates a new entry in the symbol map.
587      * </p>
588      *
589      * @param token the parameter name token.
590      * @param lexical whether the parameter is lexical or not.
591      * @param constant whether the parameter is constant or not.
592      */
593     protected void declareParameter(final Token token, final boolean lexical, final boolean constant) {
594         final String identifier =  token.image;
595         if (!allowVariable(identifier)) {
596             throwFeatureException(JexlFeatures.LOCAL_VAR, token);
597         }
598         Scope scope = scopeReference.get();
599         if (scope == null) {
600             scope = new Scope(null, (String[]) null);
601             scopeReference.set(scope);
602         }
603         final int symbol = scope.declareParameter(identifier);
604         // not sure how declaring a parameter could fail...
605         // lexical feature error
606         final LexicalUnit block = getUnit();
607         if (!block.declareSymbol(symbol)) {
608             if (lexical || getFeatures().isLexical()) {
609                 final JexlInfo xinfo = info.at(token.beginLine, token.beginColumn);
610                 throw new JexlException.Parsing(xinfo, identifier + ": parameter is already declared").clean();
611             }
612         } else if (lexical) {
613             scope.addLexical(symbol);
614             if (constant) {
615                 block.setConstant(symbol);
616             }
617         }
618     }
619 
620     /**
621      * Adds a pragma declaration.
622      *
623      * @param key the pragma key.
624      * @param value the pragma value.
625      */
626     protected void declarePragma(final String key, final Object value) {
627         final JexlFeatures features = getFeatures();
628         if (!features.supportsPragma()) {
629             throwFeatureException(JexlFeatures.PRAGMA, getToken(0));
630         }
631         if (PRAGMA_IMPORT.equals(key) && !features.supportsImportPragma()) {
632             throwFeatureException(JexlFeatures.IMPORT_PRAGMA, getToken(0));
633         }
634         if (pragmas == null) {
635             pragmas = new TreeMap<>();
636         }
637         // declaring a namespace or module
638         final String[] nsprefixes = { PRAGMA_JEXLNS, PRAGMA_MODULE };
639         for(final String nsprefix : nsprefixes) {
640             if (key.startsWith(nsprefix)) {
641                 if (!features.supportsNamespacePragma()) {
642                     throwFeatureException(JexlFeatures.NS_PRAGMA, getToken(0));
643                 }
644                 final String nsname = key.substring(nsprefix.length());
645                 if (!nsname.isEmpty()) {
646                     if (namespaces == null) {
647                         namespaces = new HashSet<>();
648                     }
649                     namespaces.add(nsname);
650                 }
651                 break;
652             }
653         }
654         // merge new value into a set created on the fly if key is already mapped
655         if (value == null) {
656             pragmas.putIfAbsent(key, null);
657         } else {
658             pragmas.merge(key, value, (previous, newValue) -> {
659                 if (previous instanceof Set<?>) {
660                     ((Set<Object>) previous).add(newValue);
661                     return previous;
662                 }
663                 final Set<Object> values = new LinkedHashSet<>();
664                 values.add(previous);
665                 values.add(newValue);
666                 return values;
667             });
668         }
669     }
670 
671     /**
672      * Declares a symbol.
673      *
674      * @param symbol the symbol index.
675      * @return true if symbol can be declared in lexical scope, false (error)
676      * if it is already declared.
677      */
678     private boolean declareSymbol(final int symbol) {
679         for (final LexicalUnit lu : blocks) {
680             if (lu.hasSymbol(symbol)) {
681                 return false;
682             }
683             // stop at first new scope reset, aka lambda
684             if (lu instanceof ASTJexlLambda) {
685                 break;
686             }
687         }
688         final LexicalUnit block = getUnit();
689         return block == null || block.declareSymbol(symbol);
690     }
691 
692     /**
693      * Declares a local variable.
694      * <p>
695      * This method creates an new entry in the symbol map.
696      * </p>
697      *
698      * @param variable the identifier used to declare.
699      * @param lexical  whether the symbol is lexical.
700      * @param constant whether the symbol is constant.
701      * @param token    the variable name token.
702      */
703     protected void declareVariable(final ASTVar variable, final Token token, final boolean lexical, final boolean constant) {
704         final String name = token.image;
705         if (!allowVariable(name)) {
706             throwFeatureException(JexlFeatures.LOCAL_VAR, token);
707         }
708         Scope scope = scopeReference.get();
709         if (scope == null) {
710             scope = new Scope(null);
711             scopeReference.set(scope);
712         }
713         final int symbol = scope.declareVariable(name);
714         variable.setSymbol(symbol, name);
715         variable.setLexical(lexical);
716         variable.setConstant(constant);
717         if (scope.isCapturedSymbol(symbol)) {
718             variable.setCaptured(true);
719         }
720         // if not the first time we declare this symbol...
721         if (!declareSymbol(symbol)) {
722             if (lexical || scope.isLexical(symbol) || getFeatures().isLexical()) {
723                 final JexlInfo location = info.at(token.beginLine, token.beginColumn);
724                 throw new JexlException.Parsing(location, name + ": variable is already declared").clean();
725             }
726             // not lexical, redefined nevertheless
727             variable.setRedefined(true);
728         } else if (lexical) {
729             scope.addLexical(symbol);
730             if (constant) {
731                 getUnit().setConstant(symbol);
732             }
733         }
734     }
735 
736     /**
737      * Gets the current set of features active during parsing.
738      *
739      * @return the current set of features active during parsing.
740      */
741     protected JexlFeatures getFeatures() {
742         return featureController.getFeatures();
743     }
744 
745     /**
746      * Gets the frame used by this parser.
747      * <p>
748      * Since local variables create new symbols, it is important to
749      * regain access after parsing to known which / how-many registers are needed.
750      * </p>
751      *
752      * @return the named register map
753      */
754     protected Scope getScope() {
755         return scopeReference.get();
756     }
757 
758     /**
759      * Overridden in actual parser to access tokens stack.
760      *
761      * @param index 0 to get current token.
762      * @return the token on the stack.
763      */
764     protected abstract Token getToken(int index);
765 
766     /**
767      * Gets the lexical unit used by this parser.
768      *
769      * @return the named register map.
770      */
771     protected LexicalUnit getUnit() {
772         return blockReference.get();
773     }
774 
775     /**
776      * Default implementation does nothing but is overridden by generated code.
777      *
778      * @param top whether the identifier is beginning an l/r value.
779      * @throws ParseException subclasses may throw ParseException.
780      */
781     @SuppressWarnings("unused") // subclasses may throw ParseException
782     protected void Identifier(final boolean top) throws ParseException {
783         // Overridden by generated code
784     }
785 
786     /**
787      * Checks whether a symbol has been declared as a const in the current stack of lexical units.
788      *
789      * @param symbol the symbol.
790      * @return true if constant, false otherwise.
791      */
792     private boolean isConstant(final int symbol) {
793         if (symbol >= 0) {
794             final LexicalUnit block = getUnit();
795             if (block != null && block.hasSymbol(symbol)) {
796                 return block.isConstant(symbol);
797             }
798             Scope blockScope = blockScopes.get(block);
799             int lexical = symbol;
800             for (final LexicalUnit unit : blocks) {
801                 final Scope unitScope = blockScopes.get(unit);
802                 // follow through potential capture
803                 if (blockScope != unitScope) {
804                     final int declared = blockScope != null ? blockScope.getCaptureDeclaration(lexical) : -1;
805                     if (declared >= 0) {
806                         lexical = declared;
807                     }
808                     if (unitScope != null) {
809                         blockScope = unitScope;
810                     }
811                 }
812                 if (unit.hasSymbol(lexical)) {
813                     return unit.isConstant(lexical);
814                 }
815             }
816         }
817         return false;
818     }
819 
820     /**
821      * Checks whether a name is a declared namespace.
822      *
823      * @param name the namespace name.
824      * @return true if declared, false otherwise.
825      */
826     private boolean isNamespace(final String name) {
827         // templates
828         if ("jexl".equals(name) || "$jexl".equals(name)) {
829             return true;
830         }
831         final Set<String> ns = namespaces;
832         // declared through local pragma ?
833         if (ns != null && ns.contains(name)) {
834             return true;
835         }
836         // declared through engine features ?
837         return getFeatures().namespaceTest().test(name);
838     }
839 
840     /**
841      * Semantic check identifying whether a list of 4 tokens forms a namespace function call.
842      * <p>This is needed to disambiguate ternary operator, map entries and actual calls.</p>
843      * <p>Note that this check is performed before syntactic check so the expected parameters need to be
844      * verified.</p>
845      *
846      * @param ns the namespace token.
847      * @param colon expected to be &quot;:&quot;
848      * @param fun the function name
849      * @param paren expected to be &quot;(&quot;
850      * @return true if the name qualifies a namespace function call.
851      */
852     protected boolean isNamespaceFuncall(final Token ns, final Token colon, final Token fun, final Token paren) {
853         // let's make sure this is a namespace function call
854         if (!":".equals(colon.image)) {
855             return false;
856         }
857         if (!"(".equals(paren.image)) {
858             return false;
859         }
860         // namespace as identifier means no spaces in between ns, colon and fun, no matter what
861         if (featureController.getFeatures().supportsNamespaceIdentifier()) {
862             return colon.beginColumn - 1 == ns.endColumn
863                     && colon.endColumn == fun.beginColumn - 1;
864         }
865         // if namespace name is shared with a variable name
866         // or if fun is a variable name (likely a function call),
867         // use syntactic hint
868         if (isVariable(ns.image) || isVariable(fun.image)) {
869             // the namespace sticks to the colon as in 'ns:fun()' (vs 'ns : fun()')
870             return colon.beginColumn - 1 == ns.endColumn
871                     && (colon.endColumn == fun.beginColumn - 1 || isNamespace(ns.image));
872         }
873         return true;
874     }
875 
876     /**
877      * Checks if a symbol is defined in lexical scopes.
878      * <p>This works with parsed scripts in template resolution only.
879      *
880      * @param info an info linked to a node.
881      * @param symbol the symbol number.
882      * @return true if symbol accessible in lexical scope.
883      */
884     private boolean isSymbolDeclared(final JexlNode.Info info, final int symbol) {
885         JexlNode walk = info.getNode();
886         while(walk != null) {
887             if (walk instanceof JexlParser.LexicalUnit) {
888                 final LexicalScope scope = ((JexlParser.LexicalUnit) walk).getLexicalScope();
889                 if (scope != null && scope.hasSymbol(symbol)) {
890                     return true;
891                 }
892                 // stop at first new scope reset, aka lambda
893                 if (walk instanceof ASTJexlLambda) {
894                     break;
895                 }
896             }
897             walk = walk.jjtGetParent();
898         }
899         return false;
900     }
901 
902     /**
903      * Checks whether an identifier is a local variable or argument.
904      *
905      * @param name the variable name.
906      * @return true if a variable with that name was declared.
907      */
908     protected boolean isVariable(final String name) {
909         final Scope scope = scopeReference.get();
910         return scope != null && scope.getSymbol(name) != null;
911     }
912 
913     /**
914      * Checks whether a statement is ambiguous.
915      * <p>
916      * This is used to detect statements that are not terminated by a semicolon,
917      * and that may be confused with an expression.
918      * </p>
919      *
920      * @param semicolon the semicolon token kind.
921      * @return true if statement is ambiguous, false otherwise.
922      */
923     protected boolean isAmbiguousStatement(final int semicolon) {
924         if (autoSemicolon) {
925             final Token current = getToken(0);
926             final Token next = getToken(1);
927             if (current != null && next != null && current.endLine != next.beginLine) {
928                 // if the next token is on a different line, no ambiguity reported
929                 return false;
930             }
931         }
932         return !getFeatures().supportsAmbiguousStatement();
933     }
934 
935     /**
936      * Called by parser at end of node construction.
937      * <p>
938      * Detects "Ambiguous statement" and 'non-left value assignment'.</p>
939      *
940      * @param node the node.
941      * @throws JexlException.Parsing when parsing fails.
942      */
943     protected void jjtreeCloseNodeScope(final JexlNode node) {
944         if (node instanceof ASTAmbiguous) {
945             throwAmbiguousException(node);
946         }
947         if (node instanceof ASTJexlScript) {
948             if (node instanceof ASTJexlLambda && !getFeatures().supportsLambda()) {
949                 throwFeatureException(JexlFeatures.LAMBDA, node.jexlInfo());
950             }
951             final ASTJexlScript script = (ASTJexlScript) node;
952             // reaccess in case local variables have been declared
953             final Scope scope = scopeReference.get();
954             if (script.getScope() != scope) {
955                 script.setScope(scope);
956             }
957         } else if (ASSIGN_NODES.contains(node.getClass())) {
958             final JexlNode lv = node.jjtGetChild(0);
959             if (!lv.isLeftValue()) {
960                 JexlInfo xinfo = lv.jexlInfo();
961                 xinfo = info.at(xinfo.getLine(), xinfo.getColumn());
962                 final String msg = readSourceLine(source, xinfo.getLine());
963                 throw new JexlException.Assignment(xinfo, msg).clean();
964             }
965             if (lv instanceof ASTIdentifier && !(lv instanceof ASTVar)) {
966                 final ASTIdentifier varName = (ASTIdentifier) lv;
967                 if (isConstant(varName.getSymbol())) { // if constant, fail...
968                     JexlInfo xinfo = lv.jexlInfo();
969                     xinfo = info.at(xinfo.getLine(), xinfo.getColumn());
970                     throw new JexlException.Assignment(xinfo, varName.getName()).clean();
971                 }
972             }
973         }
974         // heavy check
975         featureController.controlNode(node);
976     }
977 
978     /**
979      * Parses an embedded Jexl expression within an interpolation node.
980      * <p>This creates a sub-parser that shares the scopes of the parent parser.</p>
981      *
982      * @param info the JexlInfo
983      * @param src the source to parse
984      * @return the parsed tree
985      */
986     @Override
987     public ASTJexlScript jxltParse(final JexlInfo info, final JexlFeatures features, final String src, final Scope scope) {
988         JexlFeatures previous = getFeatures();
989         try {
990             return new Parser(this).parse(info, features, src, scope);
991         } catch (JexlException ex) {
992             cleanup(previous);
993             throw ex;
994         }
995     }
996 
997     /**
998      * Parses an interpolation expression.
999      * <p>Requires the JEXL engine to be accessible through its thread-local.</p>
1000      *
1001      * @param info the JexlInfo
1002      * @param src the source to parse
1003      * @param scope the scope
1004      * @return the expression
1005      */
1006     static JxltEngine.Expression parseInterpolation(final JexlInfo info, final String src, final Scope scope) {
1007         final JexlEngine jexl = JexlEngine.getThreadEngine();
1008         if (jexl != null) {
1009             // interpolation uses default $ and # as expression markers;
1010             // the cache size is negative to reuse the engine cache
1011             final JxltEngine jxlt = jexl.createJxltEngine(true, -1, '$', '#');
1012             if (jxlt instanceof TemplateEngine) {
1013                 return ((TemplateEngine) jxlt).createExpression(info, src, scope);
1014             }
1015         }
1016         throw new IllegalStateException("engine is not a accessible");
1017     }
1018 
1019     /**
1020      * Called by parser at the beginning of a node construction.
1021      *
1022      * @param node the node.
1023      */
1024     protected void jjtreeOpenNodeScope(final JexlNode node) {
1025         // nothing
1026     }
1027 
1028     /**
1029      * Starts the definition of a lambda.
1030      *
1031      * @param jjtThis the script.
1032      */
1033     protected void beginLambda(final ASTJexlScript jjtThis) {
1034         jjtThis.setFeatures(getFeatures());
1035         pushScope();
1036         pushUnit(jjtThis);
1037     }
1038 
1039     /**
1040      * Ends the definition of a lambda.
1041      *
1042      * @param jjtThis the script.
1043      */
1044     protected void endLambda(final ASTJexlScript jjtThis) {
1045         popUnit(jjtThis);
1046         popScope();
1047     }
1048 
1049     /**
1050      * Pops back to previous local variable scope.
1051      */
1052     protected void popScope() {
1053         final Scope scope = scopes.isEmpty() ? null : scopes.pop();
1054         scopeReference.set(scope);
1055         if (!loopCounts.isEmpty()) {
1056             loopCount.set(loopCounts.pop());
1057         }
1058     }
1059 
1060     /**
1061      * Restores the previous lexical unit.
1062      *
1063      * @param unit restores the previous lexical scope.
1064      */
1065     protected void popUnit(final LexicalUnit unit) {
1066         final LexicalUnit block = blockReference.get();
1067         if (block == unit){
1068             blockScopes.remove(unit);
1069             blockReference.set(blocks.isEmpty()? null : blocks.pop());
1070         }
1071     }
1072 
1073     /**
1074      * Creates a new local variable scope and push it as current.
1075      */
1076     protected void pushScope() {
1077         Scope scope = scopeReference.get();
1078         if (scope != null) {
1079             scopes.push(scope);
1080         }
1081         scope = new Scope(scope, (String[]) null);
1082         scopeReference.set(scope);
1083         loopCounts.push(loopCount.getAndSet(0));
1084     }
1085 
1086     /**
1087      * Pushes a new lexical unit.
1088      *
1089      * @param unit the new lexical unit.
1090      */
1091     protected void pushUnit(final LexicalUnit unit) {
1092         final Scope scope = scopeReference.get();
1093         blockScopes.put(unit, scope);
1094         final LexicalUnit block = blockReference.get();
1095         if (block != null) {
1096             blocks.push(block);
1097         }
1098         blockReference.set(unit);
1099     }
1100 
1101     /**
1102      * Escape any outer (parent) loops.
1103      * <p>A lambda definition embedded in a for-block escapes that block;
1104      * break/continue are not valid within that lambda.</p>
1105      */
1106     protected void pushLoop() {
1107         loopCounts.push(loopCount.getAndSet(0));
1108     }
1109 
1110     /**
1111      * Restores the previous loop count.
1112      */
1113     protected void popLoop() {
1114         if (!loopCounts.isEmpty()) {
1115             loopCount.set(loopCounts.pop());
1116         }
1117     }
1118 
1119     /**
1120      * Sets a new set of options.
1121      *
1122      * @param features the parser features
1123      */
1124     protected void setFeatures(final JexlFeatures features) {
1125         this.featureController.setFeatures(features);
1126     }
1127 
1128     /**
1129      * Throws Ambiguous exception.
1130      * <p>
1131      * Seeks the end of the ambiguous statement to recover.
1132      * </p>
1133      *
1134      * @param node the first token in ambiguous expression.
1135      * @throws JexlException.Ambiguous in all cases.
1136      */
1137     protected void throwAmbiguousException(final JexlNode node) {
1138         final JexlInfo begin = node.jexlInfo(info.getName());
1139         final Token t = getToken(0);
1140         final JexlInfo end = info.at(t.beginLine, t.endColumn);
1141         final String msg = readSourceLine(source, end.getLine());
1142         throw new JexlException.Ambiguous(begin, end, msg).clean();
1143     }
1144 
1145     /**
1146      * Throws a feature exception.
1147      *
1148      * @param feature the feature code.
1149      * @param info the exception surroundings.
1150      * @throws JexlException.Feature in all cases.
1151      */
1152     protected void throwFeatureException(final int feature, final JexlInfo info) {
1153         final String msg = info != null ? readSourceLine(source, info.getLine()) : null;
1154         throw new JexlException.Feature(info, feature, msg).clean();
1155     }
1156 
1157     /**
1158      * Throws a feature exception.
1159      *
1160      * @param feature the feature code.
1161      * @param trigger the token that triggered it.
1162      * @throws JexlException.Parsing if actual error token cannot be found.
1163      * @throws JexlException.Feature in all other cases.
1164      */
1165     protected void throwFeatureException(final int feature, final Token trigger) {
1166         Token token = trigger;
1167         if (token == null) {
1168             token = getToken(0);
1169             if (token == null) {
1170                 throw new JexlException.Parsing(null, JexlFeatures.stringify(feature)).clean();
1171             }
1172         }
1173         final JexlInfo xinfo = info.at(token.beginLine, token.beginColumn);
1174         throwFeatureException(feature, xinfo);
1175     }
1176 
1177     /**
1178      * Throws a parsing exception.
1179      *
1180      * @param parsed the token to report.
1181      * @throws JexlException.Parsing in all cases.
1182      */
1183     protected void throwParsingException(final Token parsed) {
1184         JexlInfo xinfo  = null;
1185         String msg = "unrecoverable state";
1186         Token token = parsed;
1187         if (token == null) {
1188             token = getToken(0);
1189         }
1190         if (token != null) {
1191             xinfo = info.at(token.beginLine, token.beginColumn);
1192             msg = token.image;
1193         }
1194         throw new JexlException.Parsing(xinfo, msg).clean();
1195     }
1196 }