1 | // Copyright (C) 2007 Google Inc. |
2 | // |
3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
4 | // you may not use this file except in compliance with the License. |
5 | // You may obtain a copy of the License at |
6 | // |
7 | // http://www.apache.org/licenses/LICENSE-2.0 |
8 | // |
9 | // Unless required by applicable law or agreed to in writing, software |
10 | // distributed under the License is distributed on an "AS IS" BASIS, |
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | // See the License for the specific language governing permissions and |
13 | // limitations under the License. |
14 | |
15 | package com.google.caja.parser.quasiliteral; |
16 | |
17 | import com.google.caja.lexer.FilePosition; |
18 | import com.google.caja.lexer.Keyword; |
19 | import com.google.caja.lexer.TokenConsumer; |
20 | import com.google.caja.parser.AbstractParseTreeNode; |
21 | import com.google.caja.parser.MutableParseTreeNode; |
22 | import com.google.caja.parser.ParseTreeNode; |
23 | import com.google.caja.parser.ParseTreeNodeContainer; |
24 | import com.google.caja.parser.ParseTreeNodes; |
25 | import com.google.caja.parser.ParserBase; |
26 | import com.google.caja.parser.js.Declaration; |
27 | import com.google.caja.parser.js.Expression; |
28 | import com.google.caja.parser.js.ExpressionStmt; |
29 | import com.google.caja.parser.js.FormalParam; |
30 | import com.google.caja.parser.js.FunctionConstructor; |
31 | import com.google.caja.parser.js.Identifier; |
32 | import com.google.caja.parser.js.Literal; |
33 | import com.google.caja.parser.js.Noop; |
34 | import com.google.caja.parser.js.Operation; |
35 | import com.google.caja.parser.js.Operator; |
36 | import com.google.caja.parser.js.Reference; |
37 | import com.google.caja.parser.js.StringLiteral; |
38 | import com.google.caja.parser.js.SyntheticNodes; |
39 | import static com.google.caja.parser.js.SyntheticNodes.s; |
40 | import com.google.caja.reporting.MessageContext; |
41 | import com.google.caja.reporting.MessagePart; |
42 | import com.google.caja.reporting.RenderContext; |
43 | import com.google.caja.util.Callback; |
44 | import com.google.caja.util.Lists; |
45 | import com.google.caja.util.Maps; |
46 | import com.google.caja.util.Pair; |
47 | |
48 | import java.io.IOException; |
49 | import java.lang.reflect.Method; |
50 | import java.util.Collections; |
51 | import java.util.List; |
52 | import java.util.Map; |
53 | |
54 | /** |
55 | * A rewriting rule supplied by a subclass. |
56 | */ |
57 | public abstract class Rule implements MessagePart { |
58 | |
59 | /** |
60 | * The special return value from a rule that indicates the rule |
61 | * does not apply to the supplied input. |
62 | */ |
63 | public static final ParseTreeNode NONE = |
64 | new AbstractParseTreeNode(FilePosition.UNKNOWN) { |
65 | @Override public Object getValue() { return null; } |
66 | public void render(RenderContext r) { |
67 | throw new UnsupportedOperationException(); |
68 | } |
69 | public TokenConsumer makeRenderer( |
70 | Appendable out, Callback<IOException> exHandler) { |
71 | throw new UnsupportedOperationException(); |
72 | } |
73 | }; |
74 | |
75 | private final String name; |
76 | private Rewriter rewriter; |
77 | private RuleDescription description; |
78 | |
79 | /** |
80 | * Creates a new Rule, inferring name and other state from the {@link #fire} |
81 | * method's {@link RuleDescription}. |
82 | */ |
83 | public Rule() { |
84 | this.name = getRuleDescription().name(); |
85 | } |
86 | |
87 | /** |
88 | * Create a new {@code Rule}. |
89 | * |
90 | * @param name the unique name of this rule. |
91 | */ |
92 | public Rule(String name, Rewriter rewriter) { |
93 | assert name != null; |
94 | this.name = name; |
95 | this.rewriter = rewriter; |
96 | } |
97 | |
98 | /** |
99 | * @return the name of this {@code Rule}. |
100 | */ |
101 | public String getName() { |
102 | return name; |
103 | } |
104 | |
105 | /** |
106 | * @return the rewriter this {@code Rule} uses. |
107 | */ |
108 | public Rewriter getRewriter() { return rewriter; } |
109 | |
110 | /** |
111 | * Set the rewriter this {@code Rule} uses. |
112 | */ |
113 | public void setRewriter(Rewriter rewriter) { |
114 | assert this.rewriter == null || this.rewriter == rewriter; |
115 | this.rewriter = rewriter; |
116 | } |
117 | |
118 | /** |
119 | * Gets the {@link RuleDescription} annotation on the {@link #fire} method. |
120 | * @throws IllegalStateException if there is no such annotation. |
121 | */ |
122 | public RuleDescription getRuleDescription() { |
123 | if (description == null) { |
124 | Method fire; |
125 | try { |
126 | fire = getClass().getMethod("fire", new Class<?>[] { |
127 | ParseTreeNode.class, Scope.class |
128 | }); |
129 | } catch (NoSuchMethodException e) { |
130 | NoSuchMethodError error = new NoSuchMethodError(); |
131 | error.initCause(e); |
132 | throw error; |
133 | } |
134 | description = fire.getAnnotation(RuleDescription.class); |
135 | if (description == null) { |
136 | throw new IllegalStateException("RuleDescription not found"); |
137 | } |
138 | } |
139 | return description; |
140 | } |
141 | |
142 | /** |
143 | * Process the given input, returning a rewritten node. |
144 | * |
145 | * @param node an input node. |
146 | * @param scope the current scope. |
147 | * @return the rewritten node, or {@link #NONE} to indicate |
148 | * that this rule does not apply to the given input. |
149 | */ |
150 | public abstract ParseTreeNode fire(ParseTreeNode node, Scope scope); |
151 | |
152 | /** |
153 | * @see MessagePart#format(MessageContext,Appendable) |
154 | */ |
155 | public void format(MessageContext mc, Appendable out) throws IOException { |
156 | out.append("Rule \"" + name + "\""); |
157 | } |
158 | |
159 | protected final ParseTreeNode expandAll(ParseTreeNode node, Scope scope) { |
160 | return expandAllTo(node, node.getClass(), scope); |
161 | } |
162 | |
163 | protected final ParseTreeNode expandAllTo( |
164 | ParseTreeNode node, |
165 | Class<? extends ParseTreeNode> parentNodeClass, |
166 | Scope scope) { |
167 | List<ParseTreeNode> rewrittenChildren = Lists.newArrayList(); |
168 | for (ParseTreeNode child : node.children()) { |
169 | rewrittenChildren.add(rewriter.expand(child, scope)); |
170 | } |
171 | |
172 | ParseTreeNode result = ParseTreeNodes.newNodeInstance( |
173 | parentNodeClass, |
174 | node.getFilePosition(), |
175 | node.getValue(), |
176 | rewrittenChildren); |
177 | result.getAttributes().putAll(node.getAttributes()); |
178 | result.getAttributes().remove(ParseTreeNode.TAINTED); |
179 | |
180 | return result; |
181 | } |
182 | |
183 | static final ParseTreeNode withoutNoops(ParseTreeNode n) { |
184 | if (n instanceof ParseTreeNodeContainer) { |
185 | MutableParseTreeNode.Mutation mut = ((ParseTreeNodeContainer) n) |
186 | .createMutation(); |
187 | for (ParseTreeNode child : n.children()) { |
188 | if (child instanceof Noop) { mut.removeChild(child); } |
189 | } |
190 | mut.execute(); |
191 | } |
192 | return n; |
193 | } |
194 | |
195 | public static Reference newReference(FilePosition pos, String name) { |
196 | return new Reference(s(new Identifier(pos, name))); |
197 | } |
198 | |
199 | protected static ExpressionStmt newExprStmt(Expression e) { |
200 | return new ExpressionStmt(e.getFilePosition(), e); |
201 | } |
202 | |
203 | /** |
204 | * Given two expressions in comma normal form (defined below), this returns |
205 | * an expression equivalent to <tt>left,right</tt> that is also in comma |
206 | * normal form. |
207 | * <p> |
208 | * An expression is in <i>comma normal form</i> if<ul> |
209 | * <li>It is not a comma expression or |
210 | * <li>It is a comma expression, but its right operand is not, and<ul> |
211 | * <li>its left operand is in comma normal form, and |
212 | * <li>its left operand is not <tt>void 0</tt>, and |
213 | * <li>if its left operand is a comma expression, its left |
214 | * operand's right operand is not <tt>void 0</tt>. |
215 | * </ul> |
216 | * </ul> |
217 | */ |
218 | private Expression comma(Expression left, Expression right) { |
219 | Map<String, ParseTreeNode> leftBindings = makeBindings(); |
220 | Map<String, ParseTreeNode> rightBindings = makeBindings(); |
221 | if (QuasiBuilder.match("void 0", left)) { |
222 | return right; |
223 | } else if (QuasiBuilder.match("@leftLeft, void 0", left, leftBindings)) { |
224 | return comma((Expression) leftBindings.get("leftLeft"), right); |
225 | } else if (QuasiBuilder.match("@rightLeft, @rightRight", right, rightBindings)) { |
226 | return comma(comma(left, (Expression) rightBindings.get("rightLeft")), |
227 | (Expression) rightBindings.get("rightRight")); |
228 | } else { |
229 | return Operation.createInfix(Operator.COMMA, left, right); |
230 | } |
231 | } |
232 | |
233 | protected Expression commas(Expression... operands) { |
234 | if (operands.length == 0) { |
235 | return Operation.undefined(FilePosition.UNKNOWN); |
236 | } |
237 | Expression result = operands[0]; |
238 | for (int i = 1; i < operands.length; i++) { |
239 | result = comma(result, operands[i]); |
240 | } |
241 | return result; |
242 | } |
243 | |
244 | static private final Expression[] NO_EXPRS = {}; |
245 | |
246 | protected Expression newCommaOperation(List<? extends ParseTreeNode> operands) { |
247 | return commas(operands.toArray(NO_EXPRS)); |
248 | } |
249 | |
250 | /** |
251 | * Returns a pair of a reusable expression and an initializing expression that |
252 | * together represent the reusable expansion of the <tt>value</tt> expression. |
253 | * <p> |
254 | * In the expansion context, the initializing expression must be executed |
255 | * exactly once and prior to evaluating the reusable expression. |
256 | */ |
257 | protected Pair<Expression, Expression> reuse( |
258 | ParseTreeNode value, Scope scope) { |
259 | Expression rhs = (Expression) rewriter.expand(value, scope); |
260 | if (rhs instanceof Reference || rhs instanceof Literal) { |
261 | return new Pair<Expression, Expression>( |
262 | rhs, Operation.undefined(FilePosition.UNKNOWN)); |
263 | } |
264 | Expression tempRef = new Reference( |
265 | scope.declareStartOfScopeTempVariable()); |
266 | Expression tempInit = (Expression) QuasiBuilder.substV( |
267 | "@ref = @rhs;", |
268 | "ref", tempRef, |
269 | "rhs", rhs); |
270 | return new Pair<Expression, Expression>(tempRef, tempInit); |
271 | } |
272 | |
273 | /** |
274 | * Returns a pair of a reusable expression list and an initializing |
275 | * expression that together represent the reusable expansion of the |
276 | * <tt>arguments</tt> expression list. |
277 | * <p> |
278 | * In the expansion context, the initializing expression must be executed |
279 | * exactly once and prior to evaluating the reusable expression. |
280 | */ |
281 | protected Pair<ParseTreeNodeContainer, Expression> reuseAll( |
282 | ParseTreeNode arguments, Scope scope) { |
283 | List<ParseTreeNode> refs = Lists.newArrayList(); |
284 | Expression[] inits = new Expression[arguments.children().size()]; |
285 | |
286 | for (int i = 0; i < arguments.children().size(); i++) { |
287 | Pair<Expression, Expression> p = reuse( |
288 | arguments.children().get(i), scope); |
289 | refs.add(p.a); |
290 | inits[i] = p.b; |
291 | } |
292 | |
293 | return new Pair<ParseTreeNodeContainer, Expression>( |
294 | new ParseTreeNodeContainer(refs), |
295 | commas(inits)); |
296 | } |
297 | |
298 | /** |
299 | * Return a name suitable for naming a function derived from <tt>node</tt>, where |
300 | * the name is derived from baseName and optionally ext, is distinct from baseName, |
301 | * and is probably not used within <tt>node</tt>. |
302 | * <p> |
303 | * We operate under the (currently unchecked) assumption |
304 | * that node contains no variables whose names contain a "$_". |
305 | */ |
306 | protected String nym(ParseTreeNode node, String baseName, String ext) { |
307 | String result; |
308 | if (node != null && baseName.indexOf("$_") != -1) { |
309 | result = baseName + "$"; |
310 | } else { |
311 | result = baseName + "$_" + ext; |
312 | } |
313 | if (!ParserBase.isJavascriptIdentifier(result)) { |
314 | result = "badName$_" + ext; |
315 | } |
316 | // TODO: If we ever have a cheap way to test whether result is used freely |
317 | // in node <i>including any nested functions</i>, then we should modify result |
318 | // so that it does not collide. |
319 | return result; |
320 | } |
321 | |
322 | /** |
323 | * Returns a ParseTreeNode with the same meaning as node, but potentially better |
324 | * debugging info. |
325 | * <p> |
326 | * If node defines an anonymous function expression, then return a new named |
327 | * function expression, where the name is derived from baseName. |
328 | * For all other nodes, currently returns the node itself. |
329 | */ |
330 | protected ParseTreeNode nymize(ParseTreeNode node, String baseName, String ext) { |
331 | Map<String, ParseTreeNode> bindings = makeBindings(); |
332 | if (QuasiBuilder.match("function (@ps*) {@bs*;}", node, bindings)) { |
333 | return QuasiBuilder.substV( |
334 | "function @fname(@ps*) {@bs*;}", |
335 | "fname", new Identifier( |
336 | FilePosition.startOf(node.getFilePosition()), |
337 | nym(node, baseName, ext)), |
338 | "ps", bindings.get("ps"), |
339 | "bs", bindings.get("bs")); |
340 | } |
341 | return node; |
342 | } |
343 | |
344 | protected void checkFormals(ParseTreeNode formals) { |
345 | for (ParseTreeNode formal : formals.children()) { |
346 | FormalParam f = (FormalParam) formal; |
347 | if (!isSynthetic(f.getIdentifier()) |
348 | && f.getIdentifierName().endsWith("__")) { |
349 | rewriter.mq.addMessage( |
350 | RewriterMessageType.VARIABLES_CANNOT_END_IN_DOUBLE_UNDERSCORE, |
351 | f.getFilePosition(), this, f); |
352 | } |
353 | } |
354 | } |
355 | |
356 | protected static boolean isSynthetic(Identifier node) { |
357 | return node.getAttributes().is(SyntheticNodes.SYNTHETIC); |
358 | } |
359 | |
360 | protected static boolean isSynthetic(Reference node) { |
361 | return isSynthetic(node.getIdentifier()); |
362 | } |
363 | |
364 | protected static boolean isSynthetic(FunctionConstructor node) { |
365 | return node.getAttributes().is(SyntheticNodes.SYNTHETIC); |
366 | } |
367 | |
368 | protected static String getReferenceName(ParseTreeNode ref) { |
369 | return ((Reference)ref).getIdentifierName(); |
370 | } |
371 | |
372 | protected static String getIdentifierName(ParseTreeNode id) { |
373 | return ((Identifier)id).getValue(); |
374 | } |
375 | |
376 | /** |
377 | * Produce a StringLiteral from node's identifier. |
378 | * @param node an identifier or node that contains a single identifier like |
379 | * a declaration or reference. |
380 | * @return a string literal whose unescaped content is identical to the |
381 | * identifier's value, and whose file position is that of node. |
382 | */ |
383 | protected static final StringLiteral toStringLiteral(ParseTreeNode node) { |
384 | Identifier ident; |
385 | if (node instanceof Reference) { |
386 | ident = ((Reference) node).getIdentifier(); |
387 | } else if (node instanceof Declaration) { |
388 | ident = ((Declaration) node).getIdentifier(); |
389 | } else { |
390 | ident = (Identifier) node; |
391 | } |
392 | return new StringLiteral( |
393 | ident.getFilePosition(), StringLiteral.toQuotedValue(ident.getName())); |
394 | } |
395 | |
396 | /** |
397 | * Matches using the Quasi-pattern from {@link RuleDescription#matches} and |
398 | * returns the bindings if the match succeeded, or null otherwise. |
399 | * @return null iff node was not matched. |
400 | */ |
401 | protected Map<String, ParseTreeNode> match(ParseTreeNode node) { |
402 | Map<String, ParseTreeNode> bindings = makeBindings(); |
403 | if (QuasiBuilder.match(getRuleDescription().matches(), node, bindings)) { |
404 | return bindings; |
405 | } |
406 | return null; |
407 | } |
408 | |
409 | protected static Map<String, ParseTreeNode> makeBindings() { |
410 | return Maps.newLinkedHashMap(); |
411 | } |
412 | |
413 | /** |
414 | * For when you just want to match(), expand() all bindings, and subst() using |
415 | * the rule's matches and substitutes annotations. |
416 | */ |
417 | protected ParseTreeNode transform(ParseTreeNode node, Scope scope) { |
418 | Map<String, ParseTreeNode> bindings = match(node); |
419 | if (bindings != null) { |
420 | Map<String, ParseTreeNode> newBindings = makeBindings(); |
421 | for (Map.Entry<String, ParseTreeNode> entry : bindings.entrySet()) { |
422 | newBindings.put(entry.getKey(), |
423 | getRewriter().expand(entry.getValue(), scope)); |
424 | } |
425 | return QuasiBuilder.subst(getRuleDescription().substitutes(), |
426 | newBindings); |
427 | } |
428 | return NONE; |
429 | } |
430 | |
431 | /** |
432 | * Substitutes bindings into the Quasi-pattern from |
433 | * {@link RuleDescription#substitutes}. |
434 | * @param args quasi hole names and ParseTreeNodes per QuasiBuilder.substV. |
435 | */ |
436 | protected ParseTreeNode substV(Object... args) { |
437 | return QuasiBuilder.substV(getRuleDescription().substitutes(), args); |
438 | } |
439 | |
440 | /** |
441 | * Split the target of a read/set operation into an LHS, an RHS, and |
442 | * an ordered list of temporary variables needed to ensure proper order |
443 | * of execution. |
444 | * @param operand uncajoled expression that can be used as both an LHS and |
445 | * an RHS. |
446 | * @return null if operand is not a valid LHS, or its subexpressions do |
447 | * not cajole. |
448 | */ |
449 | ReadAssignOperands deconstructReadAssignOperand( |
450 | Expression operand, Scope scope) { |
451 | return deconstructReadAssignOperand(operand, scope, true); |
452 | } |
453 | |
454 | ReadAssignOperands deconstructReadAssignOperand( |
455 | Expression operand, Scope scope, boolean checkImported) { |
456 | if (operand instanceof Reference) { |
457 | // TODO(erights): These rules should be independent of whether we're writing |
458 | // new-caja or cajita. The check for whether it's imported only applies in the |
459 | // cajita case. |
460 | if (checkImported && scope.isImported(((Reference) operand).getIdentifierName())) { |
461 | rewriter.mq.addMessage( |
462 | RewriterMessageType.CANNOT_ASSIGN_TO_FREE_VARIABLE, |
463 | operand.getFilePosition(), this, operand); |
464 | return null; |
465 | } |
466 | return sideEffectlessReadAssignOperand(operand, scope); |
467 | } else if (operand instanceof Operation) { |
468 | Operation op = (Operation) operand; |
469 | switch (op.getOperator()) { |
470 | case SQUARE_BRACKET: |
471 | return sideEffectingReadAssignOperand( |
472 | op.children().get(0), op.children().get(1), scope); |
473 | case MEMBER_ACCESS: |
474 | return sideEffectingReadAssignOperand( |
475 | op.children().get(0), toStringLiteral(op.children().get(1)), |
476 | scope); |
477 | default: break; |
478 | } |
479 | } |
480 | throw new IllegalArgumentException("Not an lvalue : " + operand); |
481 | } |
482 | |
483 | /** |
484 | * Given a LHS that has no side effect when evaluated as an LHS, produce |
485 | * a ReadAssignOperands without using temporaries. |
486 | */ |
487 | private ReadAssignOperands sideEffectlessReadAssignOperand( |
488 | Expression lhs, Scope scope) { |
489 | return new ReadAssignOperands( |
490 | Collections.<Expression>emptyList(), |
491 | lhs, (Expression) rewriter.expand(lhs, scope)); |
492 | } |
493 | |
494 | private ReadAssignOperands sideEffectingReadAssignOperand( |
495 | Expression uncajoledObject, Expression uncajoledKey, Scope scope) { |
496 | Reference object; // The object that contains the field to assign. |
497 | Expression key; // Identifies the field to assign. |
498 | List<Expression> temporaries = Lists.newArrayList(); |
499 | |
500 | // Don't cajole the operands. We return a simple assignment operator that |
501 | // can then itself be cajoled, so that a rewriter can use context to treat |
502 | // the LHS differently from the RHS. |
503 | |
504 | // a[b] += 2 |
505 | // => |
506 | // var x___ = a; |
507 | // var x0___ = b; |
508 | |
509 | // If the right is simple then we can assume it does not modify the |
510 | // left, but otherwise the left has to be put into a temporary so that |
511 | // it's evaluated before the right can muck with it. |
512 | boolean isKeySimple = (uncajoledKey instanceof Literal |
513 | || isLocalReference(uncajoledKey, scope)); |
514 | |
515 | // If the left is simple and the right does not need a temporary variable |
516 | // then don't introduce one. |
517 | if (isKeySimple && (isLocalReference(uncajoledObject, scope) |
518 | || isImportsReference(uncajoledObject))) { |
519 | object = (Reference) uncajoledObject; |
520 | } else { |
521 | Identifier tmpVar = scope.declareStartOfScopeTempVariable(); |
522 | temporaries.add((Expression) QuasiBuilder.substV( |
523 | "@tmpVar = @left;", |
524 | "tmpVar", new Reference(tmpVar), |
525 | "left", rewriter.expand(uncajoledObject, scope))); |
526 | object = new Reference(tmpVar); |
527 | } |
528 | |
529 | // Don't bother to generate a temporary for a simple value like 'foo' |
530 | if (isKeySimple) { |
531 | key = uncajoledKey; |
532 | } else { |
533 | ParseTreeNode rightExpanded = rewriter.expand(uncajoledKey, scope); |
534 | Identifier tmpVar = scope.declareStartOfScopeTempVariable(); |
535 | key = new Reference(tmpVar); |
536 | if (QuasiBuilder.match("@s&(-1>>>1)", rightExpanded)) { |
537 | // TODO(metaweta): Figure out a way to leave key alone and |
538 | // protect propertyAccess from rewriting instead. |
539 | key = (Expression) QuasiBuilder.substV("@key&(-1>>>1)", "key", key); |
540 | } |
541 | temporaries.add((Expression) QuasiBuilder.substV( |
542 | "@tmpVar = @right;", |
543 | "tmpVar", new Reference(tmpVar), |
544 | "right", rightExpanded)); |
545 | } |
546 | |
547 | Operation propertyAccess = null; |
548 | if (key instanceof StringLiteral) { |
549 | // Make sure that cases like |
550 | // arr.length -= 1 |
551 | // optimize arr.length in the right-hand-side usage. |
552 | // See the array length case in testSetReadModifyWriteLocalVar. |
553 | String keyText = ((StringLiteral) key).getUnquotedValue(); |
554 | if (ParserBase.isJavascriptIdentifier(keyText) |
555 | && Keyword.fromString(keyText) == null) { |
556 | Reference ident = new Reference( |
557 | new Identifier(key.getFilePosition(), keyText)); |
558 | propertyAccess = Operation.create( |
559 | FilePosition.span(object.getFilePosition(), key.getFilePosition()), |
560 | Operator.MEMBER_ACCESS, object, ident); |
561 | } |
562 | } |
563 | if (propertyAccess == null) { |
564 | propertyAccess = Operation.create( |
565 | FilePosition.span(object.getFilePosition(), key.getFilePosition()), |
566 | Operator.SQUARE_BRACKET, object, key); |
567 | } |
568 | return new ReadAssignOperands( |
569 | temporaries, propertyAccess, |
570 | (Expression) rewriter.expand(propertyAccess, scope)); |
571 | } |
572 | |
573 | /** |
574 | * True iff e is a reference to a local in scope. |
575 | * We distinguish local references in many places because members of |
576 | * {@code IMPORTS___} might be backed by getters/setters, and so |
577 | * must be evaluated exactly once as an lvalue. |
578 | */ |
579 | private static boolean isLocalReference(Expression e, Scope scope) { |
580 | return e instanceof Reference |
581 | && !scope.isImported(((Reference) e).getIdentifierName()); |
582 | } |
583 | |
584 | /** True iff e is a reference to the global object. */ |
585 | private static boolean isImportsReference(Expression e) { |
586 | if (!(e instanceof Reference)) { return false; } |
587 | return ReservedNames.IMPORTS.equals(((Reference) e).getIdentifierName()); |
588 | } |
589 | |
590 | /** |
591 | * The operands in a read/assign operation. |
592 | * <p> |
593 | * When we need to express a single read/assign operation such as {@code *=} |
594 | * or {@code ++} as an operation that separates out the getting from the |
595 | * setting. |
596 | * <p> |
597 | * This encapsulates any temporary variables created to prevent multiple |
598 | * execution, and the cajoled LHS and RHS. |
599 | */ |
600 | protected static final class ReadAssignOperands { |
601 | private final List<Expression> temporaries; |
602 | private final Expression uncajoled, cajoled; |
603 | |
604 | private ReadAssignOperands( |
605 | List<Expression> temporaries, Expression lhs, Expression rhs) { |
606 | assert lhs.isLeftHandSide(); |
607 | this.temporaries = temporaries; |
608 | this.uncajoled = lhs; |
609 | this.cajoled = rhs; |
610 | } |
611 | |
612 | /** |
613 | * The temporaries required by LHS and RHS in order of initialization. |
614 | */ |
615 | public List<Expression> getTemporaries() { return temporaries; } |
616 | public ParseTreeNodeContainer getTemporariesAsContainer() { |
617 | return new ParseTreeNodeContainer(temporaries); |
618 | } |
619 | /** The uncajoled LHS. */ |
620 | public Expression getUncajoledLValue() { return uncajoled; } |
621 | /** The cajoled left hand side expression usable as an rvalue. */ |
622 | public Expression getCajoledLValue() { return cajoled; } |
623 | /** |
624 | * Can the assignment be performed using the RHS as an LHS without |
625 | * the need for temporaries? |
626 | */ |
627 | public boolean isSimpleLValue() { |
628 | return temporaries.isEmpty() && cajoled.isLeftHandSide() |
629 | && cajoled instanceof Reference; |
630 | } |
631 | |
632 | public Operation makeAssignment(Expression rhs) { |
633 | Operation e = Operation.createInfix(Operator.ASSIGN, this.uncajoled, rhs); |
634 | e.getAttributes().set(ParseTreeNode.TAINTED, true); |
635 | return e; |
636 | } |
637 | } |
638 | |
639 | @Override |
640 | public String toString() { |
641 | return "<Rule " + getName() + ">"; |
642 | } |
643 | } |