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 }