package com.oracle.js.parser.ir;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;
import org.graalvm.collections.EconomicMap;
public final class Scope {
private final Scope parent;
private final int type;
private final int flags;
private static final int BLOCK_SCOPE = 1 << 0;
private static final int FUNCTION_BODY_SCOPE = 1 << 1;
private static final int FUNCTION_PARAMETER_SCOPE = 1 << 2;
private static final int CATCH_PARAMETER_SCOPE = 1 << 3;
private static final int GLOBAL_SCOPE = 1 << 4;
private static final int MODULE_SCOPE = 1 << 5;
private static final int FUNCTION_TOP_SCOPE = 1 << 6;
private static final int SWITCH_BLOCK_SCOPE = 1 << 7;
private static final int CLASS_SCOPE = 1 << 8;
private static final int EVAL_SCOPE = 1 << 9;
private static final int IN_FUNCTION = 1 << 16;
private static final int IN_METHOD = 1 << 17;
private static final int IN_DERIVED_CONSTRUCTOR = 1 << 18;
private static final int IS_CLASS_FIELD_INITIALIZER = 1 << 19;
protected final EconomicMap<String, Symbol> symbols;
protected List<Map.Entry<VarNode, Scope>> hoistedVarDeclarations;
protected List<Map.Entry<VarNode, Scope>> hoistableBlockFunctionDeclarations;
private int blockScopedOrRedeclaredSymbols;
private int declaredNames;
private boolean closed;
private Scope(Scope parent, int type, int flags) {
this.parent = parent;
this.type = type | (isFunctionTopScope(type, parent) ? FUNCTION_TOP_SCOPE : 0);
this.symbols = EconomicMap.create();
this.flags = flags;
}
private Scope(Scope parent, int type) {
this(parent, type, parent == null ? 0 : parent.flags);
}
private static boolean isFunctionTopScope(int type, Scope parent) {
return (type & FUNCTION_PARAMETER_SCOPE) != 0 || ((type & FUNCTION_BODY_SCOPE) != 0 && (parent == null || !parent.isFunctionParameterScope()));
}
private static int computeFlags(Scope parent, int functionFlags) {
if ((functionFlags & FunctionNode.IS_ARROW) != 0) {
return parent.flags;
} else {
int flags = 0;
flags |= IN_FUNCTION;
flags |= ((functionFlags & FunctionNode.IS_METHOD) != 0) ? IN_METHOD : 0;
flags |= ((functionFlags & FunctionNode.IS_DERIVED_CONSTRUCTOR) != 0) ? IN_DERIVED_CONSTRUCTOR : 0;
flags |= ((functionFlags & FunctionNode.IS_CLASS_FIELD_INITIALIZER) != 0) ? IS_CLASS_FIELD_INITIALIZER : 0;
return flags;
}
}
public static Scope createGlobal() {
return new Scope(null, FUNCTION_BODY_SCOPE | GLOBAL_SCOPE);
}
public static Scope createModule() {
return new Scope(null, FUNCTION_BODY_SCOPE | MODULE_SCOPE);
}
public static Scope createFunctionBody(Scope parent, int functionFlags) {
return new Scope(parent, FUNCTION_BODY_SCOPE, computeFlags(parent, functionFlags));
}
public static Scope createBlock(Scope parent) {
return new Scope(parent, BLOCK_SCOPE);
}
public static Scope createCatch(Scope parent) {
return new Scope(parent, CATCH_PARAMETER_SCOPE);
}
public static Scope createParameter(Scope parent, int functionFlags) {
return new Scope(parent, FUNCTION_PARAMETER_SCOPE, computeFlags(parent, functionFlags));
}
public static Scope createSwitchBlock(Scope parent) {
return new Scope(parent, BLOCK_SCOPE | SWITCH_BLOCK_SCOPE);
}
public static Scope createClass(Scope parent) {
return new Scope(parent, BLOCK_SCOPE | CLASS_SCOPE);
}
public static Scope createEval(Scope parent, boolean strict) {
return new Scope(parent, EVAL_SCOPE | (strict ? FUNCTION_BODY_SCOPE : 0));
}
public Scope getParent() {
return parent;
}
public Iterable<Symbol> getSymbols() {
return symbols.getValues();
}
public Symbol getExistingSymbol(final String name) {
return symbols.get(name);
}
public boolean hasSymbol(final String name) {
return symbols.containsKey(name);
}
public int getSymbolCount() {
return symbols.size();
}
public Symbol putSymbol(final Symbol symbol) {
assert !closed : "scope is closed";
Symbol existing = symbols.put(symbol.getName(), symbol);
if (existing != null) {
assert (existing.getFlags() & Symbol.KINDMASK) == (symbol.getFlags() & Symbol.KINDMASK) : symbol;
return existing;
}
if (symbol.isBlockScoped() || symbol.isVarRedeclaredHere()) {
blockScopedOrRedeclaredSymbols++;
}
if (symbol.isBlockScoped() || (symbol.isVar() && !symbol.isParam())) {
declaredNames++;
}
return null;
}
public boolean hasBlockScopedOrRedeclaredSymbols() {
return blockScopedOrRedeclaredSymbols != 0;
}
public boolean hasDeclarations() {
return declaredNames != 0;
}
public boolean isLexicallyDeclaredName(final String varName, final boolean annexB, final boolean includeParameters) {
for (Scope current = this; current != null; current = current.getParent()) {
Symbol existingSymbol = current.getExistingSymbol(varName);
if (existingSymbol != null && existingSymbol.isBlockScoped()) {
if (existingSymbol.isCatchParameter() && annexB) {
continue;
}
return true;
}
if (includeParameters ? current.isFunctionTopScope() : current.isFunctionBodyScope()) {
break;
}
}
return false;
}
public Symbol findBlockScopedSymbolInFunction(String varName) {
for (Scope current = this; current != null; current = current.getParent()) {
Symbol existingSymbol = current.getExistingSymbol(varName);
if (existingSymbol != null) {
if (existingSymbol.isBlockScoped()) {
return existingSymbol;
} else {
break;
}
}
if (current.isFunctionTopScope()) {
break;
}
}
return null;
}
public void recordHoistedVarDeclaration(final VarNode varDecl, final Scope scope) {
assert !varDecl.isBlockScoped();
if (hoistedVarDeclarations == null) {
hoistedVarDeclarations = new ArrayList<>();
}
hoistedVarDeclarations.add(new AbstractMap.SimpleImmutableEntry<>(varDecl, scope));
}
public VarNode verifyHoistedVarDeclarations() {
if (!hasHoistedVarDeclarations()) {
return null;
}
for (Map.Entry<VarNode, Scope> entry : hoistedVarDeclarations) {
VarNode varDecl = entry.getKey();
Scope declScope = entry.getValue();
String varName = varDecl.getName().getName();
for (Scope current = declScope; current != this; current = current.getParent()) {
Symbol existing = current.getExistingSymbol(varName);
if (existing != null && existing.isBlockScoped()) {
if (existing.isCatchParameter()) {
continue;
}
return varDecl;
}
}
}
return null;
}
public boolean hasHoistedVarDeclarations() {
return hoistedVarDeclarations != null;
}
public void recordHoistableBlockFunctionDeclaration(final VarNode functionDeclaration, final Scope scope) {
assert functionDeclaration.isFunctionDeclaration() && functionDeclaration.isBlockScoped();
if (hoistableBlockFunctionDeclarations == null) {
hoistableBlockFunctionDeclarations = new ArrayList<>();
}
hoistableBlockFunctionDeclarations.add(new AbstractMap.SimpleImmutableEntry<>(functionDeclaration, scope));
}
public void declareHoistedBlockFunctionDeclarations() {
if (hoistableBlockFunctionDeclarations == null) {
return;
}
next: for (Map.Entry<VarNode, Scope> entry : hoistableBlockFunctionDeclarations) {
VarNode functionDecl = entry.getKey();
Scope functionDeclScope = entry.getValue();
String varName = functionDecl.getName().getName();
for (Scope current = functionDeclScope.getParent(); current != null; current = current.getParent()) {
Symbol existing = current.getExistingSymbol(varName);
if (existing != null && (existing.isBlockScoped() && !existing.isCatchParameter())) {
continue next;
}
if (current.isFunctionBodyScope()) {
break;
}
}
if (getExistingSymbol(varName) == null) {
putSymbol(new Symbol(varName, Symbol.IS_VAR | (isGlobalScope() ? Symbol.IS_GLOBAL : 0)));
}
functionDeclScope.getExistingSymbol(varName).setHoistedBlockFunctionDeclaration();
}
}
public boolean addPrivateName(String name, int symbolFlags) {
assert isClassScope();
if (hasSymbol(name)) {
assert getExistingSymbol(name).isPrivateName();
return false;
} else {
putSymbol(new Symbol(name, Symbol.IS_CONST | Symbol.IS_PRIVATE_NAME | Symbol.HAS_BEEN_DECLARED | symbolFlags));
return true;
}
}
public boolean findPrivateName(String name) {
for (Scope current = this; current != null; current = current.parent) {
if (current.hasSymbol(name)) {
return true;
}
}
return false;
}
public boolean isBlockScope() {
return (type & BLOCK_SCOPE) != 0;
}
public boolean isFunctionBodyScope() {
return (type & FUNCTION_BODY_SCOPE) != 0;
}
public boolean isFunctionParameterScope() {
return (type & FUNCTION_PARAMETER_SCOPE) != 0;
}
public boolean isCatchParameterScope() {
return (type & CATCH_PARAMETER_SCOPE) != 0;
}
public boolean isGlobalScope() {
return (type & GLOBAL_SCOPE) != 0;
}
public boolean isModuleScope() {
return (type & MODULE_SCOPE) != 0;
}
public boolean isFunctionTopScope() {
return (type & FUNCTION_TOP_SCOPE) != 0;
}
public boolean isSwitchBlockScope() {
return (type & SWITCH_BLOCK_SCOPE) != 0;
}
public boolean isClassScope() {
return (type & CLASS_SCOPE) != 0;
}
public boolean isEvalScope() {
return (type & EVAL_SCOPE) != 0;
}
public boolean inFunction() {
return (flags & IN_FUNCTION) != 0;
}
public boolean inMethod() {
return (flags & IN_METHOD) != 0;
}
public boolean inDerivedConstructor() {
return (flags & IN_DERIVED_CONSTRUCTOR) != 0;
}
public boolean inClassFieldInitializer() {
return (flags & IS_CLASS_FIELD_INITIALIZER) != 0;
}
public void close() {
if (closed) {
return;
}
if (hoistableBlockFunctionDeclarations != null) {
declareHoistedBlockFunctionDeclarations();
}
closed = true;
}
@Override
public String toString() {
StringJoiner names = new StringJoiner(",", "(", ")");
for (String name : symbols.getKeys()) {
names.add(name);
}
return "[" + getScopeKindName() + "Scope" + names + (parent == null ? "" : ", " + parent + "") + "]";
}
private String getScopeKindName() {
if (isGlobalScope()) {
return "Global";
} else if (isModuleScope()) {
return "Module";
} else if (isFunctionBodyScope()) {
return "Var";
} else if (isFunctionParameterScope()) {
return "Param";
} else if (isCatchParameterScope()) {
return "Catch";
} else if (isSwitchBlockScope()) {
return "Switch";
} else if (isClassScope()) {
return "Class";
} else if (isEvalScope()) {
return "Eval";
}
return "";
}
}