Package com.google.caja.plugin.templates

Source Code of com.google.caja.plugin.templates.SideEffectPart

// Copyright (C) 2009 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.caja.plugin.templates;

import com.google.caja.lexer.FilePosition;
import com.google.caja.parser.js.ArrayConstructor;
import com.google.caja.parser.js.Expression;
import com.google.caja.parser.js.FunctionConstructor;
import com.google.caja.parser.js.Literal;
import com.google.caja.parser.js.NumberLiteral;
import com.google.caja.parser.js.ObjectConstructor;
import com.google.caja.parser.js.Operation;
import com.google.caja.parser.js.Operator;
import com.google.caja.parser.js.StringLiteral;
import com.google.caja.render.JsMinimalPrinter;
import com.google.caja.reporting.RenderContext;

import java.util.ArrayList;
import java.util.List;

/**
* Given a series of javascript expressions, produces an expression that is the
* result of appending the result of coercing each to a string.
*
* @author mikesamuel@gmail.com
*/
public final class JsConcatenator {
  private final List<Part> parts = new ArrayList<Part>();

  public void append(FilePosition pos, String s) {
    append(StringLiteral.valueOf(pos, s));
  }

  public void append(Expression e) {
    StringLiteral sl = Emitter.asStringLiteral(e);
    if (sl != null && sl.getUnquotedValue().length() == 0) { return; }
    if (Emitter.is(e, Operator.ADDITION)) {
      List<? extends Expression> operands = ((Operation) e).children();
      if (Emitter.isStringy(operands.get(0), false)
          || Emitter.isStringy(operands.get(1), false)) {
        append(operands.get(0));
        append(operands.get(1));
        return;
      }
    }
    parts.add(new StringPart(sl != null ? sl : e));
  }

  public void forSideEffect(Expression e) {
    e = e.simplifyForSideEffect();
    if (e != null) {
      parts.add(new SideEffectPart(e, true));
    }
  }

  /**
   * Returns an expression describing the result of concatenating the result of
   * each expression passed to {@link #append}.
   *
   * @param mustBeString true if the typeof result must be {@code string}.
   *      Otherwise, we guarantee that the result is an expression whose result,
   *      when coerced to a string, would be the same as the result of the
   *      expression produced by this method when this parameter is true.
   * @return an expression that evaluates to the concatenation of the string
   *      form of the results of evaluating the expressions passed to
   *      {@link #append} in order.
   */
  public Expression toExpression(boolean mustBeString) {
    return new Emitter(new ArrayList<Part>(parts)).toExpression(mustBeString);
  }
}

abstract class Part { /* marker base class */ }
final class StringPart extends Part {
  final Expression e;
  StringPart(Expression e) { this.e = e; }
}
final class SideEffectPart extends Part {
  final Expression e;
  final boolean canReorder;
  SideEffectPart(Expression e, boolean canReorder) {
    this.e = e;
    this.canReorder = canReorder;
  }
}

final class Emitter {
  final List<Part> parts;
  Emitter(List<Part> parts) { this.parts = parts; }

  Expression toExpression(boolean mustBeString) {
    moveSideEffectsBack();
    foldAdjacentStringLiterals();
    foldSideEffectsIntoStringPart(true);
    checkThatOneOperandIsStringLike(mustBeString);
    if (parts.isEmpty()) {
      return StringLiteral.valueOf(FilePosition.UNKNOWN, "");
    }
    Expression e = null;
    for (Part p : parts) {
      Expression pe;
      if (p instanceof StringPart) {
        pe = ((StringPart) p).e;
      } else if (p instanceof SideEffectPart) {
        SideEffectPart sep = (SideEffectPart) p;
        FilePosition epos = sep.e.getFilePosition();
        pe = Operation.createInfix(
            Operator.COMMA, sep.e, StringLiteral.valueOf(epos, ""));
      } else {
        throw new IllegalStateException();
      }
      if (e != null) {
        e = Operation.createInfix(Operator.ADDITION, e, pe);
      } else {
        e = pe;
      }
    }
    return e;
  }

