EMMA Coverage Report (generated Mon Nov 01 16:48:29 PDT 2010)
[all classes][com.google.caja.parser.quasiliteral]

COVERAGE SUMMARY FOR SOURCE FILE [Rule.java]

nameclass, %method, %block, %line, %
Rule.java100% (4/4)88%  (46/52)90%  (1007/1119)91%  (157.6/174)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class Rule100% (1/1)95%  (36/38)91%  (917/1005)92%  (143.2/155)
<static initializer> 100% (1/1)88%  (14/16)96%  (2.9/3)
Rule (): void 100% (1/1)100% (8/8)100% (3/3)
Rule (String, Rewriter): void 100% (1/1)76%  (13/17)92%  (4.6/5)
checkFormals (ParseTreeNode): void 100% (1/1)56%  (25/45)83%  (5/6)
comma (Expression, Expression): Expression 100% (1/1)100% (47/47)100% (9/9)
commas (Expression []): Expression 100% (1/1)100% (27/27)100% (6/6)
deconstructReadAssignOperand (Expression, Scope): Rule$ReadAssignOperands 100% (1/1)100% (6/6)100% (1/1)
deconstructReadAssignOperand (Expression, Scope, boolean): Rule$ReadAssignOpe... 100% (1/1)87%  (79/91)91%  (10/11)
expandAll (ParseTreeNode, Scope): ParseTreeNode 100% (1/1)100% (7/7)100% (1/1)
expandAllTo (ParseTreeNode, Class, Scope): ParseTreeNode 100% (1/1)100% (42/42)100% (7/7)
format (MessageContext, Appendable): void 100% (1/1)100% (15/15)100% (2/2)
getIdentifierName (ParseTreeNode): String 0%   (0/1)0%   (0/4)0%   (0/1)
getName (): String 100% (1/1)100% (3/3)100% (1/1)
getReferenceName (ParseTreeNode): String 100% (1/1)100% (4/4)100% (1/1)
getRewriter (): Rewriter 100% (1/1)100% (3/3)100% (1/1)
getRuleDescription (): RuleDescription 100% (1/1)66%  (31/47)55%  (6/11)
isImportsReference (Expression): boolean 100% (1/1)100% (11/11)100% (2/2)
isLocalReference (Expression, Scope): boolean 100% (1/1)100% (13/13)100% (1/1)
isSynthetic (FunctionConstructor): boolean 100% (1/1)100% (5/5)100% (1/1)
isSynthetic (Identifier): boolean 100% (1/1)100% (5/5)100% (1/1)
isSynthetic (Reference): boolean 100% (1/1)100% (4/4)100% (1/1)
makeBindings (): Map 100% (1/1)100% (2/2)100% (1/1)
match (ParseTreeNode): Map 100% (1/1)100% (13/13)100% (4/4)
newCommaOperation (List): Expression 100% (1/1)100% (7/7)100% (1/1)
newExprStmt (Expression): ExpressionStmt 100% (1/1)100% (7/7)100% (1/1)
newReference (FilePosition, String): Reference 100% (1/1)100% (10/10)100% (1/1)
nym (ParseTreeNode, String, String): String 100% (1/1)79%  (33/42)83%  (5/6)
nymize (ParseTreeNode, String, String): ParseTreeNode 100% (1/1)100% (52/52)100% (4/4)
reuse (ParseTreeNode, Scope): Pair 100% (1/1)100% (54/54)100% (6/6)
reuseAll (ParseTreeNode, Scope): Pair 100% (1/1)100% (47/47)100% (7/7)
setRewriter (Rewriter): void 100% (1/1)76%  (13/17)90%  (2.7/3)
sideEffectingReadAssignOperand (Expression, Expression, Scope): Rule$ReadAssi... 100% (1/1)100% (205/205)100% (24/24)
sideEffectlessReadAssignOperand (Expression, Scope): Rule$ReadAssignOperands 100% (1/1)100% (13/13)100% (1/1)
substV (Object []): ParseTreeNode 100% (1/1)100% (6/6)100% (1/1)
toString (): String 0%   (0/1)0%   (0/12)0%   (0/1)
toStringLiteral (ParseTreeNode): StringLiteral 100% (1/1)82%  (23/28)83%  (5/6)
transform (ParseTreeNode, Scope): ParseTreeNode 100% (1/1)100% (40/40)100% (7/7)
withoutNoops (ParseTreeNode): ParseTreeNode 100% (1/1)100% (30/30)100% (6/6)
     
