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