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 }