| /* |
| * Copyright 2008 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.gwt.dev.js.ast; |
| |
| import com.google.gwt.dev.js.JsKeywords; |
| import com.google.gwt.dev.util.StringInterner; |
| |
| import java.io.Serializable; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| /** |
| * A scope is a factory for creating and allocating |
| * {@link com.google.gwt.dev.js.ast.JsName}s. A JavaScript AST is built in terms |
| * of abstract name objects without worrying about obfuscation, |
| * keyword/identifier blacklisting, and so on. |
| * |
| * <p> |
| * |
| * Scopes are associated with {@link com.google.gwt.dev.js.ast.JsFunction}s, but |
| * the two are not equivalent. Functions <i>have</i> scopes, but a scope does |
| * not necessarily have an associated Function. Examples of this include the |
| * {@link com.google.gwt.dev.js.ast.JsRootScope} and synthetic scopes that might |
| * be created by a client. |
| * |
| * <p> |
| * |
| * Scopes can have parents to provide constraints when allocating actual |
| * identifiers for names. Specifically, names in child scopes are chosen such |
| * that they do not conflict with names in their parent scopes. The ultimate |
| * parent is usually the global scope (see |
| * {@link com.google.gwt.dev.js.ast.JsProgram#getGlobalScope()}), but parentless |
| * scopes are useful for managing names that are always accessed with a |
| * qualifier and could therefore never be confused with the global scope |
| * hierarchy. |
| */ |
| public abstract class JsScope implements Serializable { |
| |
| /** |
| * Prevents the client from programmatically creating an illegal ident. |
| */ |
| private static String maybeMangleKeyword(String ident) { |
| if (JsKeywords.isKeyword(ident)) { |
| ident = ident + "_$"; |
| } |
| return StringInterner.get().intern(ident); |
| } |
| |
| private final String description; |
| |
| protected JsScope(String description) { |
| this.description = StringInterner.get().intern(description); |
| } |
| |
| /** |
| * Gets a name object associated with the specified ident in this scope, |
| * creating it if necessary. |
| * |
| * @param ident An identifier that is unique within this scope. |
| */ |
| public final JsName declareName(String ident) { |
| ident = maybeMangleKeyword(ident); |
| JsName name = findExistingNameNoRecurse(ident); |
| if (name != null) { |
| return name; |
| } |
| return doCreateName(ident, ident); |
| } |
| |
| /** |
| * Gets a name object associated with the specified ident in this scope, |
| * creating it if necessary. |
| * |
| * @param ident An identifier that is unique within this scope. |
| * @param shortIdent A "pretty" name that does not have to be unique. |
| * @throws IllegalArgumentException if ident already exists in this scope but |
| * the requested short name does not match the existing short name. |
| */ |
| public final JsName declareName(String ident, String shortIdent) { |
| ident = maybeMangleKeyword(ident); |
| shortIdent = maybeMangleKeyword(shortIdent); |
| JsName name = findExistingNameNoRecurse(ident); |
| if (name != null) { |
| if (!name.getShortIdent().equals(shortIdent)) { |
| throw new IllegalArgumentException("Requested short name " + shortIdent |
| + " conflicts with preexisting short name " + name.getShortIdent() |
| + " for identifier " + ident); |
| } |
| return name; |
| } |
| return doCreateName(ident, shortIdent); |
| } |
| |
| /** |
| * Attempts to find the name object for the specified ident, searching in this |
| * scope, and if not found, in the parent scopes. |
| * |
| * @return <code>null</code> if the identifier has no associated name |
| */ |
| public final JsName findExistingName(String ident) { |
| ident = maybeMangleKeyword(ident); |
| JsName name = findExistingNameNoRecurse(ident); |
| if (name == null && getParent() != null) { |
| return getParent().findExistingName(ident); |
| } |
| return name; |
| } |
| |
| /** |
| * Attempts to find an unobfuscatable name object for the specified ident, |
| * searching in this scope, and if not found, in the parent scopes. |
| * |
| * @return <code>null</code> if the identifier has no associated name |
| */ |
| public final JsName findExistingUnobfuscatableName(String ident) { |
| ident = maybeMangleKeyword(ident); |
| JsName name = findExistingNameNoRecurse(ident); |
| if (name != null && name.isObfuscatable()) { |
| name = null; |
| } |
| if (name == null && getParent() != null) { |
| return getParent().findExistingUnobfuscatableName(ident); |
| } |
| return name; |
| } |
| |
| /** |
| * Returns an iterator for all the names defined by this scope. |
| */ |
| public abstract Iterator<JsName> getAllNames(); |
| |
| /** |
| * Returns a list of this scope's child scopes. |
| */ |
| public abstract List<JsScope> getChildren(); |
| |
| /** |
| * Returns the parent scope of this scope, or <code>null</code> if this is the |
| * root scope. |
| */ |
| public abstract JsScope getParent(); |
| |
| @Override |
| public final String toString() { |
| if (getParent() != null) { |
| return description + "->" + getParent(); |
| } else { |
| return description; |
| } |
| } |
| |
| protected abstract void addChild(JsScope child); |
| |
| /** |
| * Creates a new name in this scope. |
| */ |
| protected abstract JsName doCreateName(String ident, String shortIdent); |
| |
| /** |
| * Attempts to find the name object for the specified ident, searching in this |
| * scope only. |
| * |
| * @return <code>null</code> if the identifier has no associated name |
| */ |
| protected abstract JsName findExistingNameNoRecurse(String ident); |
| } |