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