001 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file
002 // for details. All rights reserved. Use of this source code is governed by a
003 // BSD-style license that can be found in the LICENSE file.
004
005 package com.google.dart.compiler.backend.js.ast;
006
007 import com.google.dart.compiler.util.Maps;
008 import org.jetbrains.annotations.NotNull;
009 import org.jetbrains.annotations.Nullable;
010
011 import java.util.Collections;
012 import java.util.HashMap;
013 import java.util.Map;
014
015 import static com.google.dart.compiler.backend.js.ast.AstPackage.JsObjectScope;
016
017 /**
018 * A scope is a factory for creating and allocating
019 * {@link JsName}s. A JavaScript AST is
020 * built in terms of abstract name objects without worrying about obfuscation,
021 * keyword/identifier blacklisting, and so on.
022 * <p/>
023 * <p/>
024 * <p/>
025 * Scopes are associated with
026 * {@link JsFunction}s, but the two are
027 * not equivalent. Functions <i>have</i> scopes, but a scope does not
028 * necessarily have an associated Function. Examples of this include the
029 * {@link JsRootScope} and synthetic
030 * scopes that might be created by a client.
031 * <p/>
032 * <p/>
033 * <p/>
034 * Scopes can have parents to provide constraints when allocating actual
035 * identifiers for names. Specifically, names in child scopes are chosen such
036 * that they do not conflict with names in their parent scopes. The ultimate
037 * parent is usually the global scope (see
038 * {@link JsProgram#getRootScope()}),
039 * but parentless scopes are useful for managing names that are always accessed
040 * with a qualifier and could therefore never be confused with the global scope
041 * hierarchy.
042 */
043 public abstract class JsScope {
044 @NotNull
045 private final String description;
046 private Map<String, JsName> names = Collections.emptyMap();
047 private final JsScope parent;
048 protected int tempIndex = 0;
049 private final String scopeId;
050
051 public JsScope(JsScope parent, @NotNull String description, @Nullable String scopeId) {
052 assert (parent != null);
053 this.scopeId = scopeId;
054 this.description = description;
055 this.parent = parent;
056 }
057
058 protected JsScope(@NotNull String description) {
059 this.description = description;
060 parent = null;
061 scopeId = null;
062 }
063
064 @NotNull
065 public JsScope innerObjectScope(@NotNull String scopeName) {
066 return JsObjectScope(this, scopeName);
067 }
068
069 /**
070 * Gets a name object associated with the specified identifier in this scope,
071 * creating it if necessary.<br/>
072 * If the JsName does not exist yet, a new JsName is created. The identifier,
073 * short name, and original name of the newly created JsName are equal to
074 * the given identifier.
075 *
076 * @param identifier An identifier that is unique within this scope.
077 */
078 @NotNull
079 public JsName declareName(@NotNull String identifier) {
080 JsName name = findOwnName(identifier);
081 return name != null ? name : doCreateName(identifier);
082 }
083
084 /**
085 * Creates a new variable with an unique ident in this scope.
086 * The generated JsName is guaranteed to have an identifier that does not clash with any existing variables in the scope.
087 * Future declarations of variables might however clash with the temporary
088 * (unless they use this function).
089 */
090 @NotNull
091 public JsName declareFreshName(@NotNull String suggestedName) {
092 String name = suggestedName;
093 int counter = 0;
094 while (hasOwnName(name)) {
095 name = suggestedName + '_' + counter++;
096 }
097 return doCreateName(name);
098 }
099
100 private String getNextTempName() {
101 // introduced by the compiler
102 return "tmp$" + (scopeId != null ? scopeId + "$" : "") + tempIndex++;
103 }
104
105 /**
106 * Creates a temporary variable with an unique name in this scope.
107 * The generated temporary is guaranteed to have an identifier (but not short
108 * name) that does not clash with any existing variables in the scope.
109 * Future declarations of variables might however clash with the temporary.
110 */
111 @NotNull
112 public JsName declareTemporary() {
113 return declareFreshName(getNextTempName());
114 }
115
116 /**
117 * Attempts to find the name object for the specified ident, searching in this
118 * scope, and if not found, in the parent scopes.
119 *
120 * @return <code>null</code> if the identifier has no associated name
121 */
122 @Nullable
123 public final JsName findName(String ident) {
124 JsName name = findOwnName(ident);
125 if (name == null && parent != null) {
126 return parent.findName(ident);
127 }
128 return name;
129 }
130
131 public boolean hasOwnName(@NotNull String name) {
132 return names.containsKey(name);
133 }
134
135 /**
136 * Returns the parent scope of this scope, or <code>null</code> if this is the
137 * root scope.
138 */
139 public final JsScope getParent() {
140 return parent;
141 }
142
143 public JsProgram getProgram() {
144 assert (parent != null) : "Subclasses must override getProgram() if they do not set a parent";
145 return parent.getProgram();
146 }
147
148 @Override
149 public final String toString() {
150 if (parent != null) {
151 return description + "->" + parent;
152 }
153 else {
154 return description;
155 }
156 }
157
158 public void copyOwnNames(JsScope other) {
159 names = new HashMap<String, JsName>(names);
160 names.putAll(other.names);
161 }
162
163 @NotNull
164 public String getDescription() {
165 return description;
166 }
167
168 @NotNull
169 protected JsName doCreateName(@NotNull String ident) {
170 JsName name = new JsName(this, ident);
171 names = Maps.put(names, ident, name);
172 return name;
173 }
174
175 /**
176 * Attempts to find the name object for the specified ident, searching in this
177 * scope only.
178 *
179 * @return <code>null</code> if the identifier has no associated name
180 */
181 protected JsName findOwnName(@NotNull String ident) {
182 return names.get(ident);
183 }
184 }