/* --------------------------------------------------------- *
* __________ D E L T A S C R I P T *
* (_________() *
* / === / - A fast, dynamic scripting language *
* | == | - Version 4.13.11.0 *
* / === / - Developed by Adam R. Nelson *
* | = = | - 2011-2013 *
* / === / - Distributed under GNU LGPL v3 *
* (________() - http://github.com/ar-nelson/deltascript *
* *
* --------------------------------------------------------- */
package com.sector91.delta.script.objects;
import static com.sector91.delta.script.DScriptErr.*;
import static com.sector91.delta.script.objects.DS_Tag.tag;
import static com.sector91.util.StringTemplate.$;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import com.sector91.delta.script.DScriptContext;
import com.sector91.delta.script.DScriptErr;
import com.sector91.delta.script.DeltaScript;
import com.sector91.delta.script.Operator;
import com.sector91.delta.script.annotations.DSInaccessible;
import com.sector91.delta.script.annotations.DSType;
import com.sector91.delta.script.objects.reflect.DS_JavaClass;
import com.sector91.delta.script.objects.reflect.DS_JavaClassMethod;
import com.sector91.delta.script.objects.reflect.DS_JavaInstanceMethod;
import com.sector91.util.A;
import com.sector91.util.NestedPrintable;
/**
* <p>A collection of named {@link DS_Object}s, which provides a scope for
* DeltaScript scripts to run in.</p>
*
* <p>A scope can have <i>parent</i> and <i>child</i> scopes, and can access and
* overwrite variables defined in any of its parent scopes. Child scopes can be
* created with the {@link #createSubscope()} method.</p>
*
* <p>Scopes are also DeltaScript objects (they implement {@link DS_Object}),
* and can be created with the {@code scope} keyword.</p>
*
* <p>Since version 3.12.2.0, scopes use {@link DS_Tag}s as keys for faster
* lookup. Most of the time, these tags are created and cached when a script
* is compiled. Internally, scopes are not necessarily represented by one data
* structure; the implementation used could be anything from a hash map to a
* linked list depending on "expected size." For best performance, write scripts
* with lots of small nested scopes.</p>
*
* @author Adam R. Nelson
* @version 4.13.11.0
*/
@DSType("Scope")
public abstract class DS_Scope implements DS_Indexable,
Cloneable,
NestedPrintable
{
public static final String TYPE_NAME = "Scope";
@DSInaccessible
public static final DS_JavaClass DSCLASS = DS_JavaClass.fromClass(
DS_Scope.class);
// Error Tags
// ----------------------------------------------------
private static final DS_Tag
T_SCOPE = tag(TYPE_NAME);
// Special Variable Tags
// ----------------------------------------------------
// Built-in special vars:
@DSInaccessible public static final DS_Tag S_STDLIB = tag("$StdLib");
@DSInaccessible public static final DS_Tag S_SCOPE = tag("$scope");
@DSInaccessible public static final DS_Tag S_PARENT = tag("$parent");
@DSInaccessible public static final DS_Tag S_ANCESTOR = tag("$ancestor");
@DSInaccessible public static final DS_Tag S_KEYS = tag("$keys");
@DSInaccessible public static final DS_Tag S_VALUES = tag("$values");
@DSInaccessible public static final DS_Tag S_VARS = tag("$vars");
@DSInaccessible public static final DS_Tag S_ISDEF = tag("$defined");
@DSInaccessible public static final DS_Tag S_CLONE = tag("$clone");
@DSInaccessible public static final DS_Tag S_ROOT = tag("$root");
@DSInaccessible public static final DS_Tag S_GET = tag("$get");
@DSInaccessible public static final DS_Tag S_SET = tag("$set");
// Assignable special vars:
@DSInaccessible public static final DS_Tag S_HASH = tag("$hash");
@DSInaccessible public static final DS_Tag S_EQUALS = tag("$equals");
@DSInaccessible public static final DS_Tag S_COMPARE = tag("$compare");
@DSInaccessible public static final DS_Tag S_STR = tag("$str");
@DSInaccessible public static final DS_Tag S_ADD = tag("$add");
@DSInaccessible public static final DS_Tag S_SUBTRACT = tag("$subtract");
@DSInaccessible public static final DS_Tag S_MULTIPLY = tag("$multiply");
@DSInaccessible public static final DS_Tag S_DIVIDE = tag("$divide");
@DSInaccessible public static final DS_Tag S_MODULUS = tag("$modulus");
@DSInaccessible public static final DS_Tag S_EXPONENT = tag("$exponent");
@DSInaccessible public static final DS_Tag S_AND = tag("$and");
@DSInaccessible public static final DS_Tag S_OR = tag("$or");
@DSInaccessible public static final DS_Tag S_IN = tag("$in");
@DSInaccessible public static final DS_Tag S_TO = tag("$to");
@DSInaccessible public static final DS_Tag S_THROUGH = tag("$through");
@DSInaccessible public static final DS_Tag S_BY = tag("$by");
@DSInaccessible public static final DS_Tag S_CROSS = tag("$X");
@DSInaccessible public static final DS_Tag S_RANDOM = tag("$random");
@DSInaccessible public static final DS_Tag S_ABSOLUTE = tag("$absolute");
@DSInaccessible public static final DS_Tag S_NEGATE = tag("$negate");
@DSInaccessible public static final DS_Tag S_NOT = tag("$not");
/**
* <p>The set of special variable names (variable names starting with
* {@code $}) that can be assigned to, and that are therefore stored in the
* scope as normal variables. DeltaScript instructions that access these
* values are changed from calls to {@link #getSpecial(DS_Tag)} to calls to
* {@link #getLocal(DS_Tag)} at compile time.</p>
*
* <p>These variables represent "special functions" that are used by
* {@code DS_Scope} methods such as {@link #equals(DS_Object)} and {@link
* #operator(Operator, DS_Object)}. They can be used to imitate special Java
* functions such as {@code equals} and {@code hashCode}, and they are used
* in operator overloading.</p>
*/
@DSInaccessible public static final Set<DS_Tag> MODIFIABLE_SPECIAL_VARS =
Collections.unmodifiableSet(A.setOf(
S_HASH, S_EQUALS, S_COMPARE, S_STR, S_ADD, S_SUBTRACT, S_MULTIPLY,
S_DIVIDE, S_MODULUS, S_EXPONENT, S_AND, S_OR, S_IN, S_TO, S_THROUGH,
S_BY, S_CROSS, S_RANDOM, S_ABSOLUTE, S_NEGATE, S_NOT));
// Cached Special Methods
// ----------------------------------------------------
private static final DS_JavaClassMethod
M_GETANCESTOR, M_CLONE, M_GETLOCAL, M_SETLOCAL, M_DEFINEDLOCAL;
static {
try
{
M_GETANCESTOR = new DS_JavaClassMethod(DSCLASS, "getAncestor",
DS_Scope.class.getDeclaredMethod("getAncestor", int.class));
M_CLONE = new DS_JavaClassMethod(DSCLASS, "clone",
DS_Scope.class.getDeclaredMethod("clone"));
M_GETLOCAL = new DS_JavaClassMethod(DSCLASS, "getLocal",
DS_Scope.class.getDeclaredMethod("getLocal", DS_Tag.class));
M_SETLOCAL = new DS_JavaClassMethod(DSCLASS, "setLocal",
DS_Scope.class.getDeclaredMethod("setLocal", DS_Tag.class,
DS_Object.class));
M_DEFINEDLOCAL = new DS_JavaClassMethod(DSCLASS, "definedLocal",
DS_Scope.class.getDeclaredMethod("definedLocal", DS_Tag.class));
}
catch (Exception ex)
{
throw new IllegalStateException("Failed to load deafult" +
" DeltaScript scope methods.", ex);
}
}
// Instance Variables
// ----------------------------------------------------
private final DS_Scope parent;
private Identity id;
// Constructors
// ----------------------------------------------------
/** <p>Creates a root scope, with no parent.</p> */
@DSInaccessible
protected DS_Scope()
{
this.parent = null;
this.id = DSCLASS;
}
/**
* <p>Creates a new scope, with another scope as its parent. The new scope
* will appear to contain all of the parent scope's members as well as its
* own.</p>
*
* @param parent The parent of the new scope.
*/
@DSInaccessible
public DS_Scope(DS_Scope parent)
{
this.parent = parent;
this.id = DSCLASS;
if (parent == null)
throw new IllegalArgumentException(
"A scope cannot have a null parent.");
}
// Script Scope Methods
// ----------------------------------------------------
/**
* <p>Returns the parent of this scope, if any. Will return null if this is
* a root scope.</p>
* @see #isRootScope()
* @see #hasAncestor(DS_Object)
*/
public DS_Scope getParent()
{return parent;}
/**
* <p>Returns the ancestor of this scope at the given depth, or null if no
* such ancestor exists. If {@code depth} is <= 0, will return this
* scope.</p>
*
* @param depth The number of levels up to search for an ancestor.
* @return The ancestor {@code depth} levels above this scope, or
* {@code null} if no such ancestor exists.
*/
public DS_Scope getAncestor(int depth)
{
if (depth <= 0) return this;
else if (depth == 1) return getParent();
else if (!isRootScope()) return parent.getAncestor(depth-1);
else return null;
}
public DS_RootScope getRootScope()
{
if (parent.isRootScope())
return (DS_RootScope)parent;
else
return parent.getRootScope();
}
/**
* <p>Returns true if this scope is a root scope (that is, if this scope
* does not have a parent).</p>
* @see #getParent()
*/
public boolean isRootScope()
{return false;}
/**
* <p>Returns true if one of this scope's ancestors is the given scope.</p>
* @param ancestor The scope that may be this scope's ancestor.
* @see #getParent()
*/
public boolean hasAncestor(DS_Object ancestor)
{
if (isRootScope()) return false;
else return parent.recursiveHasAncestor(ancestor);
}
private boolean recursiveHasAncestor(DS_Object ancestor)
{
if (this == ancestor) return true;
else if (isRootScope()) return false;
else return parent.recursiveHasAncestor(ancestor);
}
public DS_Scope createSubscope()
{return createSubscope(DeltaScript.SCOPE_HASH_BUCKETS);}
/**
* <p>Creates and returns a <i>subscope</i> (child scope) of this scope,
* which will appear to contain all of this scope's members as well as its
* own.</p>
*/
public DS_Scope createSubscope(int expectedSize)
{
if (expectedSize <= DS_LinkedScope.MAX_SIZE)
return new DS_LinkedScope(this);
else
return new DS_HashedScope(this);
}
@Override public DS_Scope clone()
{
DS_Scope newScope = parent.createSubscope(size());
for (ScopeEntry n : getEntries())
newScope.addEntryDirectly(n.cloneTo(this, newScope));
newScope.id = new IdentityChain(this, id);
return newScope;
}
public void include(DS_Scope other) throws DScriptErr
{
for (ScopeEntry n : other.getEntries())
{
if (definedLocal(n.tag))
continue;
DS_Object value = n.getValue();
if (value instanceof DS_Function)
value = ((DS_Function)value).bind(this);
setLocal(n.tag, value);
}
addIdentity(other);
}
public boolean is(DS_Object obj)
{
if (obj == this)
return true;
return id.is(obj);
}
public DScriptContext getContext()
{return getRootScope().getContext();}
// Containment
// ----------------------------------------------------
/**
* <p>Returns {@code true} if the given identifier is defined in this
* scope or any of its parent scopes.</p>
*
* @param tag The identifier tag to search for.
* @return {@code true} if the given identifier is defined in this
* scope or any of its parent scopes, {@code false} otherwise.
* @see #definedLocal(DS_Tag)
*/
public boolean defined(final DS_Tag tag)
{
return definedLocal(tag) ||
(parent != null && parent.defined(tag));
}
/**
* <p>Returns {@code true} if the given identifier is defined in this
* scope. Does not search parent scopes.</p>
*
* @param tag The identifier tag to search for.
* @return {@code true} if the given identifier is defined in this
* scope, {@code false} otherwise.
* @see #defined(DS_Tag)
*/
public abstract boolean definedLocal(final DS_Tag tag);
public boolean containsValue(DS_Object value) throws DScriptErr
{
return containsValueLocal(value) ||
(parent != null && parent.containsValue(value));
}
public boolean containsValueLocal(DS_Object value) throws DScriptErr
{return getValues().contains(value);}
// Getters
// ----------------------------------------------------
/**
* <p>Retrieves an item from this scope or one of its parent scopes,
* or {@code null} if the item is not present.</p>
*
* @param tag The identifier to search for.
* @return The object bound to the given identifier.
* @see #getLocal(DS_Tag)
*/
public DS_Object get(final DS_Tag tag) throws DScriptErr
{
DS_Object obj = getLocal(tag);
if (obj == null && parent != null)
obj = parent.get(tag);
return obj;
}
/**
* <p>Retrieves an item from this scope, or {@code null} if the item
* is not present. Does not search parent scopes.</p>
*
* @param tag The identifier to search for.
* @return The object bound to the given identifier, or {@code null}
* if the given identifier is not defined in this scope.
* @since 3.12.2.0
* @see #get(DS_Tag)
*/
public abstract DS_Object getLocal(final DS_Tag tag)
throws DScriptErr;
/**
* <p>Retrieves a predefined <em>special variable</em> from this scope,
* throwing an exception if the given identifier is not a valid special
* variable. Special variables start with {@code $}, and are not
* actually contained in the scope (with certain exceptions; see {@link
* #MODIFIABLE_SPECIAL_VARS}). They represent various metadata about the
* scope, or special functions such as {@code $clone()}.</p>
*
* @param tag The identifier representing the special variable.
* @return The value of the special variable.
* @since 3.12.2.0
*/
public DS_Object getSpecial(DS_Tag tag) throws DScriptErr
{
if (tag == S_SCOPE)
return this;
else if (tag == S_PARENT)
return isRootScope()?DS_Blank.BLANK:parent;
else if (tag == S_ANCESTOR)
return new DS_JavaInstanceMethod(M_GETANCESTOR, this);
else if (tag == S_STDLIB)
return DeltaScript.STDLIB;
else if (tag == S_KEYS)
return getKeys();
else if (tag == S_VALUES)
return getValues();
else if (tag == S_VARS)
return _vars();
else if (tag == S_ISDEF)
return new DS_JavaInstanceMethod(M_DEFINEDLOCAL, this);
else if (tag == S_CLONE)
return new DS_JavaInstanceMethod(M_CLONE, this);
else if (tag == S_ROOT)
return getRootScope();
else if (tag == S_GET)
return new DS_JavaInstanceMethod(M_GETLOCAL, this);
else if (tag == S_SET)
return new DS_JavaInstanceMethod(M_SETLOCAL, this);
else if (MODIFIABLE_SPECIAL_VARS.contains(tag))
return getLocal(tag);
else
throw new DScriptErr("Invalid special variable: " + tag.str,
DScriptErr.T_UNDEFINED);
}
// Setters - Bindings
// ----------------------------------------------------
public synchronized void set(DS_Tag tag, DS_Object value)
throws DScriptErr
{
if (!definedLocal(tag) && parent != null && parent.defined(tag))
parent.set(tag, value);
else
setLocal(tag, value);
}
public abstract void setLocal(DS_Tag tag, DS_Object value)
throws DScriptErr;
public synchronized void createDynamicField(DS_Tag tag,
DS_Callable callable)
{
delete(tag);
addEntryDirectly(new FieldEntry(tag, callable));
}
protected abstract void addEntryDirectly(ScopeEntry entry);
/**
* <p>Wraps a Java object and binds it to a DeltaScript identifier in this
* scope.</p>
*
* @param tag The identifier tag to bind this value to.
* @param o A Java object, which will be boxed with
* {@link DeltaScript#box(Object)} and bound to the identifier.
* @see #set(DS_Tag, DS_Object)
*/
public void setJavaObject(DS_Tag tag, Object o) throws DScriptErr
{set(tag, DeltaScript.box(o));}
/**
* <p>Wraps a Java object and binds it to a DeltaScript identifier in this
* scope.</p>
*
* <p>Note that this version of the method creates a new {@link DS_Tag}
* from the given string, and is therefore slow. If performance is an
* issue, use {@link #setJavaObject(DS_Tag, Object)} instead.</p>
*
* @param identifier A String representation of the identifier, which will
* be converted to a {@link DS_Tag}.
* @param o A Java object, which will be boxed with
* {@link DeltaScript#box(Object)} and bound to the identifier.
* @since 3.12.2.0
* @see #set(DS_Tag, DS_Object)
*/
public void setJavaObject(String identifier, Object o)
throws DScriptErr
{set(tag(identifier), DeltaScript.box(o));}
public void setAll(Map<? extends DS_Tag, ? extends DS_Object> m)
throws DScriptErr
{
for (Map.Entry<? extends DS_Tag, ? extends DS_Object> entry :
m.entrySet()) set(entry.getKey(), entry.getValue());
}
// Other API Methods
// ----------------------------------------------------
/**
* <p>Deletes a binding from this scope. The identifier is freed and can
* later be bound to a different value.</p>
*
* @param tag The identifier to delete from this scope.
*/
public abstract void delete(DS_Tag tag);
/**
* <p>Deletes all values stored in this scope. This does not affect values
* stored in any parent scopes.</p>
*/
public abstract void clear();
/**
* <p>Returns the number of identifiers bound in this scope.</p>
*
* @return The number of identifiers bound in this scope.
*/
public abstract int size();
/**
* <p>Returns {@code true} if this scope contains no members.</p>
*
* @return {@code true} if this scope contains no members.
*/
public abstract boolean isEmpty();
public DS_Set getKeys()
{
Set<ScopeEntry> nodes = getEntries();
DS_Set keys = new DS_Set();
for (ScopeEntry n : nodes)
keys.add(n.tag);
return keys;
}
public DS_Set getValues() throws DScriptErr
{
Set<ScopeEntry> nodes = getEntries();
DS_Set values = new DS_Set();
for (ScopeEntry n : nodes)
values.add(n.getValue());
return values;
}
// $ Methods
// ----------------------------------------------------
protected DS_Array _vars() throws DScriptErr
{
Set<ScopeEntry> nodes = getEntries();
DS_Array[] arr = new DS_Array[nodes.size()];
int i = 0;
for (ScopeEntry n : nodes)
{
arr[i] = new DS_Array(n.tag, n.getValue());
++i;
}
return new DS_Array(arr);
}
protected void addIdentity(DS_Object typeObj)
{id = new IdentityChain(typeObj, id);}
// DS_Object Methods
// ----------------------------------------------------
public Object unbox()
{return this;}
public String getTypeName()
{return TYPE_NAME;}
public Set<DS_Tag> getMembers()
{
Set<ScopeEntry> nodes = getEntries();
Set<DS_Tag> members = new HashSet<DS_Tag>();
for (ScopeEntry node : nodes) members.add(node.tag);
return members;
}
public DS_Object dotGet(DS_Tag prop) throws DScriptErr
{
final DS_Object value = getLocal(prop);
if (value == null)
throw DScriptErr.undefined(prop.str);
return value;
}
public void dotSet(DS_Tag prop, DS_Object value)
throws DScriptErr
{setLocal(prop, value);}
@Override public String toString()
{return toStringNested(1);}
public String toStringNested(int indentLevel)
{
if (indentLevel >= 10)
return "(Exceeded max nesting level...)";
StringBuffer out = new StringBuffer("(" + getTypeName() + ":");
Set<ScopeEntry> nodes = getEntries();
for (ScopeEntry n : nodes)
{
out.append('\n');
StringBuffer def = new StringBuffer();
for (int i=0; i<indentLevel; i++)
def.append(" ");
def.append(n.tag.str);
def.append(" = ");
out.append(def);
if (n instanceof KeyValueEntry)
{
final KeyValueEntry kv = (KeyValueEntry)n;
if (kv.value instanceof NestedPrintable)
{
if (kv.value == this)
out.append("(Recursive self-reference)");
else
out.append(((NestedPrintable)kv.value).toStringNested(
indentLevel+1));
}
else
{
StringBuffer indent = new StringBuffer();
for (int i=0; i<def.length(); i++) indent.append(' ');
out.append(kv.value.toString().replaceAll("\n",
"\n"+indent));
}
}
else
out.append("(Dynamic field)");
}
if (!nodes.isEmpty())
{
out.append('\n');
for (int i=0; i<indentLevel-1; i++)
out.append(" ");
}
out.append(")");
return out.toString();
}
public boolean equals(DS_Object other)
{
if (definedLocal(S_EQUALS))
{
try
{
final DS_Object equals = getLocal(S_EQUALS);
if (equals instanceof DS_Callable)
return ((DS_Callable)equals).call(other).booleanValue();
else
throw new IllegalStateException(S_EQUALS.str + " must be" +
" callable, but is a non-callable " +
equals.getTypeName());
}
catch (DScriptErr ex)
{throw new RuntimeException(ex);}
}
else return this == other;
}
@Override public int hashCode()
{
if (definedLocal(S_HASH))
{
try
{
final DS_Object hash = getLocal(S_HASH);
if (hash instanceof DS_Callable)
return DeltaScript.intValue(((DS_Callable)hash).call());
else
throw new IllegalStateException(S_HASH.str + " must be" +
" callable, but is a non-callable "+hash.getTypeName());
}
catch (DScriptErr ex)
{throw new RuntimeException(ex);}
}
else return super.hashCode();
}
public boolean booleanValue()
{return !isEmpty();}
@SuppressWarnings("incomplete-switch")
public DS_Object operator(Operator op, DS_Object other)
throws DScriptErr
{
DS_Object callable = null;
switch (op)
{
case ADD: callable = getLocal(S_ADD); break;
case SUBTRACT: callable = getLocal(S_SUBTRACT); break;
case MULTIPLY: callable = getLocal(S_MULTIPLY); break;
case DIVIDE: callable = getLocal(S_DIVIDE); break;
case MODULUS: callable = getLocal(S_MODULUS); break;
case EXPONENT: callable = getLocal(S_EXPONENT); break;
case BIT_AND: callable = getLocal(S_AND); break;
case BIT_OR: callable = getLocal(S_OR); break;
case IN: callable = getLocal(S_IN); break;
case RANGE_EXC: callable = getLocal(S_TO); break;
case RANGE_INC: callable = getLocal(S_THROUGH); break;
case RANGE_STEP: callable = getLocal(S_BY); break;
case CROSS: callable = getLocal(S_CROSS); break;
case RANDOM: callable = getLocal(S_RANDOM); break;
case ABSOLUTE: callable = getLocal(S_ABSOLUTE); break;
case UNARY_MINUS: callable = getLocal(S_NEGATE); break;
}
if (callable == null)
{
if (other == null)
throw DScriptErr.invalidOperation(op.str, this);
else
throw DScriptErr.invalidOperation(op.str, this, other);
}
else if (!(callable instanceof DS_Callable))
{
throw new DScriptErr($("Scope operator functions must be callable,"+
" but encountered a non-callable {} for operator {}.",
callable.getTypeName(), op.str),
T_SCOPE, T_NOT_CALLABLE);
}
else
{
if (other == null)
return ((DS_Callable)callable).call();
else
return ((DS_Callable)callable).call(other);
}
}
public int compare(DS_Object o) throws DScriptErr
{
if (definedLocal(S_COMPARE))
{
final DS_Object compare = getLocal(S_COMPARE);
if (compare instanceof DS_Callable)
return DeltaScript.intValue(((DS_Callable)compare).call(o));
else
throw new IllegalStateException(S_COMPARE.str + " must be" +
" callable, but is a non-callable " + compare.getTypeName());
}
else throw DScriptErr.invalidCompare(this, o);
}
public DS_Object getIndex(DS_Object index) throws DScriptErr
{
if (index instanceof DS_Tag)
return getLocal((DS_Tag)index);
else
throw new DScriptErr($("Only a {} can be used as an index for a" +
" scope. Got index of type {} instead.",
DS_Tag.TYPE_NAME, index.getTypeName()),
T_SCOPE, T_INDEX, T_INVALID_TYPE);
}
public DS_Object setIndex(DS_Object index, DS_Object value)
throws DScriptErr
{
if (index instanceof DS_Tag)
setLocal((DS_Tag)index, value);
else
throw new DScriptErr($("Only a {} can be used as an index for a" +
" scope. Got index of type {} instead.",
DS_Tag.TYPE_NAME, index.getTypeName()),
T_SCOPE, T_INDEXSET, T_INVALID_TYPE);
return null;
}
// Iteration
// ----------------------------------------------------
public abstract Set<ScopeEntry> getEntries();
public static abstract class ScopeEntry
{
final DS_Tag tag;
ScopeEntry next;
ScopeEntry(DS_Tag tag)
{this.tag = tag;}
public DS_Tag getKey()
{return tag;}
public abstract DS_Object getValue() throws DScriptErr;
public abstract DS_Object setValue(DS_Object value)
throws DScriptErr;
abstract ScopeEntry cloneTo(DS_Scope oldScope, DS_Scope newScope);
}
static class KeyValueEntry extends ScopeEntry
{
DS_Object value;
KeyValueEntry(DS_Tag tag, DS_Object value)
{
super(tag);
this.value = value;
}
@Override public DS_Object getValue()
{return value;}
@Override public DS_Object setValue(DS_Object value)
{
final DS_Object oldValue = this.value;
this.value = value;
return oldValue;
}
@Override KeyValueEntry cloneTo(DS_Scope oldScope, DS_Scope newScope)
{
DS_Object newValue = value;
if (newValue instanceof DS_Function &&
((DS_Function)newValue).getBoundScope() == oldScope)
newValue = ((DS_Function)newValue).bind(newScope);
return new KeyValueEntry(getKey(), newValue);
}
}
static class FieldEntry extends ScopeEntry
{
DS_Callable valueFunc;
FieldEntry(DS_Tag tag, DS_Callable valueFunc)
{
super(tag);
this.valueFunc = valueFunc;
}
@Override public DS_Object getValue() throws DScriptErr
{return valueFunc.call();}
@Override public DS_Object setValue(DS_Object value)
throws DScriptErr
{
throw new DScriptErr("Cannot modify the dynamic field '" +
tag.str + "'.");
}
@Override FieldEntry cloneTo(DS_Scope oldScope, DS_Scope newScope)
{
DS_Callable newFunc = valueFunc;
if (newFunc instanceof DS_Function &&
((DS_Function)newFunc).getBoundScope() == oldScope)
newFunc = ((DS_Function)newFunc).bind(newScope);
return new FieldEntry(getKey(), newFunc);
}
}
static final class IdentityChain implements Identity
{
final Identity car, cdr;
IdentityChain(Identity car, Identity cdr)
{
this.car = car;
this.cdr = cdr;
}
public boolean is(DS_Object typeObj)
{return car.is(typeObj) || cdr.is(typeObj);}
}
}
class DS_LinkedScope extends DS_Scope
{
static final int MAX_SIZE = 5;
private ScopeEntry first = null;
DS_LinkedScope()
{super();}
DS_LinkedScope(DS_Scope parent)
{super(parent);}
@Override public synchronized Set<ScopeEntry> getEntries()
{
final Set<ScopeEntry> entries = new HashSet<ScopeEntry>();
for (ScopeEntry e = first; e != null; e = e.next)
entries.add(e);
return entries;
}
@Override public boolean definedLocal(DS_Tag tag)
{
for (ScopeEntry e = first; e != null; e = e.next)
if (e.tag == tag)
return true;
return false;
}
@Override public DS_Object getLocal(DS_Tag tag) throws DScriptErr
{
for (ScopeEntry e = first; e != null; e = e.next)
if (e.tag == tag)
return e.getValue();
return null;
}
@Override synchronized public void setLocal(DS_Tag tag,DS_Object value)
throws DScriptErr
{
for (ScopeEntry e = first; e != null; e = e.next)
{
if (e.tag == tag)
{
e.setValue(value);
return;
}
}
final ScopeEntry entry = new KeyValueEntry(tag, value);
entry.next = first;
first = entry;
}
@Override protected void addEntryDirectly(ScopeEntry entry)
{
entry.next = first;
first = entry;
}
@Override public void delete(DS_Tag tag)
{
ScopeEntry last = null;
for (ScopeEntry e = first; e != null; e = e.next)
{
if (e.tag == tag)
{
if (last != null)
last.next = e.next;
else
first = e.next;
return;
}
last = e;
}
}
@Override public synchronized void clear()
{first = null;}
@Override public int size()
{
int size = 0;
for (ScopeEntry e = first; e != null; e = e.next)
size++;
return size;
}
@Override
public boolean isEmpty()
{return first == null;}
}
class DS_HashedScope extends DS_Scope
{
private final ScopeEntry[] buckets =
new ScopeEntry[DeltaScript.SCOPE_HASH_BUCKETS];
private int size;
DS_HashedScope()
{super();}
DS_HashedScope(DS_Scope parent)
{super(parent);}
@Override public synchronized Set<ScopeEntry> getEntries()
{
Set<ScopeEntry> entries = new HashSet<ScopeEntry>();
for (int i=0; i<buckets.length; i++)
{
ScopeEntry entry = buckets[i];
while (entry != null)
{
entries.add(entry);
entry = entry.next;
}
}
return entries;
}
@Override public boolean definedLocal(DS_Tag tag)
{
ScopeEntry entry = buckets[tag.getHashBucket()];
while (entry != null)
{
if (entry.tag == tag)
return true;
entry = entry.next;
}
return false;
}
@Override public DS_Object getLocal(final DS_Tag tag)
throws DScriptErr
{
ScopeEntry entry = buckets[tag.getHashBucket()];
while (entry != null)
{
if (entry.tag == tag)
return entry.getValue();
entry = entry.next;
}
return null;
}
@Override public synchronized void setLocal(DS_Tag tag,DS_Object value)
throws DScriptErr
{
ScopeEntry entry = buckets[tag.getHashBucket()];
if (entry == null)
{
buckets[tag.getHashBucket()] = new KeyValueEntry(tag, value);
size++;
}
else
{
if (entry.tag == tag)
{
entry.setValue(value);
return;
}
while (entry.next != null)
{
entry = entry.next;
if (entry.tag == tag)
{
entry.setValue(value);
return;
}
}
entry.next = new KeyValueEntry(tag, value);
size++;
}
}
@Override protected void addEntryDirectly(ScopeEntry entry)
{
final int bucket = entry.getKey().getHashBucket();
ScopeEntry existing = buckets[bucket];
entry.next = existing;
buckets[bucket] = entry;
}
@Override public synchronized void delete(DS_Tag tag)
{
ScopeEntry entry = buckets[tag.getHashBucket()];
if (entry == null)
return;
if (entry.tag == tag)
buckets[tag.getHashBucket()] = entry.next;
while (entry.next != null)
{
if (entry.next.tag == tag)
{
entry.next = entry.next.next;
return;
}
entry = entry.next;
}
}
@Override public synchronized void clear()
{
for (int i=0; i<buckets.length; i++)
buckets[i] = null;
size = 0;
}
@Override public int size()
{return size;}
@Override public boolean isEmpty()
{return size == 0;}
}