View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.jexl3.internal;
18  
19  import java.util.ArrayList;
20  import java.util.Arrays;
21  import java.util.LinkedHashMap;
22  import java.util.List;
23  import java.util.Map;
24  
25  /**
26   * A script scope, stores the declaration of parameters and local variables as symbols.
27   * <p>This also acts as the functional scope and variable definition store.</p>
28   * @since 3.0
29   */
30  public final class Scope {
31      /**
32       * The value of an as-yet  undeclared but variable, for instance: x; before var x;.
33       */
34      static final Object UNDECLARED = new Object() {
35          @Override public String toString() {
36              return "??";
37          }
38      };
39      /**
40       * The value of a declared but undefined variable, for instance: var x;.
41       */
42      static final Object UNDEFINED = new Object() {
43          @Override public String toString() {
44              return "?";
45          }
46      };
47      /**
48       * The parent scope.
49       */
50      private final Scope parent;
51      /**
52       * The number of parameters.
53       */
54      private int parms;
55      /**
56       * The number of local variables.
57       */
58      private int vars;
59      /**
60       * The map of named variables aka script parameters and local variables.
61       * Each parameter is associated to a symbol and is materialized as an offset in the stacked array used
62       * during evaluation.
63       */
64      private Map<String, Integer> namedVariables = null;
65      /**
66       * The map of local captured variables to parent scope variables, ie closure.
67       */
68      private Map<Integer, Integer> capturedVariables = null;
69      /**
70       * Let symbols.
71       */
72      private LexicalScope lexicalVariables = null;
73  
74      /**
75       * The empty string array.
76       */
77      private static final String[] EMPTY_STRS = {};
78  
79      /**
80       * Creates a new scope with a list of parameters.
81       * @param scope the parent scope if any
82       * @param parameters the list of parameters
83       */
84      public Scope(final Scope scope, final String... parameters) {
85          if (parameters != null) {
86              parms = parameters.length;
87              namedVariables = new LinkedHashMap<>();
88              for (int p = 0; p < parms; ++p) {
89                  namedVariables.put(parameters[p], p);
90              }
91          } else {
92              parms = 0;
93          }
94          vars = 0;
95          parent = scope;
96      }
97  
98      /**
99       * Checks whether an identifier is a local variable or argument, ie a symbol.
100      * If this fails, look in parents for symbol that can be captured.
101      * @param name the symbol name
102      * @return the symbol index
103      */
104     public Integer getSymbol(final String name) {
105         return getSymbol(name, true);
106     }
107 
108     /**
109      * Checks whether an identifier is a local variable or argument, ie a symbol.
110      * @param name the symbol name
111      * @param capture whether solving by capturing a parent symbol is allowed
112      * @return the symbol index
113      */
114     private Integer getSymbol(final String name, final boolean capture) {
115         Integer register = namedVariables != null ? namedVariables.get(name) : null;
116         if (register == null && capture && parent != null) {
117             final Integer pr = parent.getSymbol(name, true);
118             if (pr != null) {
119                 if (capturedVariables == null) {
120                     capturedVariables = new LinkedHashMap<>();
121                 }
122                 if (namedVariables == null) {
123                     namedVariables = new LinkedHashMap<>();
124                 }
125                 register = namedVariables.size();
126                 namedVariables.put(name, register);
127                 capturedVariables.put(register, pr);
128             }
129         }
130         return register;
131     }
132 
133     /**
134      * Marks a symbol as a lexical, declared through let or const.
135      * @param s the symbol
136      * @return true if the symbol was not already present in the lexical set
137      */
138     public boolean addLexical(final int s) {
139         if (lexicalVariables == null) {
140             lexicalVariables = new LexicalScope();
141         }
142         return lexicalVariables.addSymbol(s);
143     }
144 
145     /**
146      * Checks whether a symbol is declared through a let or const.
147      * @param s the symbol
148      * @return true if symbol was declared through let or const
149      */
150     public boolean isLexical(final int s) {
151         return lexicalVariables != null && s >= 0 && lexicalVariables.hasSymbol(s);
152     }
153 
154     /**
155      * Checks whether a given symbol is captured.
156      * @param symbol the symbol number
157      * @return true if captured, false otherwise
158      */
159     public boolean isCapturedSymbol(final int symbol) {
160         return capturedVariables != null && capturedVariables.containsKey(symbol);
161     }
162 
163     /**
164      * Declares a parameter.
165      * <p>
166      * This method creates an new entry in the symbol map.
167      * </p>
168      * @param name the parameter name
169      * @return the register index storing this variable
170      */
171     public int declareParameter(final String name) {
172         if (namedVariables == null) {
173             namedVariables = new LinkedHashMap<>();
174         } else if (vars > 0) {
175             throw new IllegalStateException("cant declare parameters after variables");
176         }
177         Integer register = namedVariables.get(name);
178         if (register == null) {
179             register = namedVariables.size();
180             namedVariables.put(name, register);
181             parms += 1;
182         }
183         return register;
184     }
185 
186     /**
187      * Declares a local variable.
188      * <p>
189      * This method creates an new entry in the symbol map.
190      * </p>
191      * @param name the variable name
192      * @return the register index storing this variable
193      */
194     public int declareVariable(final String name) {
195         if (namedVariables == null) {
196             namedVariables = new LinkedHashMap<>();
197         }
198         Integer register = namedVariables.get(name);
199         if (register == null) {
200             register = namedVariables.size();
201             namedVariables.put(name, register);
202             vars += 1;
203             // check if local is redefining captured
204             if (parent != null) {
205                 final Integer pr = parent.getSymbol(name, true);
206                 if (pr != null) {
207                     if (capturedVariables == null) {
208                         capturedVariables = new LinkedHashMap<>();
209                     }
210                     capturedVariables.put(register, pr);
211                 }
212             }
213         }
214         return register;
215     }
216 
217     /**
218      * Creates a frame by copying values up to the number of parameters.
219      * <p>This captures the captured variables values.</p>
220      * @param frame the caller frame
221      * @param args the arguments
222      * @return the arguments array
223      */
224     public Frame createFrame(final Frame frame, final Object...args) {
225         if (namedVariables == null) {
226             return null;
227         }
228         final Object[] arguments = new Object[namedVariables.size()];
229         Arrays.fill(arguments, UNDECLARED);
230         if (frame != null && capturedVariables != null && parent != null) {
231             for (final Map.Entry<Integer, Integer> capture : capturedVariables.entrySet()) {
232                 final Integer target = capture.getKey();
233                 final Integer source = capture.getValue();
234                 final Object arg = frame.get(source);
235                 arguments[target] = arg;
236             }
237         }
238         return new Frame(this, arguments, 0).assign(args);
239     }
240 
241     /**
242      * Gets the captured index of a given symbol, ie the target index of a symbol in a child scope.
243      * @param symbol the symbol index
244      * @return the target symbol index or null if the symbol is not captured
245      */
246     public Integer getCaptured(final int symbol) {
247         if (capturedVariables != null) {
248             for (final Map.Entry<Integer, Integer> capture : capturedVariables.entrySet()) {
249                 final Integer source = capture.getValue();
250                 if (source == symbol) {
251                     return capture.getKey();
252                 }
253             }
254         }
255         return null;
256     }
257 
258     /**
259      * Gets the index of a captured symbol, ie the source index of a symbol in a parent scope.
260      * @param symbol the symbol index
261      * @return the source symbol index or -1 if the symbol is not captured
262      */
263     public int getCaptureDeclaration(final int symbol) {
264         Integer declared = capturedVariables != null? capturedVariables.get(symbol)  : null;
265         return declared != null? declared.intValue() : -1;
266     }
267 
268     /**
269      * Gets this script captured symbols names, i.e. local variables defined in outer scopes and used
270      * by this scope.
271      * @return the captured names
272      */
273     public String[] getCapturedVariables() {
274         if (capturedVariables != null) {
275             final List<String> captured = new ArrayList<>(vars);
276             for (final Map.Entry<String, Integer> entry : namedVariables.entrySet()) {
277                 final int symnum = entry.getValue();
278                 if (symnum >= parms && capturedVariables.containsKey(symnum)) {
279                     captured.add(entry.getKey());
280                 }
281             }
282             if (!captured.isEmpty()) {
283                 return captured.toArray(new String[0]);
284             }
285         }
286         return EMPTY_STRS;
287     }
288 
289     /**
290      * Gets the (maximum) number of arguments this script expects.
291      * @return the number of parameters
292      */
293     public int getArgCount() {
294         return parms;
295     }
296 
297     /**
298      * Gets this script symbols names, i.e. parameters and local variables.
299      * @return the symbol names
300      */
301     public String[] getSymbols() {
302         return namedVariables != null ? namedVariables.keySet().toArray(new String[0]) : EMPTY_STRS;
303     }
304 
305     /**
306      * Gets this script parameters, i.e. symbols assigned before creating local variables.
307      * @return the parameter names
308      */
309     public String[] getParameters() {
310         return getParameters(0);
311     }
312 
313     /**
314      * Gets this script parameters.
315      * @param bound number of known bound parameters (curry)
316      * @return the parameter names
317      */
318      String[] getParameters(final int bound) {
319         final int unbound = parms - bound;
320         if ((namedVariables == null) || (unbound <= 0)) {
321             return EMPTY_STRS;
322         }
323         final String[] pa = new String[unbound];
324         int p = 0;
325         for (final Map.Entry<String, Integer> entry : namedVariables.entrySet()) {
326             final int argn = entry.getValue();
327             if (argn >= bound && argn < parms) {
328                 pa[p++] = entry.getKey();
329             }
330         }
331         return pa;
332     }
333 
334     /**
335      * Gets this script local variable, i.e. symbols assigned to local variables excluding captured variables.
336      * @return the local variable names
337      */
338     public String[] getLocalVariables() {
339         if ((namedVariables == null) || (vars <= 0)) {
340             return EMPTY_STRS;
341         }
342         final List<String> locals = new ArrayList<>(vars);
343         for (final Map.Entry<String, Integer> entry : namedVariables.entrySet()) {
344             final int symnum = entry.getValue();
345             if (symnum >= parms && (capturedVariables == null || !capturedVariables.containsKey(symnum))) {
346                 locals.add(entry.getKey());
347             }
348         }
349         return locals.toArray(new String[0]);
350     }
351 
352 }