/**
* Copyright (c) 2012-2016 André Bargull
* Alle Rechte vorbehalten / All Rights Reserved. Use is subject to license terms.
*
* <https://github.com/anba/es6draft>
*/
package com.github.anba.es6draft.runtime;
import static com.github.anba.es6draft.runtime.internal.Errors.newReferenceError;
import static com.github.anba.es6draft.runtime.internal.Errors.newTypeError;
import java.util.Collections;
import java.util.Formatter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import com.github.anba.es6draft.runtime.internal.Messages;
import com.github.anba.es6draft.runtime.types.Reference;
import com.github.anba.es6draft.runtime.types.ScriptObject;
/**
* <h1>8 Executable Code and Execution Contexts</h1><br>
* <h2>8.1 Lexical Environments</h2><br>
* <h3>8.1.1 Environment Records</h3>
* <ul>
* <li>8.1.1.1 Declarative Environment Records
* </ul>
*/
public class DeclarativeEnvironmentRecord implements EnvironmentRecord {
public static abstract class Binding implements Cloneable {
final boolean mutable;
final boolean deletable;
final boolean strict;
Binding(boolean mutable, boolean deletable, boolean strict) {
this.mutable = mutable;
this.deletable = deletable;
this.strict = strict;
}
public final boolean isMutable() {
return mutable;
}
public final boolean isDeletable() {
return deletable;
}
public final boolean isStrict() {
return strict;
}
public abstract boolean isInitialized();
public abstract void initialize(Object value);
@Override
public abstract Binding clone();
public abstract void setValue(Object value);
public abstract Object getValue();
}
private static final class DirectBinding extends Binding {
private Object value;
DirectBinding(boolean mutable, boolean deletable, boolean strict) {
super(mutable, deletable, strict);
}
@Override
public DirectBinding clone() {
DirectBinding clone = new DirectBinding(mutable, deletable, strict);
clone.value = value;
return clone;
}
@Override
public String toString() {
return String.format("{mutable = %b, deletable = %b, strict = %b, value = %s}",
mutable, deletable, strict, Objects.toString(value, "<uninitialized>"));
}
@Override
public boolean isInitialized() {
return value != null;
}
@Override
public void initialize(Object value) {
assert this.value == null : "binding already initialized";
this.value = value;
}
@Override
public void setValue(Object newValue) {
assert newValue != null && this.value != null && mutable;
this.value = newValue;
}
@Override
public Object getValue() {
assert value != null;
return value;
}
}
private final ExecutionContext cx;
private final boolean catchEnvironment;
private final HashMap<String, Binding> bindings;
public DeclarativeEnvironmentRecord(ExecutionContext cx, boolean catchEnvironment) {
this.cx = cx;
this.catchEnvironment = catchEnvironment;
this.bindings = new HashMap<>();
}
DeclarativeEnvironmentRecord(DeclarativeEnvironmentRecord source) {
this.cx = source.cx;
this.catchEnvironment = source.catchEnvironment;
this.bindings = source.cloneBindings();
}
private HashMap<String, Binding> cloneBindings() {
HashMap<String, Binding> newBindings = new HashMap<>();
for (Map.Entry<String, Binding> entry : bindings.entrySet()) {
String name = entry.getKey();
Binding binding = entry.getValue();
assert binding.isInitialized() : "binding not initialized: " + name;
newBindings.put(name, binding.clone());
}
return newBindings;
}
protected final void createBinding(String name, Binding binding) {
bindings.put(name, binding);
}
protected final Binding getBinding(String name) {
return bindings.get(name);
}
protected final void removeBinding(String name) {
bindings.remove(name);
}
@Override
public String toString() {
return String.format("%s: {%n\tbindings=%s%n}", getClass().getSimpleName(),
bindingsToString());
}
/*package*/String bindingsToString() {
if (bindings.isEmpty()) {
return "{}";
}
try (Formatter f = new Formatter(new StringBuilder(), null)) {
f.format("{");
for (Iterator<Map.Entry<String, Binding>> iter = bindings.entrySet().iterator();;) {
Map.Entry<String, Binding> entry = iter.next();
f.format("%n\t\t%s=%s", entry.getKey(), entry.getValue());
if (!iter.hasNext())
break;
f.format(",");
}
f.format("%n\t}");
return f.toString();
}
}
@Override
public final Set<String> bindingNames() {
if (bindings.isEmpty()) {
return Collections.emptySet();
}
return Collections.unmodifiableSet(bindings.keySet());
}
@Override
public final Object getBindingValueOrNull(String name, boolean strict) {
Binding b = getBinding(name);
if (b == null) {
return null;
}
if (!b.isInitialized()) {
throw newReferenceError(cx, Messages.Key.UninitializedBinding, name);
}
return b.getValue();
}
@Override
public final Reference<DeclarativeEnvironmentRecord, String> getReferenceOrNull(String name,
boolean strict) {
Binding b = getBinding(name);
if (b == null) {
return null;
}
if (b.deletable) {
return new Reference.IdentifierReference<>(this, name, strict);
}
return new Reference.BindingReference(this, b, name, strict);
}
/**
* 8.1.1.1.1 HasBinding(N)
*/
@Override
public final boolean hasBinding(String name) {
/* step 1 (omitted) */
/* steps 2-3 */
return !bindings.isEmpty() && bindings.containsKey(name);
}
/**
* 8.1.1.1.2 CreateMutableBinding (N,D)
*/
@Override
public final void createMutableBinding(String name, boolean deletable) {
/* step 1 (omitted) */
/* step 2 */
assert !hasBinding(name) : "binding redeclaration: " + name;
/* steps 3-4 */
createBinding(name, new DirectBinding(true, deletable, false));
}
/**
* 8.1.1.1.3 CreateImmutableBinding (N, S)
*/
@Override
public final void createImmutableBinding(String name, boolean strict) {
/* step 1 (omitted) */
/* step 2 */
assert !hasBinding(name) : "binding redeclaration: " + name;
/* step 3 */
createBinding(name, new DirectBinding(false, false, strict));
/* step 4 (return) */
}
/**
* 8.1.1.1.4 InitializeBinding (N,V)
*/
@Override
public final void initializeBinding(String name, Object value) {
assert value != null;
Binding b = getBinding(name);
/* step 1 (omitted) */
/* step 2 */
assert b != null : "binding not found: " + name;
assert !b.isInitialized() : "binding already initialized: " + name;
/* steps 3-4 */
b.initialize(value);
/* step 5 (return) */
}
/**
* 8.1.1.1.5 SetMutableBinding (N,V,S)
*/
@Override
public final void setMutableBinding(String name, Object value, boolean strict) {
assert value != null;
Binding b = getBinding(name);
/* step 1 (omitted) */
/* step 2 */
if (b == null) {
if (strict) {
throw newReferenceError(cx, Messages.Key.UnresolvableReference, name);
}
createMutableBinding(name, true);
initializeBinding(name, value);
return;
}
/* steps 3-7 */
if (!b.isInitialized()) {
throw newReferenceError(cx, Messages.Key.UninitializedBinding, name);
} else if (b.mutable) {
b.setValue(value);
} else if (strict || b.isStrict()) {
throw newTypeError(cx, Messages.Key.ImmutableBinding, name);
}
}
/**
* 8.1.1.1.6 GetBindingValue(N,S)
*/
@Override
public final Object getBindingValue(String name, boolean strict) {
Binding b = getBinding(name);
/* step 1 (omitted) */
/* step 2 */
assert b != null : "binding not found: " + name;
/* step 3 */
if (!b.isInitialized()) {
throw newReferenceError(cx, Messages.Key.UninitializedBinding, name);
}
/* step 4 */
return b.getValue();
}
/**
* 8.1.1.1.7 DeleteBinding (N)
*/
@Override
public final boolean deleteBinding(String name) {
Binding b = getBinding(name);
/* step 1 (omitted) */
/* step 2 */
assert b != null;
/* step 3 */
if (!b.deletable) {
return false;
}
/* step 4 */
removeBinding(name);
/* step 5 */
return true;
}
/**
* 8.1.1.1.8 HasThisBinding ()
*/
@Override
public boolean hasThisBinding() {
/* step 1 */
return false;
}
/**
* -
*/
@Override
public Object getThisBinding(ExecutionContext cx) {
throw new IllegalStateException();
}
/**
* 8.1.1.1.9 HasSuperBinding ()
*/
@Override
public boolean hasSuperBinding() {
/* step 1 */
return false;
}
/**
* 8.1.1.1.10 WithBaseObject()
*/
@Override
public final ScriptObject withBaseObject() {
/* step 1 */
return null;
}
/**
* Returns {@code true} if this declarative environment was created for a catch clause.
*
* @return {@code true} if this environment record is the environment of a catch clause
*/
public final boolean isCatchEnvironment() {
return catchEnvironment;
}
}