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    }