Example #1
0
/**
 * A mapping of identifiers which ensures that no use of an identifier in a scope masks a use of an
 * identifier in an outer scope.
 *
 * <p>Javascript is not a block scoped language, but IHTML constructs are block scoped, so we
 * alpha-rename all variables to prevent collisions.
 *
 * @param <NAME> a type that can work as a hashtable key
 * @param <BINDING> type of data associated with a particular name. This can be any object related
 *     to the result of resolving the name.
 * @author [email protected]
 */
public final class NameContext<NAME, BINDING> {
  private final NameContext<NAME, BINDING> parent;
  private final Iterator<String> nameGenerator;
  /** maps names in original source to details about the renamed instance. */
  private final Map<NAME, VarInfo<NAME, BINDING>> vars = Maps.newLinkedHashMap();

  public static final class VarInfo<NAME, BINDING> {
    public final NAME origName;
    public final String newName;
    public final FilePosition declaredAt;
    private BINDING binding;

    private VarInfo(NAME origName, String newName, FilePosition declaredAt) {
      assert origName != null;
      this.origName = origName;
      this.newName = newName;
      this.declaredAt = declaredAt;
    }

    public BINDING getBinding() {
      return binding;
    }

    public void bind(BINDING binding) {
      this.binding = binding;
    }

    @Override
    public String toString() {
      return "(" + getClass().getSimpleName() + " " + origName + ")";
    }
  }

  /**
   * Creates a context with no parent context.
   *
   * @param nameGenerator an infinite iterator that returns safe identifiers and that never returns
   *     the same String twice. Typically, a {@link com.google.caja.util.SafeIdentifierMaker}.
   */
  public NameContext(Iterator<String> nameGenerator) {
    this(null, nameGenerator);
  }

  private NameContext(NameContext<NAME, BINDING> parent, Iterator<String> nameGenerator) {
    this.parent = parent;
    this.nameGenerator = nameGenerator;
  }

  /**
   * Produces a context that has the same name generator and which has this context as its parent.
   */
  public NameContext<NAME, BINDING> makeChildContext() {
    return new NameContext<NAME, BINDING>(this, this.nameGenerator);
  }

  /**
   * The context that is used to resolve original names that have not been declared in this context,
   * or null if no such context.
   */
  public NameContext<NAME, BINDING> getParentContext() {
    return parent;
  }

  /**
   * Introduce a new declaration which will mask any declaration with the same name in the {@link
   * #getParentContext} context.
   */
  public VarInfo<NAME, BINDING> declare(NAME origName, FilePosition declSite)
      throws RedeclarationException {
    VarInfo<NAME, BINDING> d = vars.get(origName);
    if (d == null) {
      String newName = nameGenerator.next();
      VarInfo<NAME, BINDING> vi = new VarInfo<NAME, BINDING>(origName, newName, declSite);
      vars.put(origName, vi);
      return vi;
    } else {
      FilePosition dPos = d.declaredAt;
      throw new RedeclarationException(
          new Message(
              RewriterMessageType.CANNOT_REDECLARE_VAR,
              declSite,
              MessagePart.Factory.valueOf(origName.toString()),
              dPos));
    }
  }

  /**
   * Find a declaration with the given original name, looking in ancestor contexts if {@code
   * declare(originalName, ...)} was never called on this context.
   */
  public VarInfo<NAME, BINDING> lookup(NAME originalName) {
    for (NameContext<NAME, BINDING> c = this; c != null; c = c.parent) {
      VarInfo<NAME, BINDING> vi = c.vars.get(originalName);
      if (vi != null) {
        return vi;
      }
    }
    return null;
  }

  /** The set of vars declared in this context, not including any in ancestor contexts. */
  public Iterable<VarInfo<NAME, BINDING>> vars() {
    return Collections.unmodifiableMap(vars).values();
  }

  /** The name generator used to generate names for new declarations. */
  public Iterator<String> getNameGenerator() {
    return nameGenerator;
  }

  public static class RedeclarationException extends CajaException {
    public RedeclarationException(Message m, Throwable th) {
      super(m, th);
    }

    public RedeclarationException(Message m) {
      this(m, null);
    }
  }
}