class Rule$1100% (1/1)25%  (1/4)29%  (4/14)25%  (1/4)
Rule$1 (FilePosition): void 100% (1/1)100% (4/4)100% (1/1)
getValue (): Object 0%   (0/1)0%   (0/2)0%   (0/1)
makeRenderer (Appendable, Callback): TokenConsumer 0%   (0/1)0%   (0/4)0%   (0/1)
render (RenderContext): void 0%   (0/1)0%   (0/4)0%   (0/1)
     
class Rule$2100% (1/1)100% (1/1)89%  (17/19)89%  (0.9/1)
<static initializer> 100% (1/1)89%  (17/19)89%  (0.9/1)
     
class Rule$ReadAssignOperands100% (1/1)89%  (8/9)85%  (69/81)90%  (13.5/15)
<static initializer> 100% (1/1)75%  (6/8)75%  (0.8/1)
Rule$ReadAssignOperands (List, Expression, Expression): void 100% (1/1)81%  (17/21)94%  (5.6/6)
Rule$ReadAssignOperands (List, Expression, Expression, Rule$1): void 100% (1/1)100% (6/6)100% (1/1)
getCajoledLValue (): Expression 100% (1/1)100% (3/3)100% (1/1)
getTemporaries (): List 100% (1/1)100% (3/3)100% (1/1)
getTemporariesAsContainer (): ParseTreeNodeContainer 0%   (0/1)0%   (0/6)0%   (0/1)
getUncajoledLValue (): Expression 100% (1/1)100% (3/3)100% (1/1)
isSimpleLValue (): boolean 100% (1/1)100% (16/16)100% (1/1)
makeAssignment (Expression): Operation 100% (1/1)100% (15/15)100% (3/3)

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 
15package com.google.caja.parser.quasiliteral;
16 
17import com.google.caja.lexer.FilePosition;
18import com.google.caja.lexer.Keyword;
19import com.google.caja.lexer.TokenConsumer;
20import com.google.caja.parser.AbstractParseTreeNode;
21import com.google.caja.parser.MutableParseTreeNode;
22import com.google.caja.parser.ParseTreeNode;
23import com.google.caja.parser.ParseTreeNodeContainer;
24import com.google.caja.parser.ParseTreeNodes;
25import com.google.caja.parser.ParserBase;
26import com.google.caja.parser.js.Declaration;
27import com.google.caja.parser.js.Expression;
28import com.google.caja.parser.js.ExpressionStmt;
29import com.google.caja.parser.js.FormalParam;
30import com.google.caja.parser.js.FunctionConstructor;
31import com.google.caja.parser.js.Identifier;
32import com.google.caja.parser.js.Literal;
33import com.google.caja.parser.js.Noop;
34import com.google.caja.parser.js.Operation;
35import com.google.caja.parser.js.Operator;
36import com.google.caja.parser.js.Reference;
37import com.google.caja.parser.js.StringLiteral;
38import com.google.caja.parser.js.SyntheticNodes;
39import static com.google.caja.parser.js.SyntheticNodes.s;
40import com.google.caja.reporting.MessageContext;
41import com.google.caja.reporting.MessagePart;
42import com.google.caja.reporting.RenderContext;
43import com.google.caja.util.Callback;
44import com.google.caja.util.Lists;
45import com.google.caja.util.Maps;
46import com.google.caja.util.Pair;
47 
48import java.io.IOException;
49import java.lang.reflect.Method;
50import java.util.Collections;
51import java.util.List;
52import java.util.Map;
53 
54/**
55 * A rewriting rule supplied by a subclass.
56 */
57public 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}

[all classes][com.google.caja.parser.quasiliteral]
EMMA 2.0.5312 (C) Vladimir Roubtsov