001: /*
002: * Copyright 2007 Google Inc.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License. You may obtain a copy of
006: * the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013: * License for the specific language governing permissions and limitations under
014: * the License.
015: */
016: package com.google.gwt.dev.js.ast;
017:
018: import com.google.gwt.dev.js.JsKeywords;
019:
020: import java.util.ArrayList;
021: import java.util.Iterator;
022: import java.util.List;
023: import java.util.Map;
024: import java.util.TreeMap;
025:
026: /**
027: * A scope is a factory for creating and allocating
028: * {@link com.google.gwt.dev.js.ast.JsName}s. A JavaScript AST is built
029: * in terms of abstract name objects without worrying about obfuscation,
030: * keyword/identifier blacklisting, and so on.
031: *
032: * <p>
033: *
034: * Scopes are associated with {@link com.google.gwt.dev.js.ast.JsFunction}s,
035: * but the two are not equivalent. Functions <i>have</i> scopes, but a scope
036: * does not necessarily have an associated Function. Examples of this include
037: * the {@link com.google.gwt.dev.js.ast.JsRootScope} and synthetic scopes that
038: * might be created by a client.
039: *
040: * <p>
041: *
042: * Scopes can have parents to provide constraints when allocating actual
043: * identifiers for names. Specifically, names in child scopes are chosen such
044: * that they do not conflict with names in their parent scopes. The ultimate
045: * parent is usually the global scope (see
046: * {@link com.google.gwt.dev.js.ast.JsProgram#getGlobalScope()}), but
047: * parentless scopes are useful for managing names that are always accessed with
048: * a qualifier and could therefore never be confused with the global scope
049: * hierarchy.
050: */
051: public class JsScope {
052:
053: /**
054: * Prevents the client from programmatically creating an illegal ident.
055: */
056: private static String maybeMangleKeyword(String ident) {
057: if (JsKeywords.isKeyword(ident)) {
058: ident = ident + "_$";
059: }
060: return ident;
061: }
062:
063: private final List<JsScope> children = new ArrayList<JsScope>();
064: private final String description;
065: private final Map<String, JsName> names = new TreeMap<String, JsName>();
066: private final JsScope parent;
067:
068: /**
069: * Create a scope with parent.
070: */
071: public JsScope(JsScope parent, String description) {
072: assert (parent != null);
073: this .description = description;
074: this .parent = parent;
075: this .parent.children.add(this );
076: }
077:
078: /**
079: * Subclasses can be parentless.
080: */
081: protected JsScope(String description) {
082: this .description = description;
083: this .parent = null;
084: }
085:
086: /**
087: * Gets a name object associated with the specified ident in this scope,
088: * creating it if necessary.
089: *
090: * @param ident An identifier that is unique within this scope.
091: */
092: public JsName declareName(String ident) {
093: ident = maybeMangleKeyword(ident);
094: JsName name = findExistingNameNoRecurse(ident);
095: if (name != null) {
096: return name;
097: }
098: return doCreateName(ident, ident);
099: }
100:
101: /**
102: * Gets a name object associated with the specified ident in this scope,
103: * creating it if necessary.
104: *
105: * @param ident An identifier that is unique within this scope.
106: * @param shortIdent A "pretty" name that does not have to be unique.
107: * @throws IllegalArgumentException if ident already exists in this scope but
108: * the requested short name does not match the existing short name.
109: */
110: public JsName declareName(String ident, String shortIdent) {
111: ident = maybeMangleKeyword(ident);
112: shortIdent = maybeMangleKeyword(shortIdent);
113: JsName name = findExistingNameNoRecurse(ident);
114: if (name != null) {
115: if (!name.getShortIdent().equals(shortIdent)) {
116: throw new IllegalArgumentException(
117: "Requested short name "
118: + shortIdent
119: + " conflicts with preexisting short name "
120: + name.getShortIdent()
121: + " for identifier " + ident);
122: }
123: return name;
124: }
125: return doCreateName(ident, shortIdent);
126: }
127:
128: /**
129: * Attempts to find the name object for the specified ident, searching in this
130: * scope, and if not found, in the parent scopes.
131: *
132: * @return <code>null</code> if the identifier has no associated name
133: */
134: public final JsName findExistingName(String ident) {
135: ident = maybeMangleKeyword(ident);
136: JsName name = findExistingNameNoRecurse(ident);
137: if (name == null && parent != null) {
138: return parent.findExistingName(ident);
139: }
140: return name;
141: }
142:
143: /**
144: * Attempts to find an unobfuscatable name object for the specified ident,
145: * searching in this scope, and if not found, in the parent scopes.
146: *
147: * @return <code>null</code> if the identifier has no associated name
148: */
149: public final JsName findExistingUnobfuscatableName(String ident) {
150: ident = maybeMangleKeyword(ident);
151: JsName name = findExistingNameNoRecurse(ident);
152: if (name != null && name.isObfuscatable()) {
153: name = null;
154: }
155: if (name == null && parent != null) {
156: return parent.findExistingUnobfuscatableName(ident);
157: }
158: return name;
159: }
160:
161: /**
162: * Returns an iterator for all the names defined by this scope.
163: */
164: public Iterator<JsName> getAllNames() {
165: return names.values().iterator();
166: }
167:
168: /**
169: * Returns a list of this scope's child scopes.
170: */
171: public final List<JsScope> getChildren() {
172: return children;
173: }
174:
175: /**
176: * Returns the parent scope of this scope, or <code>null</code> if this is
177: * the root scope.
178: */
179: public final JsScope getParent() {
180: return parent;
181: }
182:
183: /**
184: * Returns the associated program.
185: */
186: public JsProgram getProgram() {
187: assert (parent != null) : "Subclasses must override getProgram() if they do not set a parent";
188: return parent.getProgram();
189: }
190:
191: @Override
192: public final String toString() {
193: if (parent != null) {
194: return description + "->" + parent;
195: } else {
196: return description;
197: }
198: }
199:
200: /**
201: * Creates a new name in this scope.
202: */
203: protected JsName doCreateName(String ident, String shortIdent) {
204: JsName name = new JsName(this , ident, shortIdent);
205: names.put(ident, name);
206: return name;
207: }
208:
209: /**
210: * Attempts to find the name object for the specified ident, searching in this
211: * scope only.
212: *
213: * @return <code>null</code> if the identifier has no associated name
214: */
215: protected JsName findExistingNameNoRecurse(String ident) {
216: return names.get(ident);
217: }
218:
219: }
|