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 }