/* --------------------------------------------------------- * * __________ 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;} }