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    }