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 }