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    }