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