  /**
   * Move side effects before runs of StringParts so that we can more
   * efficiently fold adjacent string literal portions.
   */
  private void moveSideEffectsBack() {
    // Move side effects before runs of string parts so that we can attach the
    // side effect to the string part.
    // This allows us to turn the naive sequence:
    //    o___.push('<a');
    //    foo();
    //    o___.push(' href="');
    // which has 5 lookups, and 3 method calls to
    //    o___.push((foo(), '<a href="'));
    // which has 3 lookups and 2 method calls and is semantically equivalent
    // assuming foo cannot reference o___.
    List<Part> newParts = new ArrayList<Part>();
    List<StringPart> stringParts = new ArrayList<StringPart>();
    for (Part p : parts) {
      if (p instanceof StringPart) {
        if (((StringPart) p).e instanceof Literal) {
          // We can move Literals past ones that have side effect, since their
          // meaning is independent of position, and they have no side effect.
          stringParts.add((StringPart) p);
          continue;
        }
      } else if (p instanceof SideEffectPart) {
        if (((SideEffectPart) p).canReorder) {
          newParts.add(p);
          continue;
        }
      }
      newParts.addAll(stringParts);
      stringParts.clear();
      newParts.add(p);
    }
    newParts.addAll(stringParts);
    stringParts = null;

    parts.clear();

    // Combine runs of side effects into a single comma operation.
    SideEffectPart lastSep = null;
    for (Part p : newParts) {
      if (p instanceof SideEffectPart) {
        SideEffectPart sep = (SideEffectPart) p;
        lastSep = lastSep == null
            ? sep
            // Left associative combination
            : new SideEffectPart(
                Operation.createInfix(Operator.COMMA, lastSep.e, sep.e),
                lastSep.canReorder && sep.canReorder);
      } else {
        if (lastSep != null) {
          parts.add(lastSep);
          lastSep = null;
        }
        parts.add(p);
      }
    }
    if (lastSep != null) { parts.add(lastSep); }
  }

  private void foldAdjacentStringLiterals() {
    for (int i = parts.size(); --i >= 0;) {
      Part p = parts.get(i);
      if (!(p instanceof StringPart)) { continue; }
      StringPart sp = (StringPart) p;
      if (!(sp.e instanceof StringLiteral)) { continue; }
      int j = i;
      while (j > 0 && isStringLiteralPart(parts.get(j - 1))) { --j; }
      if (i != j) {
        List<Part> toFold = parts.subList(j, i + 1);
        i = j;
        StringBuilder sb = new StringBuilder();
        for (Part tf : toFold) {
          sb.append(((StringLiteral) ((StringPart) tf).e).getUnquotedValue());
        }
        StringPart first = (StringPart) toFold.get(0);
        StringPart last = (StringPart) toFold.get(toFold.size() - 1);
        FilePosition pos = FilePosition.span(
            first.e.getFilePosition(), last.e.getFilePosition());
        toFold.clear();
        toFold.add(new StringPart(StringLiteral.valueOf(pos, sb.toString())));
      }
    }
  }

  private static boolean isStringLiteralPart(Part p) {
    return p instanceof StringPart
        && ((StringPart) p).e instanceof StringLiteral;
  }

  private void foldSideEffectsIntoStringPart(boolean alwaysFold) {
    for (int i = parts.size(); --i >= 0;) {
      Part p = parts.get(i);
      if (p instanceof SideEffectPart) {
        SideEffectPart sep = (SideEffectPart) p;
        Part np = i + 1 < parts.size() ? parts.get(i + 1) : null;
        Part pp = i > 0 ? parts.get(i - 1) : null;
        if (np instanceof StringPart
            && (alwaysFold || pp instanceof StringPart)) {
          Expression se = ((StringPart) np).e;
          StringPart combined = new StringPart(Operation.create(
              FilePosition.UNKNOWN, Operator.COMMA, sep.e, se));
          List<Part> both = parts.subList(i, i + 2);
          both.clear();
          both.add(combined);
        } else if (alwaysFold) {
          FilePosition pos = sep.e.getFilePosition();
          parts.set(i, new StringPart(Operation.create(
              pos, Operator.COMMA, sep.e,
              new StringLiteral(FilePosition.endOf(pos), ""))));
        }
      }
    }
  }

