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