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 empty string array.
49 */
50 private static final String[] EMPTY_STRS = {};
51 /**
52 * The parent scope.
53 */
54 private final Scope parent;
55 /**
56 * The number of parameters.
57 */
58 private int parms;
59 /**
60 * The number of local variables.
61 */
62 private int vars;
63 /**
64 * The map of named variables aka script parameters and local variables.
65 * Each parameter is associated to a symbol and is materialized as an offset in the stacked array used
66 * during evaluation.
67 */
68 private Map<String, Integer> namedVariables;
69 /**
70 * The map of local captured variables to parent scope variables, ie closure.
71 */
72 private Map<Integer, Integer> capturedVariables;
73 /**
74 * Let symbols.
75 */
76 private LexicalScope lexicalVariables;
77
78 /**
79 * Creates a new scope with a list of parameters.
80 * @param scope the parent scope if any
81 * @param parameters the list of parameters
82 */
83 public Scope(final Scope scope, final String... parameters) {
84 if (parameters != null) {
85 parms = parameters.length;
86 namedVariables = new LinkedHashMap<>();
87 for (int p = 0; p < parms; ++p) {
88 namedVariables.put(parameters[p], p);
89 }
90 } else {
91 parms = 0;
92 }
93 vars = 0;
94 parent = scope;
95 }
96
97 /**
98 * Marks a symbol as a lexical, declared through let or const.
99 * @param s the symbol
100 * @return true if the symbol was not already present in the lexical set
101 */
102 public boolean addLexical(final int s) {
103 if (lexicalVariables == null) {
104 lexicalVariables = new LexicalScope();
105 }
106 return lexicalVariables.addSymbol(s);
107 }
108
109 /**
110 * Creates a frame by copying values up to the number of parameters.
111 * <p>This captures the captured variables values.</p>
112 * @param frame the caller frame
113 * @param args the arguments
114 * @return the arguments array
115 */
116 public Frame createFrame(final boolean ref, final Frame frame, final Object...args) {
117 if (namedVariables == null) {
118 return null;
119 }
120 final Object[] arguments = new Object[namedVariables.size()];
121 Arrays.fill(arguments, UNDECLARED);
122 final Frame newFrame;
123 if (frame != null && capturedVariables != null && parent != null) {
124 for (final Map.Entry<Integer, Integer> capture : capturedVariables.entrySet()) {
125 final Integer target = capture.getKey();
126 final Integer source = capture.getValue();
127 final Object arg = frame.capture(source);
128 arguments[target] = arg;
129 }
130 newFrame = frame.newFrame(this, arguments, 0);
131 } else {
132 newFrame = ref
133 ? new ReferenceFrame(this, arguments, 0)
134 : new Frame(this, arguments, 0);
135 }
136 return newFrame.assign(args);
137 }
138
139 /**
140 * Declares a parameter.
141 * <p>
142 * This method creates an new entry in the symbol map.
143 * </p>
144 * @param param the parameter name
145 * @return the register index storing this variable
146 */
147 public int declareParameter(final String param) {
148 if (namedVariables == null) {
149 namedVariables = new LinkedHashMap<>();
150 } else if (vars > 0) {
151 throw new IllegalStateException("cant declare parameters after variables");
152 }
153 return namedVariables.computeIfAbsent(param, name -> {
154 final int register = namedVariables.size();
155 parms += 1;
156 return register;
157 });
158 }
159
160 /**
161 * Declares a local variable.
162 * <p>
163 * This method creates an new entry in the symbol map.
164 * </p>
165 * @param varName the variable name
166 * @return the register index storing this variable
167 */
168 public int declareVariable(final String varName) {
169 if (namedVariables == null) {
170 namedVariables = new LinkedHashMap<>();
171 }
172 return namedVariables.computeIfAbsent(varName, name -> {
173 final int register = namedVariables.size();
174 vars += 1;
175 // check if local is redefining captured
176 if (parent != null) {
177 final Integer pr = parent.getSymbol(name, true);
178 if (pr != null) {
179 if (capturedVariables == null) {
180 capturedVariables = new LinkedHashMap<>();
181 }
182 capturedVariables.put(register, pr);
183 }
184 }
185 return register;
186 });
187 }
188
189 /**
190 * Gets the (maximum) number of arguments this script expects.
191 * @return the number of parameters
192 */
193 public int getArgCount() {
194 return parms;
195 }
196
197 /**
198 * Gets the captured index of a given symbol, ie the target index of a symbol in a child scope.
199 * @param symbol the symbol index
200 * @return the target symbol index or null if the symbol is not captured
201 */
202 public Integer getCaptured(final int symbol) {
203 if (capturedVariables != null) {
204 for (final Map.Entry<Integer, Integer> capture : capturedVariables.entrySet()) {
205 final Integer source = capture.getValue();
206 if (source == symbol) {
207 return capture.getKey();
208 }
209 }
210 }
211 return null;
212 }
213
214 /**
215 * Gets the index of a captured symbol, ie the source index of a symbol in a parent scope.
216 * @param symbol the symbol index
217 * @return the source symbol index or -1 if the symbol is not captured
218 */
219 public int getCaptureDeclaration(final int symbol) {
220 final Integer declared = capturedVariables != null ? capturedVariables.get(symbol) : null;
221 return declared != null ? declared : -1;
222 }
223
224 /**
225 * Gets this script captured symbols names, i.e. local variables defined in outer scopes and used
226 * by this scope.
227 * @return the captured names
228 */
229 public String[] getCapturedVariables() {
230 if (capturedVariables != null) {
231 final List<String> captured = new ArrayList<>(vars);
232 for (final Map.Entry<String, Integer> entry : namedVariables.entrySet()) {
233 final int symnum = entry.getValue();
234 if (symnum >= parms && capturedVariables.containsKey(symnum)) {
235 captured.add(entry.getKey());
236 }
237 }
238 if (!captured.isEmpty()) {
239 return captured.toArray(new String[0]);
240 }
241 }
242 return EMPTY_STRS;
243 }
244
245 /**
246 * Gets this script local variable, i.e. symbols assigned to local variables excluding captured variables.
247 * @return the local variable names
248 */
249 public String[] getLocalVariables() {
250 if (namedVariables == null || vars <= 0) {
251 return EMPTY_STRS;
252 }
253 final List<String> locals = new ArrayList<>(vars);
254 for (final Map.Entry<String, Integer> entry : namedVariables.entrySet()) {
255 final int symnum = entry.getValue();
256 if (symnum >= parms && (capturedVariables == null || !capturedVariables.containsKey(symnum))) {
257 locals.add(entry.getKey());
258 }
259 }
260 return locals.toArray(new String[0]);
261 }
262
263 /**
264 * Gets this script parameters, i.e. symbols assigned before creating local variables.
265 * @return the parameter names
266 */
267 public String[] getParameters() {
268 return getParameters(0);
269 }
270
271 /**
272 * Gets this script parameters.
273 * @param bound number of known bound parameters (curry)
274 * @return the parameter names
275 */
276 String[] getParameters(final int bound) {
277 final int unbound = parms - bound;
278 if (namedVariables == null || unbound <= 0) {
279 return EMPTY_STRS;
280 }
281 final String[] pa = new String[unbound];
282 int p = 0;
283 for (final Map.Entry<String, Integer> entry : namedVariables.entrySet()) {
284 final int argn = entry.getValue();
285 if (argn >= bound && argn < parms) {
286 pa[p++] = entry.getKey();
287 }
288 }
289 return pa;
290 }
291
292 Scope getParent() {
293 return parent;
294 }
295
296 /**
297 * Checks whether an identifier is a local variable or argument, ie a symbol.
298 * If this fails, look in parents for symbol that can be captured.
299 * @param name the symbol name
300 * @return the symbol index
301 */
302 public Integer getSymbol(final String name) {
303 return getSymbol(name, true);
304 }
305
306 /**
307 * Checks whether an identifier is a local variable or argument, ie a symbol.
308 * @param name the symbol name
309 * @param capture whether solving by capturing a parent symbol is allowed
310 * @return the symbol index
311 */
312 private Integer getSymbol(final String name, final boolean capture) {
313 Integer register = namedVariables != null ? namedVariables.get(name) : null;
314 if (register == null && capture && parent != null) {
315 final Integer pr = parent.getSymbol(name, true);
316 if (pr != null) {
317 if (capturedVariables == null) {
318 capturedVariables = new LinkedHashMap<>();
319 }
320 if (namedVariables == null) {
321 namedVariables = new LinkedHashMap<>();
322 }
323 register = namedVariables.size();
324 namedVariables.put(name, register);
325 capturedVariables.put(register, pr);
326 }
327 }
328 return register;
329 }
330
331 /**
332 * Gets this script symbols names, i.e. parameters and local variables.
333 * @return the symbol names
334 */
335 public String[] getSymbols() {
336 return namedVariables != null ? namedVariables.keySet().toArray(new String[0]) : EMPTY_STRS;
337 }
338
339 /**
340 * Checks whether a given symbol is captured.
341 * @param symbol the symbol number
342 * @return true if captured, false otherwise
343 */
344 public boolean isCapturedSymbol(final int symbol) {
345 return capturedVariables != null && capturedVariables.containsKey(symbol);
346 }
347
348 /**
349 * Checks whether a symbol is declared through a let or const.
350 * @param s the symbol
351 * @return true if symbol was declared through let or const
352 */
353 public boolean isLexical(final int s) {
354 return lexicalVariables != null && s >= 0 && lexicalVariables.hasSymbol(s);
355 }
356 }