  private void checkThatOneOperandIsStringLike(boolean mustBeString) {
    switch (parts.size()) {
      case 0: return;
      case 1// We need to introduce a + operator unless we can prove that the
               // single argument is a string.
        if (!mustBeString || isStringy(parts.get(0), true)) { return; }
        break;
      default:
        // We need to make sure that one of the first two results produces a
        // stringy result so that addition operators added between exprs act
        // as string concatenation instead of numeric addition.
        if (isStringy(parts.get(0), false) || isStringy(parts.get(1), false)) {
          return;
        }
        break;
    }
    Expression e = ((StringPart) parts.get(0)).e;
    parts.set(0, new StringPart(makeStringy(e, parts.size() == 1)));
  }

  static boolean isStringy(Part p, boolean strict) {
    return p instanceof StringPart && isStringy(((StringPart) p).e, strict);
  }

  static boolean isStringy(Expression e, boolean strict) {
    if (e instanceof StringLiteral) { return true; }
    if (e instanceof Operation) {
      Operation op = (Operation) e;
      List<? extends Expression> operands = op.children();
      switch (op.getOperator()) {
        case ADDITION:
          return isStringy(operands.get(0), false)
              || isStringy(operands.get(1), false);
        case TERNARY:
        case LOGICAL_AND:
        case LOGICAL_OR:
          return isStringy(operands.get(operands.size() - 2), strict)
              && isStringy(operands.get(operands.size() - 1), strict);
        case COMMA:
          return isStringy(operands.get(operands.size() - 1), strict);
        case CONSTRUCTOR:
          return !strict;
        default:
          return false;
      }
    }
    if (e instanceof ArrayConstructor
        || e instanceof FunctionConstructor
        || e instanceof ObjectConstructor) {
      return !strict;
    }
    return false;
  }

  static Expression makeStringy(Expression e, boolean strict) {
    if (isStringy(e, strict)) { return e; }

    Expression stringier = null;
    if (e instanceof Operation) {
      Operation op = (Operation) e;
      List<? extends Expression> operands = op.children();
      switch (op.getOperator()) {
        // Change arguments to ||,&& would change semantics.
        case TERNARY:
          stringier = Operation.create(
              e.getFilePosition(), Operator.TERNARY, operands.get(0),
              makeStringy(operands.get(1), strict),
              makeStringy(operands.get(2), strict));
          break;
        case COMMA:
          stringier = Operation.create(
              e.getFilePosition(), Operator.COMMA, operands.get(0),
              makeStringy(operands.get(1), strict));
          break;
        default: break;
      }
    }
    if (stringier == null) {
      stringier = Operation.createInfix(
          Operator.ADDITION,
          StringLiteral.valueOf(FilePosition.startOf(e.getFilePosition()), ""),
          e);
    }
    assert isStringy(stringier, strict);
    return stringier;
  }

  static boolean is(Expression e, Operator op) {
    return e instanceof Operation && op == ((Operation) e).getOperator();
  }

  static boolean isStringConcat(Expression e) {
    if (!is(e, Operator.ADDITION)) { return false; }
    Operation op = (Operation) e;
    return isStringy(op.children().get(0), false)
        || isStringy(op.children().get(1), false);
  }

  static String asString(Literal l) {
    if (l instanceof NumberLiteral) {
      return NumberLiteral.numberToString(((NumberLiteral) l).doubleValue());
    } else {
      StringBuilder sb = new StringBuilder();
      JsMinimalPrinter p = new JsMinimalPrinter(sb);
      l.render(new RenderContext(p));
      p.noMoreTokens();
      return sb.toString();
    }
  }

  static StringLiteral asStringLiteral(Expression e) {
    if (e instanceof Literal) {
      if (e instanceof StringLiteral) { return (StringLiteral) e; }
      return StringLiteral.valueOf(e.getFilePosition(), asString((Literal) e));
    } else if (is(e, Operator.NEGATION)) {
      Expression child = ((Operation) e).children().get(0);
      if (child instanceof NumberLiteral) {
        return StringLiteral.valueOf(
            e.getFilePosition(), "-" + asString((Literal) child));
      }
    }
    return null;
  }
}
TOP

Related Classes of com.google.caja.plugin.templates.SideEffectPart

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.