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        @NotNull
084        public JsName declareName(String identifier) {
085            JsName name = findOwnName(identifier);
086            return name != null ? name : doCreateName(identifier);
087        }
088    
089        /**
090         * Creates a new variable with an unique ident in this scope.
091         * The generated JsName is guaranteed to have an identifier that does not clash with any existing variables in the scope.
092         * Future declarations of variables might however clash with the temporary
093         * (unless they use this function).
094         */
095        @NotNull
096        public JsName declareFreshName(String suggestedName) {
097            String name = suggestedName;
098            int counter = 0;
099            while (hasOwnName(name)) {
100                name = suggestedName + '_' + counter++;
101            }
102            return doCreateName(name);
103        }
104    
105        private String getNextTempName() {
106            // introduced by the compiler
107            return "tmp$" + (scopeId != null ? scopeId + "$" : "") + tempIndex++;
108        }
109    
110        /**
111         * Creates a temporary variable with an unique name in this scope.
112         * The generated temporary is guaranteed to have an identifier (but not short
113         * name) that does not clash with any existing variables in the scope.
114         * Future declarations of variables might however clash with the temporary.
115         */
116        @NotNull
117        public JsName declareTemporary() {
118            return declareFreshName(getNextTempName());
119        }
120    
121        /**
122         * Attempts to find the name object for the specified ident, searching in this
123         * scope, and if not found, in the parent scopes.
124         *
125         * @return <code>null</code> if the identifier has no associated name
126         */
127        @Nullable
128        public final JsName findName(String ident) {
129            JsName name = findOwnName(ident);
130            if (name == null && parent != null) {
131                return parent.findName(ident);
132            }
133            return name;
134        }
135    
136        protected boolean hasOwnName(@NotNull String name) {
137            return names.containsKey(name);
138        }
139    
140        /**
141         * Returns the parent scope of this scope, or <code>null</code> if this is the
142         * root scope.
143         */
144        public final JsScope getParent() {
145            return parent;
146        }
147    
148        public JsProgram getProgram() {
149            assert (parent != null) : "Subclasses must override getProgram() if they do not set a parent";
150            return parent.getProgram();
151        }
152    
153        @Override
154        public final String toString() {
155            if (parent != null) {
156                return description + "->" + parent;
157            }
158            else {
159                return description;
160            }
161        }
162    
163        @NotNull
164        protected JsName doCreateName(String ident) {
165            JsName name = new JsName(this, ident);
166            names = Maps.put(names, ident, name);
167            return name;
168        }
169    
170        /**
171         * Attempts to find the name object for the specified ident, searching in this
172         * scope only.
173         *
174         * @return <code>null</code> if the identifier has no associated name
175         */
176        protected JsName findOwnName(String ident) {
177            return names.get(ident);
178        }
179    }