move to JS interface
authorcrawshaw <crawshaw@ibex.org>
Wed, 5 Jan 2005 10:17:21 +0000 (10:17 +0000)
committercrawshaw <crawshaw@ibex.org>
Wed, 5 Jan 2005 10:17:21 +0000 (10:17 +0000)
darcs-hash:20050105101721-2eb37-a0a3bf4fa71f13b2025bf9bd6a241e1e160f1f8f.gz

15 files changed:
src/org/ibex/js/Interpreter.java
src/org/ibex/js/JS.java
src/org/ibex/js/JSArray.java
src/org/ibex/js/JSDate.java
src/org/ibex/js/JSExn.java
src/org/ibex/js/JSFunction.java
src/org/ibex/js/JSMath.java
src/org/ibex/js/JSPrimitive.java
src/org/ibex/js/JSRegexp.java
src/org/ibex/js/JSScope.java
src/org/ibex/js/JSString.java
src/org/ibex/js/Parser.java
src/org/ibex/js/Script.java [new file with mode: 0644]
src/org/ibex/js/Test.java
src/org/ibex/js/Trap.java

index efbe2a9..7bb8bbe 100644 (file)
@@ -8,7 +8,7 @@ import org.ibex.util.*;
 import java.util.*;
 
 /** Encapsulates a single JS interpreter (ie call stack) */
-class Interpreter implements ByteCodes, Tokens {
+class Interpreter implements ByteCodes, Tokens, Pausable {
     // Thread-Interpreter Mapping /////////////////////////////////////////////////////////////////////////
 
     static Interpreter current() { return (Interpreter)threadToInterpreter.get(Thread.currentThread()); }
@@ -23,7 +23,7 @@ class Interpreter implements ByteCodes, Tokens {
     final Stack stack = new Stack(); ///< the object stack
     int pc = 0;                   ///< the program counter
 
-    Interpreter(JSFunction f, boolean pauseable, JSArgs args) {
+    Interpreter(JSFunction f, boolean pauseable, JS[] args) {
         this.f = f;
         this.pausecount = pauseable ? 0 : -1;
         this.scope = f.parentScope;
@@ -35,7 +35,7 @@ class Interpreter implements ByteCodes, Tokens {
         }
     }
     
-    Interpreter(Trap t, JS val, boolean pauseOnPut) {
+    Interpreter(JS.Trap t, JS val, boolean pauseOnPut) {
         this.pausecount = -1;
         try {
             setupTrap(t,val,new TrapMarker(null,t,val,pauseOnPut));
@@ -43,14 +43,21 @@ class Interpreter implements ByteCodes, Tokens {
             throw new Error("should never happen");
         }
     }
-    
+
+    private boolean get = false;
+    // FIXME: split this stuff out into a Script instance control object
+    //        so it's possible to make JS either single or multi threaded.
     /** this is the only synchronization point we need in order to be threadsafe */
-    synchronized JS resume() throws JSExn {
-        if(f == null) throw new RuntimeException("function already finished");
-        if(scope == null) throw new RuntimeException("scope is null");
+    public synchronized Object run(Object o) throws JSExn {
+        if (f == null) throw new AlreadyRunningException("function already finished");
+        if (scope == null) throw new RuntimeException("scope is null");
+
         Thread t = Thread.currentThread();
         Interpreter old = (Interpreter)threadToInterpreter.get(t);
         threadToInterpreter.put(t, this);
+
+        if (get) stack.push(o);
+
         try {
             return run();
         } finally {
@@ -58,6 +65,16 @@ class Interpreter implements ByteCodes, Tokens {
             else threadToInterpreter.put(t, old);
         }
     }
+    
+    public void pause() throws NotPausableException {
+        if (pausecount == -1 || f == null) throw new NotPausableException();
+        pausecount++;
+        switch(f.op[pc]) {
+            case Tokens.RETURN: case ByteCodes.PUT: get = false; break;
+            case ByteCodes.GET: case ByteCodes.CALL: get = true; break;
+            default: throw new Error("should never happen");
+        }
+    }
 
     static int getLine() {
         Interpreter c = Interpreter.current();
@@ -124,20 +141,20 @@ class Interpreter implements ByteCodes, Tokens {
             }
 
             case PUSHKEYS: {
-                JS o = stack.peek();
+                JS o = (JS)stack.peek();
                 stack.push(o == null ? null : o.keys());
                 break;
             }
 
             case LOOP:
-                stack.push(new LoopMarker(pc, pc > 0 && f.op[pc - 1] == LABEL ? (String)f.arg[pc - 1] : (String)null, scope));
-                stack.push(JS.T);
+                stack.push(new LoopMarker(pc, (String)(pc > 0 && f.op[pc - 1] == LABEL ? f.arg[pc - 1] : null), scope));
+                stack.push(Script.T);
                 break;
 
             case BREAK:
             case CONTINUE:
                 while(!stack.empty()) {
-                    JS o = stack.pop();
+                    JS o = (JS)stack.pop();
                     if (o instanceof CallMarker) je("break or continue not within a loop");
                     if (o instanceof TryMarker) {
                         if(((TryMarker)o).finallyLoc < 0) continue; // no finally block, keep going
@@ -169,7 +186,7 @@ class Interpreter implements ByteCodes, Tokens {
             }
 
             case RETURN: {
-                JS retval = stack.pop();
+                JS retval = (JS)stack.pop();
                 while(!stack.empty()) {
                     Object o = stack.pop();
                     if (o instanceof TryMarker) {
@@ -185,19 +202,19 @@ class Interpreter implements ByteCodes, Tokens {
                             TrapMarker tm = (TrapMarker) o;
                             boolean cascade = tm.t.isWriteTrap() && !tm.cascadeHappened && !JS.toBoolean(retval);
                             if(cascade) {
-                                Trap t = tm.t.nextWriteTrap();
-                                if(t == null && tm.t.target instanceof JS.Clone) {
-                                    t = ((JS.Clone)tm.t.target).clonee.getTrap(tm.t.key);
-                                    if(t != null) t = t.writeTrap();
+                                JS.Trap t = tm.t.nextWrite();
+                                if(t == null && tm.t.target() instanceof JS.Clone) {
+                                    t = ((JS.Clone)tm.t.target()).clonee.getTrap(tm.t.key());
+                                    if(t != null && !t.isWriteTrap()) t = t.nextWrite();
                                 }
                                 if(t != null) {
                                     tm.t = t; // we reuse the old trap marker
-                                    setupTrap(t,tm.val,tm);
+                                    setupTrap(t, tm.val, tm);
                                     pc--; // we increment it on the next iter
                                     continue OUTER;
                                 } else {
                                     didTrapPut = true;
-                                    if(!tm.pauseOnPut) tm.t.target.put(tm.t.key,tm.val);
+                                    if(!tm.pauseOnPut) tm.t.target().put(tm.t.key(), tm.val);
                                 }
                             }
                         }
@@ -224,15 +241,15 @@ class Interpreter implements ByteCodes, Tokens {
                 CallMarker o = stack.findCall();
                 if(!(o instanceof TrapMarker)) throw new JSExn("tried to CASCADE while not in a trap");
                 TrapMarker tm = (TrapMarker) o;
-                JS key = tm.t.key;
-                JS target = tm.t.target;
+                JS key = tm.t.key();
+                JS target = tm.t.target();
                 if(tm.t.isWriteTrap() != write) throw new JSExn("tried to do a " + (write?"write":"read") + " cascade in a " + (write?"read":"write") + " trap");
-                Trap t = write ? tm.t.nextWriteTrap() : tm.t.nextReadTrap();
+                JS.Trap t = write ? tm.t.nextWrite() : tm.t.nextRead();
                 // FIXME: Doesn't handle multiple levels of clone's (probably can just make this a while loop)
                 if(t == null && target instanceof JS.Clone) {
                     target = ((JS.Clone)target).clonee;
                     t = target.getTrap(key);
-                    if(t != null) t = write ? t.writeTrap() : t.readTrap();
+                    if(t != null) t = write ? t.write() : t.read();
                 }
                 if(write) {
                     tm.cascadeHappened = true;
@@ -247,8 +264,8 @@ class Interpreter implements ByteCodes, Tokens {
                         target.put(key,val);
                     } else {
                         JS ret = target.get(key);
-                        if (ret == JS.METHOD) ret = new Stub(target, key);
-                        stack.push(ret);                        
+                        if (ret != null && ret instanceof JS.Method) ret = new Stub(target, key);
+                        stack.push(ret);
                     }
                     if (pausecount > initialPauseCount) { pc++; return null; }   // we were paused                    
                 }
@@ -256,19 +273,19 @@ class Interpreter implements ByteCodes, Tokens {
             }
                 
             case PUT: {
-                JS val = stack.pop();
-                JS key = stack.pop();
-                JS target = stack.peek();
-                if (target == null) throw je("tried to put " + JS.debugToString(val) + " to the " + JS.debugToString(key) + " property on the null value");
-                if (key == null) throw je("tried to assign \"" + JS.debugToString(val) + "\" to the null key");
+                JS val = (JS)stack.pop();
+                JS key = (JS)stack.pop();
+                JS target = (JS)stack.peek();
+                if (target == null) throw je("tried to put " + Script.str(val) + " to the " + Script.str(key) + " property on the null value");
+                if (key == null) throw je("tried to assign \"" + Script.str(val) + "\" to the null key");
                 
-                Trap t = target.getTrap(key);
-                if(t != null) t = t.writeTrap();
+                JS.Trap t = target.getTrap(key);
+                if(t != null) t = t.write();
                 
                 if(t == null && target instanceof JS.Clone) {
                     target = ((JS.Clone)target).clonee;
                     t = target.getTrap(key);
-                    if(t != null) t = t.writeTrap();
+                    if(t != null) t = t.nextWrite();
                 }
 
                 stack.push(val);
@@ -287,24 +304,24 @@ class Interpreter implements ByteCodes, Tokens {
             case GET_PRESERVE: {
                 JS target, key;
                 if (op == GET) {
-                    key = arg == null ? stack.pop() : (JS)arg;
-                    target = stack.pop();
+                    key = arg == null ? (JS)stack.pop() : (JS)arg;
+                    target = (JS)stack.pop();
                 } else {
-                    key = stack.pop();
-                    target = stack.peek();
+                    key = (JS)stack.pop();
+                    target = (JS)stack.peek();
                     stack.push(key);
                 }
                 JS ret = null;
                 if (key == null) throw je("tried to get the null key from " + JS.debugToString(target));
                 if (target == null) throw je("tried to get property \"" + JS.debugToString(key) + "\" from the null object");
                 
-                Trap t = target.getTrap(key);
-                if(t != null) t = t.readTrap();
+                JS.Trap t = target.getTrap(key);
+                if(t != null) t = t.read();
                 
                 if(t == null && target instanceof JS.Clone) {
                     target = ((JS.Clone)target).clonee;
                     t = target.getTrap(key);
-                    if(t != null) t = t.readTrap();
+                    if(t != null) t = t.nextRead();
                 }
                 
                 if(t != null) {
@@ -313,33 +330,28 @@ class Interpreter implements ByteCodes, Tokens {
                 } else {
                     ret = target.get(key);
                     if (pausecount > initialPauseCount) { pc++; return null; }   // we were paused
-                    if (ret == JS.METHOD) ret = new Stub(target, key);
+
+                    if (ret != null && ret instanceof JS.Method) ret = new Stub(target, key);
                     stack.push(ret);
                 }
                 break;
             }
             
             case CALL: case CALLMETHOD: {
-                int numArgs = JS.toInt((JS)arg);
-                
-                JS[] rest = numArgs > 3 ? new JS[numArgs - 3] : null;
-                for(int i=numArgs - 1; i>2; i--) rest[i-3] = stack.pop();
-                JS a2 = numArgs <= 2 ? null : stack.pop();
-                JS a1 = numArgs <= 1 ? null : stack.pop();
-                JS a0 = numArgs <= 0 ? null : stack.pop();
-                
+                JS[] jsargs = (JS[])arg;
+
                 JS method = null;
                 JS ret = null;
-                JS object = stack.pop();
+                JS object = (JS)stack.pop();
 
                 if (op == CALLMETHOD) {
-                    if (object == JS.METHOD) {
-                        method = stack.pop();
-                        object = stack.pop();
-                    } else if (object == null) {
-                        method = stack.pop();
-                        object = stack.pop();
-                        throw new JSExn("function '"+JS.debugToString(method)+"' not found in " + object.getClass().getName());
+                    if (object == null) {
+                        method = (JS)stack.pop();
+                        object = (JS)stack.pop();
+                        throw new JSExn("function '"+Script.str(method)+"' not found in " + object.getClass().getName());
+                    } else if (object instanceof JS.Method) {
+                        method = (JS)stack.pop();
+                        object = (JS)stack.pop();
                     } else {
                         stack.pop();
                         stack.pop();
@@ -348,14 +360,14 @@ class Interpreter implements ByteCodes, Tokens {
 
                 if (object instanceof JSFunction) {
                     stack.push(new CallMarker(this));
-                    stack.push(new JSArgs(a0,a1,a2,rest,numArgs,object));
+                    stack.push(jsargs);
                     f = (JSFunction)object;
                     scope = f.parentScope;
                     pc = -1;
                     break;
                 } else {
                     JS c = (JS)object;
-                    ret = method == null ? c.call(a0, a1, a2, rest, numArgs) : c.callMethod(method, a0, a1, a2, rest, numArgs);
+                    ret = method == null ? c.call(jsargs) : c.call(method, jsargs);
                 }
                 
                 if (pausecount > initialPauseCount) { pc++; return null; }
@@ -364,21 +376,21 @@ class Interpreter implements ByteCodes, Tokens {
             }
 
             case THROW:
-                throw new JSExn(stack.pop(), this);
+                throw new JSExn((JS)stack.pop(), this);
 
                 /* FIXME GRAMMAR
             case MAKE_GRAMMAR: {
                 final Grammar r = (Grammar)arg;
                 final JSScope final_scope = scope;
                 Grammar r2 = new Grammar() {
-                        public int match(String s, int start, Hash v, JSScope scope) throws JSExn {
+                        public int match(String s, int start, Map v, JSScope scope) throws JSExn {
                             return r.match(s, start, v, final_scope);
                         }
-                        public int matchAndWrite(String s, int start, Hash v, JSScope scope, String key) throws JSExn {
+                        public int matchAndWrite(String s, int start, Map v, JSScope scope, String key) throws JSExn {
                             return r.matchAndWrite(s, start, v, final_scope, key);
                         }
                         public Object call(Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn {
-                            Hash v = new Hash();
+                            Map v = new Map();
                             r.matchAndWrite((String)a0, 0, v, final_scope, "foo");
                             return v.get("foo");
                         }
@@ -390,13 +402,13 @@ class Interpreter implements ByteCodes, Tokens {
             }
                 */
             case ADD_TRAP: case DEL_TRAP: {
-                JS val = stack.pop();
-                JS key = stack.pop();
-                JS js = stack.peek();
+                JS val = (JS)stack.pop();
+                JS key = (JS)stack.pop();
+                JS js = (JS)stack.peek();
                 // A trap addition/removal
                 if(!(val instanceof JSFunction)) throw new JSExn("tried to add/remove a non-function trap");
-                if(op == ADD_TRAP) js.addTrap(key, (JSFunction)val);
-                else js.delTrap(key, (JSFunction)val);
+                if(op == ADD_TRAP) js.addTrap(key, val);
+                else js.delTrap(key, val);
                 break;
             }
 
@@ -405,8 +417,8 @@ class Interpreter implements ByteCodes, Tokens {
                 if(count < 2) throw new Error("this should never happen");
                 if(count == 2) {
                     // common case
-                    JS right = stack.pop();
-                    JS left = stack.pop();
+                    JS right = (JS)stack.pop();
+                    JS left = (JS)stack.pop();
                     JS ret;
                     if(left instanceof JSString || right instanceof JSString)
                         ret = JS.S(JS.toString(left).concat(JS.toString(right)));
@@ -420,7 +432,7 @@ class Interpreter implements ByteCodes, Tokens {
                     stack.push(ret);
                 } else {
                     JS[] args = new JS[count];
-                    while(--count >= 0) args[count] = stack.pop();
+                    while(--count >= 0) args[count] = (JS)stack.pop();
                     if(args[0] instanceof JSString) {
                         StringBuffer sb = new StringBuffer(64);
                         for(int i=0;i<args.length;i++) sb.append(JS.toString(args[i]));
@@ -451,8 +463,8 @@ class Interpreter implements ByteCodes, Tokens {
             }
 
             default: {
-                JS right = stack.pop();
-                JS left = stack.pop();
+                JS right = (JS)stack.pop();
+                JS left = (JS)stack.pop();
                 switch(op) {
                         
                 case BITOR: stack.push(JS.N(JS.toLong(left) | JS.toLong(right))); break;
@@ -503,11 +515,11 @@ class Interpreter implements ByteCodes, Tokens {
     */
     void catchException(JSExn e) throws JSExn {
         while(!stack.empty()) {
-            JS o = stack.pop();
+            JS o = (JS)stack.pop();
             if (o instanceof CatchMarker || o instanceof TryMarker) {
                 boolean inCatch = o instanceof CatchMarker;
                 if(inCatch) {
-                    o = stack.pop();
+                    o = (JS)stack.pop();
                     if(((TryMarker)o).finallyLoc < 0) continue; // no finally block, keep going
                 }
                 if(!inCatch && ((TryMarker)o).catchLoc >= 0) {
@@ -531,10 +543,10 @@ class Interpreter implements ByteCodes, Tokens {
         throw e;
     }
 
-    void setupTrap(Trap t, JS val, CallMarker cm) throws JSExn {
+    void setupTrap(JS.Trap t, JS val, CallMarker cm) throws JSExn {
         stack.push(cm);
-        stack.push(new TrapArgs(t,val));
-        f = t.f;
+        stack.push(new TrapArgs(t, val));
+        f = (JSFunction)t.function(); // FIXME
         scope = f.parentScope;
         pc = 0;
     }
@@ -542,12 +554,7 @@ class Interpreter implements ByteCodes, Tokens {
 
     // Markers //////////////////////////////////////////////////////////////////////
 
-    static class Marker extends JS {
-        public JS get(JS key) throws JSExn { throw new Error("this should not be accessible from a script"); }
-        public void put(JS key, JS val) throws JSExn { throw new Error("this should not be accessible from a script"); }
-        public String coerceToString() { throw new Error("this should not be accessible from a script"); }
-        public JS call(JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn { throw new Error("this should not be accessible from a script"); }
-    }
+    static class Marker {}
     
     static class CallMarker extends Marker {
         final int pc;
@@ -561,12 +568,12 @@ class Interpreter implements ByteCodes, Tokens {
     }
     
     static class TrapMarker extends CallMarker {
-        Trap t;
+        JS.Trap t;
         JS val;
         boolean cascadeHappened;
         final boolean pauseOnPut;
-        public TrapMarker(Interpreter cx, Trap t, JS val) { this(cx,t,val,false); } 
-        public TrapMarker(Interpreter cx, Trap t, JS val, boolean pauseOnPut) {
+        public TrapMarker(Interpreter cx, JS.Trap t, JS val) { this(cx,t,val,false); } 
+        public TrapMarker(Interpreter cx, JS.Trap t, JS val, boolean pauseOnPut) {
             super(cx);
             this.t = t;
             this.val = val;
@@ -575,7 +582,7 @@ class Interpreter implements ByteCodes, Tokens {
     }
     
     static class CatchMarker extends Marker { }
-    private static CatchMarker catchMarker = new CatchMarker();
+    private static final CatchMarker catchMarker = new CatchMarker();
     
     static class LoopMarker extends Marker {
         final public int location;
@@ -608,77 +615,41 @@ class Interpreter implements ByteCodes, Tokens {
         public FinallyData(JSExn exn) { this.exn = exn; this.op = -1; this.arg = null; } // Just throw this exn
     }
 
-    static class TrapArgs extends JS {
+    static class TrapArgs extends JS.Immutable {
         private Trap t;
         private JS val;
         public TrapArgs(Trap t, JS val) { this.t = t; this.val = val; }
         public JS get(JS key) throws JSExn {
-            if(JS.isInt(key) && JS.toInt(key) == 0) return val;
-            //#switch(JS.toString(key))
-            case "trapee": return t.target;
-            case "callee": return t.f;
-            case "trapname": return t.key;
-            case "length": return t.isWriteTrap() ? ONE : ZERO;
+            if(Script.isInt(key) && Script.toInt(key) == 0) return val;
+            //#switch(Script.str(key))
+            case "trapee": return t.target();
+            case "callee": return t.function();
+            case "trapname": return t.key();
+            case "length": return t.isWriteTrap() ? Script.ONE : Script.ZERO;
             //#end
             return super.get(key);
         }
     }
     
-    static class JSArgs extends JS {
-        private final JS a0;
-        private final JS a1;
-        private final JS a2;
-        private final JS[] rest;
-        private final int nargs;
-        private final JS callee;
-        
-        public JSArgs(JS callee) { this(null,null,null,null,0,callee); }
-        public JSArgs(JS a0, JS callee) { this(a0,null,null,null,1,callee); }
-        public JSArgs(JS a0, JS a1, JS a2, JS[] rest, int nargs, JS callee) {
-            this.a0 = a0; this.a1 = a1; this.a2 = a2;
-            this.rest = rest; this.nargs = nargs;
-            this.callee = callee;
-        }
-        
-        public JS get(JS key) throws JSExn {
-            if(JS.isInt(key)) {
-                int n = JS.toInt(key);
-                switch(n) {
-                    case 0: return a0;
-                    case 1: return a1;
-                    case 2: return a2;
-                    default: return n>= 0 && n < nargs ? rest[n-3] : null;
-                }
-            }
-            //#switch(JS.toString(key))
-            case "callee": return callee;
-            case "length": return JS.N(nargs);
-            //#end
-            return super.get(key);
-        }
-    }
-
-    static class Stub extends JS {
+    static class Stub extends JS.Immutable {
         private JS method;
         JS obj;
         public Stub(JS obj, JS method) { this.obj = obj; this.method = method; }
-        public JS call(JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn {
-            return ((JS)obj).callMethod(method, a0, a1, a2, rest, nargs);
-        }
+        public JS call(JS[] args) throws JSExn { return obj.call(method, args); }
     }
     
-    static class Stack {
+    static final class Stack {
         private static final int MAX_STACK_SIZE = 512;
-        private JS[] stack = new JS[64];
+        private Object[] stack = new Object[8];
         private int sp = 0;
         
         boolean empty() { return sp == 0; }
-        void push(JS o) throws JSExn { if(sp == stack.length) grow(); stack[sp++] = o; }
-        JS peek() { if(sp == 0) throw new RuntimeException("Stack underflow"); return stack[sp-1]; }
-        final JS pop() { if(sp == 0) throw new RuntimeException("Stack underflow"); return stack[--sp]; }
+        void push(Object o) throws JSExn { if(sp == stack.length) grow(); stack[sp++] = o; }
+        Object peek() { if(sp == 0) throw new RuntimeException("stack underflow"); return stack[sp-1]; }
+        final Object pop() { if(sp == 0) throw new RuntimeException("stack underflow"); return stack[--sp]; }
         void swap() throws JSExn {
             if(sp < 2) throw new JSExn("stack overflow");
-            JS tmp = stack[sp-2];
+            Object tmp = stack[sp-2];
             stack[sp-2] = stack[sp-1];
             stack[sp-1] = tmp;
         }
@@ -687,9 +658,10 @@ class Interpreter implements ByteCodes, Tokens {
             return null;
         }
         void grow() throws JSExn {
-            if(stack.length >= MAX_STACK_SIZE) throw new JSExn("Stack overflow");
-            JS[] stack2 = new JS[stack.length * 2];
+            if(stack.length >= MAX_STACK_SIZE) throw new JSExn("stack overflow");
+            Object[] stack2 = new Object[stack.length * 2];
             System.arraycopy(stack,0,stack2,0,stack.length);
+            stack = stack2;
         }       
         
         void backtrace(JSExn e) {
index cb6e680..0a3843c 100644 (file)
@@ -9,125 +9,236 @@ import java.io.*;
 import java.util.*;
 
 /** The minimum set of functionality required for objects which are manipulated by JavaScript */
-public abstract class JS { 
-    public static final JS METHOD = new JS() { };
-
-    public JS.Enumeration keys() throws JSExn { throw new JSExn("you can't enumerate the keys of this object (class=" + getClass().getName() +")"); }
-    public JS get(JS key) throws JSExn { return null; }
-    public void put(JS key, JS val) throws JSExn { throw new JSExn("" + key + " is read only (class=" + getClass().getName() +")"); }
-    
-    public JS callMethod(JS method, JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn {
-        throw new JSExn("method not found (" + JS.debugToString(method) + ")");
-    }
-    public JS call(JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn {
-        throw new JSExn("you cannot call this object (class=" + this.getClass().getName() +")");
-    }
-    public InputStream getInputStream() throws IOException {
-        throw new IOException("this object doesn't have a stream associated with it " + getClass().getName() + ")");
-    }
-    
-    public final JS unclone() { return _unclone(); }
-    public final JS jsclone() throws JSExn { return new Clone(this); }
-    public final boolean hasTrap(JS key) { return getTrap(key) != null; }
-    public final boolean equals(Object o) { return this == o || ((o instanceof JS) && jsequals((JS)o)); }
-    // Discourage people from using toString()
-    public final String toString() { return "JS Object [class=" + getClass().getName() + "]"; }
-    
-    // Package private methods
-    Trap getTrap(JS key) { return null; }
-    void putTrap(JS key, Trap value) throws JSExn { throw new JSExn("traps cannot be placed on this object (class=" + this.getClass().getName() +")"); }
-    String coerceToString() throws JSExn { throw new JSExn("can't coerce to a string (class=" + getClass().getName() +")"); }
-    boolean jsequals(JS o) { return this == o; }
-    JS _unclone() { return this; }
-        
-    public static class O extends JS implements Cloneable {
-        private Hash entries;
-        
-        public Enumeration keys() throws JSExn { return entries == null ? (Enumeration)EMPTY_ENUMERATION : (Enumeration)new JavaEnumeration(null,entries.keys()); }
-        public JS get(JS key) throws JSExn { return entries == null ? null : (JS)entries.get(key, null); }
-        public void put(JS key, JS val) throws JSExn { (entries==null?entries=new Hash():entries).put(key,null,val); }        
-
-        /** retrieve a trap from the entries hash */
-        final Trap getTrap(JS key) {
-            return entries == null ? null : (Trap)entries.get(key, Trap.class);
-        }
-        
-        /** retrieve a trap from the entries hash */
-        final void putTrap(JS key, Trap value) {
-            if (entries == null) entries = new Hash();
-            entries.put(key, Trap.class, value);
-        }    
+public interface JS extends Pausable {
+
+    /** Returns an enumeration of the keys in this object. */
+    public JS.Enumeration keys() throws JSExn;
+
+    /** Return the value associated with the given key. */
+    public JS get(JS key) throws JSExn;
+
+    /** Store a specific value against the given key. */
+    public void put(JS key, JS value) throws JSExn;
+
+    /** Executes or unpauses the task, running it in a
+     *  <i>pausable</i> context. */
+    public Object run(Object o) throws Exception, AlreadyRunningException;
+
+    /** Pauses the running task at its convienience. */
+    public void pause() throws NotPausableException;
+
+    /** Calls a specific method with given arguments on this object.
+     *  An exception is thrown if there is no such method or the
+     *  arguments are invalid. */
+    public JS call(JS method, JS[] args) throws JSExn;
+
+    /** Calls this object with the given arguments, running it in an
+     *  <i>unpausable</i> context. An exception is thrown if this
+     *  object is not callable or the arguments are invalid. */
+    public JS call(JS[] args) throws JSExn;
+
+    /** Returns the names of the formal arguments, if any.
+     *  This function must never return a null object. */
+    public String[] getFormalArgs();
+
+    /** Returns true if the specific key is found in this object. */
+    public boolean hasKey(JS key);
+
+    /** Put a value to the given key, calling any write traps that have
+     *  been placed on the key.
+     *
+     *  This function may block for an indeterminate amount of time.
+     *
+     *  Write traps may change the final value  before it is put, thus
+     *  be careful to read the latest value from this function's return
+     *  before doing any more work with it.
+     */
+    public JS putAndTriggerTraps(JS key, JS value) throws JSExn;
+
+    /** Gets the value for a given key, calling any read traps that have
+     *  been placed on the key.
+     *
+     *  This function may block for an indeterminate amount of time.
+     */
+    public JS getAndTriggerTraps(JS key) throws JSExn;
+
+    /** Calls the write traps placed on a given key with the given value
+     *  and returns the result without saving it to this object's key store. */
+    public JS justTriggerTraps(JS key, JS value) throws JSExn;
+
+    public void addTrap(JS key, JS function) throws JSExn;
+    public void delTrap(JS key, JS function) throws JSExn;
+    public Trap getTrap(JS key) throws JSExn;
+
+    // FIXME: consider renaming/removing these
+    public InputStream getInputStream() throws IOException, JSExn;
+    public JS unclone();
+    public String coerceToString();
+
+
+    // Implementations ////////////////////////////////////////////////////////
+
+    /** Provides the lightest possible implementation of JS, throwing
+     *  exceptions on every mutable operation. */
+    public static class Immutable implements JS {
+        private static final String[] emptystr = new String[0];
+
+        public JS unclone() { return this; }
+        public JS.Enumeration keys() throws JSExn { throw new JSExn(
+            "object has no key set, class ["+ getClass().getName() +"]"); }
+        public JS get(JS key) throws JSExn { return null; }
+        public void put(JS key, JS val) throws JSExn { throw new JSExn(
+            "'" + key + "' is read only on class ["+ getClass().getName() +"]"); }
+        public InputStream getInputStream() throws IOException, JSExn { throw new JSExn(
+            "object has not associated stream, class ["+ getClass().getName() +"]"); }
+
+        public boolean hasKey(JS key) { return false; }
+
+        public Object run(Object o) throws Exception { throw new JSExn(
+            "object cannot be called, class ["+ getClass().getName() +"]"); }
+        public void pause() { throw new NotPausableException(); }
+
+        public JS call(JS[] args) throws JSExn { throw new JSExn(
+            "object cannot be called, class ["+ getClass().getName() +"]"); }
+        public JS call(JS method, JS[] args) throws JSExn { throw new JSExn(
+            "method not found: " + Script.str(method)); }
+        public String[] getFormalArgs() { return emptystr; }
+
+        public void declare(JS key) throws JSExn { throw new JSExn(
+            "object cannot declare key: "+ Script.str(key)); }
+        public void undeclare(JS key) throws JSExn { } // FIXME throw error?
+
+        public JS putAndTriggerTraps(JS key, JS val) throws JSExn { throw new JSExn(
+            "'" + key + "' is trap read only on class ["+ getClass().getName() +"]"); }
+        public JS getAndTriggerTraps(JS key) throws JSExn { return null; } // FIXME throw errors?
+        public JS justTriggerTraps(JS key, JS value) throws JSExn { return null; }
+
+        public void addTrap(JS key, JS function) throws JSExn { throw new JSExn(
+            "'" + key + "' is not trappable on class ["+ getClass().getName() +"]"); }
+        public void delTrap(JS key, JS function) throws JSExn { throw new JSExn(
+            "'" + key + "' trap is read only on class ["+ getClass().getName() +"]"); }
+        public Trap getTrap(JS key) throws JSExn { throw new JSExn(
+            "'" + key + "' is not trappable on class ["+ getClass().getName() +"]"); }
+
+        public String coerceToString() { return "object"; }
     }
-    
-    public interface Cloneable { }
-        
-    public static class Clone extends O {
+
+    public interface Cloneable {}
+
+    public static class Clone implements JS {
         protected final JS clonee;
         public Clone(JS clonee) throws JSExn {
-            if(!(clonee instanceof Cloneable)) throw new JSExn("" + clonee.getClass().getName() + " isn't cloneable");
+            if (!(clonee instanceof Cloneable)) throw new JSExn(
+                clonee.getClass().getName() + " is not implement cloneable");
             this.clonee = clonee;
         }
-        JS _unclone() { return clonee.unclone(); }
-        boolean jsequals(JS o) { return clonee.jsequals(o); }
-        
+
+        public JS unclone() { return clonee.unclone(); }
+        public boolean equals(Object o) { return clonee.equals(o); }
+
         public Enumeration keys() throws JSExn { return clonee.keys(); }
-        public final JS get(JS key) throws JSExn { return clonee.get(key); }
-        public final void put(JS key, JS val) throws JSExn { clonee.put(key,val); }
-        public final JS callMethod(JS method, JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn {
-            return clonee.callMethod(method,a0,a1,a2,rest,nargs); 
-        }
-        public JS call(JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn {
-            return clonee.call(a0, a1, a2, rest, nargs);
-        }
-        public InputStream getInputStream() throws IOException { return clonee.getInputStream(); }
-        // FIXME: This shouldn't be necessary (its for Ibex.Blessing)
-        public JS getClonee() { return clonee; }
+        public JS get(JS k) throws JSExn { return clonee.get(k); }
+        public void put(JS k, JS v) throws JSExn { clonee.put(k, v); }
+        public InputStream getInputStream() throws IOException, JSExn {
+            return clonee.getInputStream(); }
+
+        public Object run(Object o) throws Exception { return clonee.run(o); }
+        public void pause() { clonee.pause(); }
+
+        public JS call(JS m, JS[] a) throws JSExn { return clonee.call(m, a); }
+        public JS call(JS[] a) throws JSExn { return clonee.call(a); }
+        public String[] getFormalArgs() { return clonee.getFormalArgs(); }
+
+        public boolean hasKey(JS k) { return clonee.hasKey(k); }
+
+        public JS putAndTriggerTraps(JS k, JS v) throws JSExn {
+            return clonee.putAndTriggerTraps(k, v); }
+        public JS getAndTriggerTraps(JS k) throws JSExn {
+            return clonee.getAndTriggerTraps(k); }
+        public JS justTriggerTraps(JS k, JS v) throws JSExn {
+            return clonee.justTriggerTraps(k, v); }
+
+        public void addTrap(JS k, JS f) throws JSExn { clonee.addTrap(k, f); }
+        public void delTrap(JS k, JS f) throws JSExn { clonee.delTrap(k, f); }
+        public Trap getTrap(JS k) throws JSExn { return clonee.getTrap(k); }
+
+        public String coerceToString() { return clonee.coerceToString(); }
     }
-    
-    public static abstract class Enumeration extends JS {
-        final Enumeration parent;
-        boolean done;
-        public Enumeration(Enumeration parent) { this.parent = parent; }
-        protected abstract boolean _hasMoreElements();
-        protected abstract JS _nextElement() throws JSExn;
-        
-        public final boolean hasMoreElements() {
-            if(!done && !_hasMoreElements()) done = true;
-            return !done ? true : parent != null ? parent.hasMoreElements() : false;
-        }
-        public final JS nextElement() throws JSExn { return !done ? _nextElement() : parent != null ? parent.nextElement() : null; }
-        
-        public JS get(JS key) throws JSExn {
-            //#switch(JS.toString(key))
-            case "hasMoreElements": return B(hasMoreElements());
-            case "nextElement": return nextElement();
-            //#end
-            return super.get(key);
+
+    public static class Obj extends LinearStore implements JS {
+        private static final String[] emptystr = new String[0];
+        private static final Placeholder holder = new Placeholder();
+
+        /** entries[index + 0] // key
+         *  entries[index + 1] // value
+         *  entries[index + 2] // trap
+         */
+        protected final int indexmultiple = 3;
+
+        protected void entryAdded(int p) {}
+        protected void entryUpdated(int p) {}
+        protected void entryRemoved(int p) {}
+
+        public Obj() { super(4, 0.75F); }
+
+        public JS unclone() { return this; }
+        public InputStream getInputStream() throws IOException, JSExn { throw new JSExn(
+            "object has not associated stream, class ["+ getClass().getName() +"]"); }
+
+        public Object run(Object o) throws Exception { throw new JSExn(
+            "object cannot be called, class ["+ getClass().getName() +"]"); }
+        public void pause() { throw new NotPausableException(); }
+
+        public JS call(JS[] args) throws JSExn { throw new JSExn(
+            "object cannot be called, class ["+ getClass().getName() +"]"); }
+        public JS call(JS method, JS[] args) throws JSExn { throw new JSExn(
+            "method not found: " + Script.str(method)); }
+        public String[] getFormalArgs() { return emptystr; }
+
+        public Enumeration keys() throws JSExn {
+            // FEATURE: replicate some code from a superclass to avoid double object creation
+            return new Enumeration.JavaIterator(null, new LinearStore.IndexIterator() {
+                public Object next() { return entries[nextIndex()]; } });
         }
-    }
+        public JS get(JS key) throws JSExn { int i = indexOf(key);
+            return i < 0 ? null : entries[i + 1] instanceof Placeholder ?  null : (JS)entries[i + 1]; }
+        public void put(JS key, JS val) throws JSExn {
+            // NOTE: only way value can be stored as null is using declare()
+            int dest = put(indexOf(key), key);
+            if (val == null) entries[dest + 1] = holder;
+            else entries[dest + 1] = val; }
 
-    public static class EmptyEnumeration extends Enumeration {
-        public EmptyEnumeration(Enumeration parent) { super(parent); }
-        protected boolean _hasMoreElements() { return false; }
-        protected JS _nextElement() { return null; }
-    }
-    public static class JavaEnumeration extends Enumeration {
-        private final java.util.Enumeration e;
-        public JavaEnumeration(Enumeration parent, java.util.Enumeration e) { super(parent); this.e = e; }
-        protected boolean _hasMoreElements() { return e.hasMoreElements(); }
-        protected JS _nextElement() { return (JS) e.nextElement(); }
-    }
-    
-    // Static Interpreter Control Methods ///////////////////////////////////////////////////////////////
+        public boolean hasKey(JS key) { return indexOf(key) >= 0; }
+        /*public boolean hasValue(JS key, JS value) {
+            int i = indexOf(key); return i >= 0 && entries[i + 1] != null; }
+        public boolean hasTrap(JS key, JS trap) {
+            int i = indexOf(key); return i >= 0 && entries[i + 2] != null; }*/
 
-    /** log a message with the current JavaScript sourceName/line */
-    public static void log(Object message) { info(message); }
-    public static void debug(Object message) { Log.debug(Interpreter.getSourceName() + ":" + Interpreter.getLine(), message); }
-    public static void info(Object message) { Log.info(Interpreter.getSourceName() + ":" + Interpreter.getLine(), message); }
-    public static void warn(Object message) { Log.warn(Interpreter.getSourceName() + ":" + Interpreter.getLine(), message); }
-    public static void error(Object message) { Log.error(Interpreter.getSourceName() + ":" + Interpreter.getLine(), message); }
+        public void declare(JS key) throws JSExn { entries[put(indexOf(key), key) + 1] = null; }
+        public void undeclare(JS key) throws JSExn { remove(indexOf(key)); }
 
-    public static class NotPauseableException extends Exception { NotPauseableException() { } }
+        public JS putAndTriggerTraps(JS key, JS val) throws JSExn {
+            Trap t = null; int i = indexOf(key);
+            if (i >= 0) t = (Trap)entries[i + 2];
+            if (t != null && (t = t.write()) != null) {
+                val = (JS)new Interpreter(t, val, false).run(null);
+            }
+            if (val == null) entries[i + 1] = holder;
+            else entries[i + 1] = val;
+            return val;
+        }
+        public JS getAndTriggerTraps(JS key) throws JSExn {
+            Trap t = null; int i = indexOf(key);
+            if (i < 0) return null;
+            t = (Trap)entries[i + 2];
+            return t == null ? (JS)entries[i + 1] : (JS)new Interpreter(t, null, false).run(null);
+        }
+        public JS justTriggerTraps(JS key, JS val) throws JSExn {
+            Trap t = null; int i = indexOf(key);
+            if (i >= 0) t = (Trap)entries[i + 2];
+            if (t == null || (t = t.write()) == null) return val;
+            return (JS)new Interpreter(t, val, true).run(null);
+        }
 
     /** returns a callback which will restart the context; expects a value to be pushed onto the stack when unpaused */
     public static UnpauseCallback pause() throws NotPauseableException {
@@ -136,187 +247,116 @@ public abstract class JS {
         boolean get;
         switch(i.f.op[i.pc]) {
             case Tokens.RETURN: case ByteCodes.PUT: get = false; break;
-            case ByteCodes.GET: case ByteCodes.CALL: case ByteCodes.CALLMETHOD: get = true; break;
-            default: throw new Error("should never happen: i.f.op[i.pc] == " + i.f.op[i.pc]);
+            case ByteCodes.GET: case ByteCodes.CALL: get = true; break;
+            default: throw new Error("should never happen");
         }
-        i.pausecount++;
-        return new JS.UnpauseCallback(i,get);
-    }
 
-    public static class UnpauseCallback implements Task {
-        private Interpreter i;
-        private boolean get;
-        UnpauseCallback(Interpreter i, boolean get) { this.i = i; this.get = get; }
-        public void perform() throws JSExn { unpause(); }
-        public JS unpause() throws JSExn { return unpause((JS)null); }
-        public JS unpause(JS o) throws JSExn {
-            if (o == JS.METHOD) throw new JSExn("can't return a method to a paused context");
-            if(get) i.stack.push(o);
-            return i.resume();
+        public void delTrap(JS key, JS f) throws JSExn {
+            int i = indexOf(key); if (i < 0) return;
+            Trap t = (Trap)entries[i + 2];
+            if (t.function().equals(f)) { entries[i + 2] = t.next(); return; }
+            for (; t.next() != null; t = t.next())
+                if (t.next().function().equals(f)) { ((TrapHolder)t).next = t.next().next(); return; }
         }
-        public JS unpause(JSExn e) throws JSExn {
-            i.catchException(e);
-            return i.resume();
+
+        public Trap getTrap(JS key) throws JSExn {
+            int i = indexOf(key); return i < 0 ? null : (Trap)entries[i + 2];
         }
-    }
 
+        public String coerceToString() { return "object"; }
 
+        private static final class Placeholder implements Serializable {}
 
-    // Static Helper Methods ///////////////////////////////////////////////////////////////////////////////////
+        private static final class TrapHolder implements Trap {
+            private final JS target, key, function;
+            private Trap next;
+            TrapHolder(JS t, JS k, JS f, Trap n) { target = t; key = k; function = f; next = n; }
 
-    /** coerce an object to a Boolean */
-    public static boolean toBoolean(JS o) {
-        if(o == null) return false;
-        if(o instanceof JSNumber) return ((JSNumber)o).toBoolean();
-        if(o instanceof JSString) return ((JSString)o).s.length() != 0;
-        return true;
-    }
-    //#repeat long/int/double/float toLong/toInt/toDouble/toFloat Long/Integer/Double/Float parseLong/parseInt/parseDouble/parseFloat
-    /** coerce an object to a Number */
-    public static long toLong(JS o) throws JSExn {
-        if(o == null) return 0;
-        if(o instanceof JSNumber) return ((JSNumber)o).toLong();
-        if(o instanceof JSString) return Long.parseLong(o.coerceToString());
-        throw new JSExn("can't coerce a " + o.getClass().getName() + " to a number");
-    }
-    //#end
-    
-    public static String toString(JS o) throws JSExn {
-        if(o == null) return "null";
-        return o.coerceToString();
-    }
-    
-    public static String debugToString(JS o) {
-        try { return toString(o); }
-        catch(JSExn e) { return o.toString(); }
-    }
-    
-    public static boolean isInt(JS o) {
-        if(o == null) return true;
-        if(o instanceof JSNumber.I) return true;
-        if(o instanceof JSNumber.B) return false;
-        if(o instanceof JSNumber) {
-            JSNumber n = (JSNumber) o;
-            return n.toInt() == n.toDouble();
-        }
-        if(o instanceof JSString) {
-            String s = ((JSString)o).s;
-            for(int i=0;i<s.length();i++)
-                if(s.charAt(i) < '0' || s.charAt(i) > '9') return false;
-            return true;
-        }
-        return false;
-    }
-    
-    public static boolean isString(JS o) {
-        if(o instanceof JSString) return true;
-        return false;
-    }
-    
-    // Instance Methods ////////////////////////////////////////////////////////////////////
-    public final static JS NaN = new JSNumber.D(Double.NaN);
-    public final static JS ZERO = new JSNumber.I(0);
-    public final static JS ONE = new JSNumber.I(1);
-    public final static JS MATH = new JSMath();
-        
-    public static final JS T = new JSNumber.B(true);
-    public static final JS F = new JSNumber.B(false);
-
-    public static final JS B(boolean b) { return b ? T : F; }
-    public static final JS B(int i) { return i==0 ? F : T; }
-    
-    private static final int CACHE_SIZE = 65536 / 4; // must be a power of two
-    private static final JSString[] stringCache = new JSString[CACHE_SIZE];
-    public static final JS S(String s) {
-        if(s == null) return null;
-        int slot = s.hashCode()&(CACHE_SIZE-1);
-        JSString ret = stringCache[slot];
-        if(ret == null || !ret.s.equals(s)) stringCache[slot] = ret = new JSString(s);
-        return ret;
-    }
-    public static final JS S(String s, boolean intern) { return intern ? JSString.intern(s) : S(s); }
-
-    public static final JS N(double d) { return new JSNumber.D(d); }
-    public static final JS N(long l) { return new JSNumber.L(l); }
-    
-    public static final JS N(Number n) {
-        if(n instanceof Integer) return N(n.intValue());
-        if(n instanceof Long) return N(n.longValue());
-        return N(n.doubleValue());
-    }
+            public JS key() { return key; }
+            public JS target() { return target; }
+            public JS function() { return function; }
 
-    private static final JSNumber.I[] smallIntCache = new JSNumber.I[CACHE_SIZE];
-    private static final JSNumber.I[] largeIntCache = new JSNumber.I[CACHE_SIZE];
-    public static final JS N(int i) {
-        JSNumber.I ret = null;
-        int idx = i + smallIntCache.length / 2;
-        if (idx < CACHE_SIZE && idx > 0) {
-            ret = smallIntCache[idx];
-            if (ret != null) return ret;
-        }
-        else ret = largeIntCache[Math.abs(idx % CACHE_SIZE)];
-        if (ret == null || ret.i != i) {
-            ret = new JSNumber.I(i);
-            if (idx < smallIntCache.length && idx > 0) smallIntCache[idx] = ret;
-            else largeIntCache[Math.abs(idx % CACHE_SIZE)] = ret;
+            public boolean isReadTrap()  {
+                return function.getFormalArgs() == null || function.getFormalArgs().length == 0; }
+            public boolean isWriteTrap() {
+                return function.getFormalArgs() != null && function.getFormalArgs().length != 0; }
+
+            public Trap next() { return next; }
+            public Trap nextRead() {
+                Trap t = next; while (t != null && t.isWriteTrap()) t = t.next(); return t; }
+            public Trap nextWrite() {
+                Trap t = next; while (t != null && t.isReadTrap()) t = t.next(); return t; }
+            public Trap read() { return isReadTrap() ? this : nextRead(); }
+            public Trap write() { return isWriteTrap() ? this : nextWrite(); }
         }
-        return ret;
-    }
-    
-    private static Enumeration EMPTY_ENUMERATION = new EmptyEnumeration(null);
-    
-    public static JS fromReader(String sourceName, int firstLine, Reader sourceCode) throws IOException {
-        return Parser.fromReader(sourceName, firstLine, sourceCode);
     }
 
-    public static JS cloneWithNewGlobalScope(JS js, JS s) {
-        if(js instanceof JSFunction)
-            return ((JSFunction)js)._cloneWithNewParentScope(new JSScope.Top(s));
-        else
-            return js;
+    public interface Trap {
+        public JS key();
+        public JS target();
+        public JS function();
+        public boolean isReadTrap();
+        public boolean isWriteTrap();
+        public Trap next();
+        public Trap nextRead();
+        public Trap nextWrite();
+
+        public Trap read(); // FIXME reconsider these function names
+        public Trap write();
     }
 
+    /** An empty class used for instanceof matching the result of JS.get()
+     *  to decide if the key is a method (to be called with JS.callMethod(). */
+    public static final class Method extends Immutable {}
 
-    // Trap support //////////////////////////////////////////////////////////////////////////////
 
-    /** performs a put, triggering traps if present; traps are run in an unpauseable interpreter */
-    public final void putAndTriggerTraps(JS key, JS value) throws JSExn {
-        Trap t = getTrap(key);
-        if(t == null || (t = t.writeTrap()) == null) put(key,value);
-        else new Interpreter(t,value,false).resume();
-    }
+    public static abstract class Enumeration extends Immutable {
+        public static final Enumeration EMPTY = new Empty(null);
 
-    /** performs a get, triggering traps if present; traps are run in an unpauseable interpreter */
-    public final JS getAndTriggerTraps(JS key) throws JSExn {
-        Trap t = getTrap(key);
-        if (t == null || (t = t.readTrap()) == null) return get(key);
-        else return new Interpreter(t,null,false).resume();
-    }
-    
-    public final JS justTriggerTraps(JS key, JS value) throws JSExn {
-        Trap t = getTrap(key);
-        if(t == null || (t = t.writeTrap()) == null) return value;
-        else return new Interpreter(t,value,true).resume();
-    }
+        private final Enumeration parent;
 
-    /** adds a trap, avoiding duplicates */
-    // FIXME: This shouldn't be public, it is needed for a hack in Template.java
-    public void addTrap(JS key, JSFunction f) throws JSExn {
-        if (f.numFormalArgs > 1) throw new JSExn("traps must take either one argument (write) or no arguments (read)");
-        boolean isRead = f.numFormalArgs == 0;
-        for(Trap t = getTrap(key); t != null; t = t.next) if (t.f == f) return;
-        putTrap(key, new Trap(this, key, f, (Trap)getTrap(key)));
-    }
+        public Enumeration(Enumeration parent) { this.parent = parent; }
 
-    /** deletes a trap, if present */
-    // FIXME: This shouldn't be public, it is needed for a hack in Template.java
-    public void delTrap(JS key, JSFunction f) throws JSExn {
-        Trap t = (Trap)getTrap(key);
-        if (t == null) return;
-        if (t.f == f) { putTrap(t.target, t.next); return; }
-        for(; t.next != null; t = t.next) if (t.next.f == f) { t.next = t.next.next; return; }
-    }
+        protected abstract boolean _hasNext();
+        protected abstract JS _next() throws JSExn;
+
+        public final boolean hasNext() {
+            return _hasNext() || parent != null ? parent.hasNext() : false; }
+        public final JS next() throws JSExn {
+            if (_hasNext()) return _next();
+            if (parent == null) throw new NoSuchElementException("reached end of set");
+            return parent.next();
+        }
+
+        public JS get(JS key) throws JSExn {
+            //#switch(Script.str(key))
+            case "hasNext": return Script.B(hasNext());
+            case "next": return next();
+            //#end
+            return super.get(key);
+        }
 
+        public static final class Empty extends Enumeration {
+            public Empty(Enumeration parent) { super(parent); }
+            protected boolean _hasNext() { return false; }
+            protected JS _next() { throw new NoSuchElementException(); }
+        }
+
+        public static final class JavaIterator extends Enumeration {
+            private final Iterator i;
+            public JavaIterator(Enumeration parent, Iterator i) { super(parent); this.i = i; }
+            protected boolean _hasNext() { return i.hasNext(); }
+            protected JS _next() { return (JS)i.next(); }
+        }
+
+        public static final class RandomAccessList extends Enumeration {
+            private final List l;
+            private int pos = 0, size;
+            public RandomAccessList(Enumeration parent, List list) {
+                super(parent); l = list; size = l.size(); }
+            protected boolean _hasNext() { return  pos < size; }
+            protected JS _next() { return (JS)l.get(pos++); }
+        }
+    }
 
-} 
+}
index 8fe06a7..5d6738e 100644 (file)
 
 package org.ibex.js; 
 
-import org.ibex.util.*; 
+import java.io.InputStream;
 import java.util.*;
+import org.ibex.util.*; 
+import org.ibex.util.Collections;
 
 /** A JavaScript JSArray */
-public class JSArray extends JS.O {
-    private static final Object NULL = new Object();
-    private final BalancedTree bt = new BalancedTree();
-    
-    public JSArray() { }
-    public JSArray(int size) { setSize(size); }
-    
-    /*private static int intVal(Object o) {
-        if (o instanceof Number) {
-            int intVal = ((Number)o).intValue();
-            if (intVal == ((Number)o).doubleValue()) return intVal;
-            return Integer.MIN_VALUE;
-        }
-        if (!(o instanceof String)) return Integer.MIN_VALUE;
-        String s = (String)o;
-        for(int i=0; i<s.length(); i++) if (s.charAt(i) < '0' || s.charAt(i) > '9') return Integer.MIN_VALUE;
-        return Integer.parseInt(s);
-    }*/
-    
-    public JS callMethod(JS method, JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn {
-        //#switch(JS.toString(method))
-        case "pop": {
-            int oldSize = size();
-            if(oldSize == 0) return null;
-            return removeElementAt(oldSize-1);
+class JSArray extends ArrayList implements JS, Comparator {
+    private static final JS.Method METHOD = new JS.Method();
+    private static final String[] empty = new String[0];
+
+    JSArray() { }
+    JSArray(int size) { super(size); }
+    JSArray(JS[] args) { super(args.length); addAll(args); }
+    JSArray(JS arg) { super(1); add(arg); }
+
+    public JS unclone() { return this; }
+    public JS.Enumeration keys() throws JSExn { return new Enumeration.RandomAccessList(null, this); }
+    public JS get(JS key) throws JSExn {
+        if (key == null || !(key instanceof JSNumber.I)) {
+            //#switch(Script.str(key))
+            case "pop": return METHOD;
+            case "reverse": return METHOD;
+            case "toString": return METHOD;
+            case "shift": return METHOD;
+            case "join": return METHOD;
+            case "sort": return METHOD;
+            case "slice": return METHOD;
+            case "push": return METHOD;
+            case "unshift": return METHOD;
+            case "splice": return METHOD;
+            case "length": return Script.N(size());
+            //#end
+            throw new JSExn("arrays only support positive integer keys, can not use: "+Script.str(key));
         }
-        case "reverse": return reverse();
+        return (JS)get(((JSNumber.I)key).toInt());
+    }
+    public void put(JS key, JS val) throws JSExn {
+        if (Script.str(key).equals("length")) { setSize(Script.toInt(val)); }
+
+        if (key == null || !(key instanceof JSNumber.I)) throw new JSExn(
+            "arrays only support positive integer keys, can not use: "+Script.str(key));
+        int i = ((JSNumber.I)key).toInt();
+        if (i < 0) throw new JSExn("arrays can not use negative integer keys "+i);
+        ensureCapacity(i + 1); while (size() < i) add(null);
+        set(i, val);
+    }
+    public InputStream getInputStream() { return null; }
+
+    public String[] getFormalArgs() { return empty; }
+    public String coerceToString() { return "array"; } // FIXME
+
+    public boolean hasKey(JS key) {
+        if (key == null || !(key instanceof JSNumber.I)) return false;
+        int i = ((JSNumber.I)key).toInt();
+        return 0 <= i && i < size();
+    }
+
+    public Object run(Object o) throws JSExn { return call(null); }
+    public void pause() throws NotPausableException { throw new NotPausableException(); }
+    public JS call(JS[] args) throws JSExn { throw new JSExn("can not call an array as a function"); }
+    public JS call(JS method, JS[] args) throws JSExn {
+        //#switch(Script.str(method))
+        case "pop": return size() == 0 ? null : (JS)remove(size() - 1);
+        case "push": addAll(args); return Script.N(size());
+        case "reverse": Collections.reverse(this); return this;
         case "toString": return join(",");
-        case "shift":
-            if(length() == 0) return null;
-            return removeElementAt(0);
-        case "join":
-            return join(nargs == 0 ? "," : JS.toString(a0));
-        case "sort":
-            return sort(nargs < 1 ? null : a0);
+        case "shift": return size() == 0 ? null : (JS)remove(0);
+        case "join": return join(args.length == 0 ? "," : Script.str(args[0]));
+        case "sort": return sort(args.length == 0 ? null : args[0]);
         case "slice":
-            int start = toInt(nargs < 1 ? null : a0);
-            int end = nargs < 2 ? length() : toInt(a1);
+            int start = Script.toInt(args.length < 1 ? null : args[0]);
+            int end = args.length < 2 ? size() : Script.toInt(args[1]);
             return slice(start, end);
-        case "push": {
-            int oldSize = size();
-            for(int i=0; i<nargs; i++) insertElementAt(i==0?a0:i==1?a1:i==2?a2:rest[i-3],oldSize+i);
-            return N(oldSize + nargs);
-        }
         case "unshift":
-            for(int i=0; i<nargs; i++) insertElementAt(i==0?a0:i==1?a1:i==2?a2:rest[i-3],i);
-            return N(size());
-        case "splice":
-            JSArray array = new JSArray();
-            for(int i=0; i<nargs; i++) array.addElement(i==0?a0:i==1?a1:i==2?a2:rest[i-3]);
-            return splice(array);
-        //#end
-        return super.callMethod(method, a0, a1, a2, rest, nargs);
-    }
-        
-    public JS get(JS key) throws JSExn {
-        if (isInt(key)) {
-            int i = toInt(key);
-            if (i < 0 || i >= size()) return null;
-            return elementAt(i);
-        }
-        //#switch(JS.toString(key))
-        case "pop": return METHOD;
-        case "reverse": return METHOD;
-        case "toString": return METHOD;
-        case "shift": return METHOD;
-        case "join": return METHOD;
-        case "sort": return METHOD;
-        case "slice": return METHOD;
-        case "push": return METHOD;
-        case "unshift": return METHOD;
-        case "splice": return METHOD;
-        case "length": return N(size());
+            for (int i=0; i < args.length; i++) add(i, args[i]);
+            return Script.N(size());
+        case "splice": return splice(args);
         //#end
-        return super.get(key);
-    }
 
-    public void put(JS key, JS val) throws JSExn {
-        if (isInt(key)) {
-            int i = toInt(key);
-            int oldSize = size();
-            if(i < oldSize) {
-                setElementAt(val,i);
-            } else {
-                if(i > oldSize) setSize(i);
-                insertElementAt(val,i);
-            }
-            return;
-        }
-        if(isString(key)) {
-            if (JS.toString(key).equals("length")) {
-                setSize(JS.toInt(val));
-                return;
-            }
-        }
-        super.put(key,val);
+        throw new JSExn("arrays have no function: "+Script.str(method));
     }
 
-    public Enumeration keys() throws JSExn {
-        return new Enumeration(super.keys()) {
-                private int n = 0;
-                public boolean _hasMoreElements() { return n < size(); }
-                public JS _nextElement() {
-                    return n >= size() ? null : JS.N(n++);
-                }
-            };
-    }
+    public JS putAndTriggerTraps(JS key, JS val) throws JSExn { put(key, val); return val; }
+    public JS getAndTriggerTraps(JS key) throws JSExn { return get(key); }
+    public JS justTriggerTraps(JS key, JS val) throws JSExn { return val; }
 
-    public final void setSize(int newSize) {
-        // FEATURE: This could be done a lot more efficiently in BalancedTree
-        int oldSize = size();
-        for(int i=oldSize;i<newSize;i++) insertElementAt(null,i);
-        for(int i=oldSize-1;i>=newSize;i--) removeElementAt(i);
-    }
-    
-    public final int length() { return size(); }
-    public final JS elementAt(int i) { 
-        if(i < 0 || i >= size()) throw new ArrayIndexOutOfBoundsException(i);
-        Object o = bt.getNode(i);
-        return o == NULL ? (JS)null : (JS)o;
-    }
-    public final void addElement(JS o) { 
-        bt.insertNode(size(),o==null ? NULL : o);
-    }
-    public final void setElementAt(JS o, int i) {
-        if(i < 0 || i >= size()) throw new ArrayIndexOutOfBoundsException(i);
-        bt.replaceNode(i,o==null ? NULL : o);
-    }
-    public final void insertElementAt(JS o, int i) {
-        if(i < 0 || i > size()) throw new ArrayIndexOutOfBoundsException(i);
-        bt.insertNode(i,o==null ? NULL : o);
-    }
-    public final JS removeElementAt(int i) {
-        if(i < 0 || i >= size()) throw new ArrayIndexOutOfBoundsException(i);
-        Object o = bt.deleteNode(i);
-        return o == NULL ? (JS)null : (JS)o;
+    public void addTrap(JS k, JS f) throws JSExn { throw new JSExn("arrays do not support traps"); }
+    public void delTrap(JS k, JS f) throws JSExn { throw new JSExn("arrays do not support traps"); }
+    public JS.Trap getTrap(JS k) throws JSExn { throw new JSExn("arrays do not support traps"); }
+
+    /** FIXME: move to specialised ArrayStore superclass. */
+    public void addAll(JS[] entries) { for (int i=0; i < entries.length; i++) add(entries[i]); }
+
+    public void setSize(int newSize) {
+        ensureCapacity(newSize);
+        for (int i=size(); i < newSize; i++) add(null);
+        for (int i=size() - 1; i >= newSize; i--) remove(i);
     }
-    
-    public final int size() { return bt.treeSize(); }
-    
+
+
+    // ECMA Implementation ////////////////////////////////////////////////////
+
     private JS join(String sep) throws JSExn {
         int length = size();
         if(length == 0) return JS.S("");
         StringBuffer sb = new StringBuffer(64);
         int i=0;
         while(true) {
-            JS o = elementAt(i);
-            if(o != null) sb.append(JS.toString(o));
+            JS o = (JS)get(i);
+            if(o != null) sb.append(Script.toString(o));
             if(++i == length) break;
             sb.append(sep);
         }
         return JS.S(sb.toString());
     }
-    
-    // FEATURE: Implement this more efficiently
-    private JS reverse() {
-        int size = size();
-        if(size < 2) return this;
-        Vec vec = toVec();
-        bt.clear();
-        for(int i=size-1,j=0;i>=0;i--,j++) insertElementAt((JS)vec.elementAt(i),j);
-        return this;
-    }
-    
     private JS slice(int start, int end) {
-        int length = length();
+        int length = size();
         if(start < 0) start = length+start;
         if(end < 0) end = length+end;
         if(start < 0) start = 0;
@@ -181,45 +129,16 @@ public class JSArray extends JS.O {
         if(start > length) start = length;
         if(end > length) end = length;
         JSArray a = new JSArray(end-start);
-        for(int i=0;i<end-start;i++)
-            a.setElementAt(elementAt(start+i),i);
+        for(int i=0;i<end-start;i++) // FIXME: with ArrayStore do System.arraycopy()
+            a.set(i, get(start+i));
         return a;
     }
-        
-    private static final Vec.CompareFunc defaultSort = new Vec.CompareFunc() {
-        public int compare(Object a, Object b) {
-            try {
-                return JS.toString((JS)a).compareTo(JS.toString((JS)b));
-            } catch(JSExn e) { throw new JSExn.Wrapper(e); }
-        }
-    };
-    private JS sort(JS tmp) throws JSExn {
-        Vec vec = toVec();
-        try {
-            if(tmp instanceof JS) {
-                final JS jsFunc = (JS) tmp;
-                vec.sort(new Vec.CompareFunc() {
-                    public int compare(Object a, Object b) {
-                        try {
-                            return JS.toInt(jsFunc.call((JS)a, (JS)b, null, null, 2));
-                        } catch(JSExn e) { throw new JSExn.Wrapper(e); }
-                    }
-                });
-            } else {
-                vec.sort(defaultSort);
-            }
-        } catch(JSExn.Wrapper e) {
-            throw e.refill();
-        }
-        setFromVec(vec);
-        return this;
-    }
-    
-    private JS splice(JSArray args) throws JSExn {
-        int oldLength = length();
-        int start = JS.toInt(args.length() < 1 ? null : args.elementAt(0));
-        int deleteCount = JS.toInt(args.length() < 2 ? null : args.elementAt(1));
-        int newCount = args.length() - 2;
+
+    private JS splice(JS[] args) throws JSExn {
+        int oldLength = size();
+        int start = Script.toInt(args.length < 1 ? null : args[0]);
+        int deleteCount = Script.toInt(args.length < 2 ? null : args[1]);
+        int newCount = args.length - 2;
         if(newCount < 0) newCount = 0;
         if(start < 0) start = oldLength+start;
         if(start < 0) start = 0;
@@ -229,41 +148,38 @@ public class JSArray extends JS.O {
         int newLength = oldLength - deleteCount + newCount;
         int lengthChange = newLength - oldLength;
         JSArray ret = new JSArray(deleteCount);
-        for(int i=0;i<deleteCount;i++)
-            ret.setElementAt(elementAt(start+i),i);
+        for(int i=0;i<deleteCount;i++) // FIXME: ArrayStore System.arraycopy()
+            ret.set(i, get(start+i));
         if(lengthChange > 0) {
             setSize(newLength);
             for(int i=newLength-1;i>=start+newCount;i--)
-                setElementAt(elementAt(i-lengthChange),i);
+                set(i, get(i-lengthChange));
         } else if(lengthChange < 0) {
             for(int i=start+newCount;i<newLength;i++)
-                setElementAt(elementAt(i-lengthChange),i);
+                set(i, get(i-lengthChange));
             setSize(newLength);
         }
         for(int i=0;i<newCount;i++)
-            setElementAt(args.elementAt(i+2),start+i);
+            set(start+i, args[i+2]);
         return ret;
     }
-    
-    protected Vec toVec() {
-        int count = size();
-        Vec vec = new Vec();
-        vec.setSize(count);
-        for(int i=0;i<count;i++) {
-            Object o = bt.getNode(i);
-            vec.setElementAt(o == NULL ? null : o,i);
-        }
-        return vec;
+
+    private JS sort(JS comparator) throws JSExn {
+        try {
+            if (comparator == null) Collections.sort(this);
+            else { sort = comparator; Collections.sort(this, this); }
+            return this;
+        } catch (JSExn.Wrapper w) { throw w.unwrap(); }
     }
-    
-    protected void setFromVec(Vec vec) {
-        int count = vec.size();
-        bt.clear();
-        for(int i=0;i<count;i++) {
-            Object o = vec.elementAt(i);
-            bt.insertNode(i,o==null ? NULL : o);
-        }
+
+    private JS sort = null;
+    private final JS[] sortargs = new JS[2];
+    public int compare(java.lang.Object a, java.lang.Object b) throws JSExn.Wrapper {
+        try {
+            sortargs[0] = (JS)a; sortargs[1] = (JS)b;
+            return Script.toInt(sort.call(sortargs));
+        } catch (JSExn e) { throw new JSExn.Wrapper(e);
+        } finally { sortargs[0] = null; sortargs[1] = null; }
     }
-    
-    String coerceToString() throws JSExn { return JS.toString(join(",")); }
+
 }
index dc6d839..203fdc5 100644 (file)
@@ -44,7 +44,8 @@ import java.text.DateFormat;
  * @author Mike McCabe
  * @author Adam Megacz (many modifications
  */
-public class JSDate extends JS {
+public class JSDate extends JS.Immutable {
+    private static final JS.Method METHOD = new JS.Method();
 
     public JSDate() {
         if (thisTimeZone == null) {
@@ -55,10 +56,10 @@ public class JSDate extends JS {
         }
     }
 
-    String coerceToString() { return date_format(date, FORMATSPEC_FULL); }
+    public String coerceToString() { return date_format(date, FORMATSPEC_FULL); }
 
-    public JS callMethod(JS method, JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn {
-        switch(nargs) {
+    public JS call(JS method, JS[] args) throws JSExn {
+        switch(args.length) {
             case 0: {
                 //#switch(JS.toString(method))
                 case "toString": return JS.S(date_format(date, FORMATSPEC_FULL));
@@ -89,7 +90,7 @@ public class JSDate extends JS {
                 case "getUTCMilliseconds": return N(msFromTime(date));
                 case "getTimezoneOffset": return N(getTimezoneOffset(date));
                 //#end
-                return super.callMethod(method, a0, a1, a2, rest, nargs);
+                return super.call(method, args);
             }
             case 1: {
                 //#switch(JS.toString(method))
@@ -119,7 +120,7 @@ public class JSDate extends JS {
                 //#end
             }
         }
-        return super.callMethod(method, a0, a1, a2, rest, nargs);
+        return super.call(method, args);
     }
 
     public JS get(JS key) throws JSExn {
@@ -892,10 +893,10 @@ public class JSDate extends JS {
     private static double _toNumber(JS[] o, int index) throws JSExn { return JS.toDouble(o[index]); }
     private static double toDouble(double d) { return d; }
 
-    public JSDate(JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn {
+    public JSDate(JS[] args) throws JSExn {
 
         JSDate obj = this;
-        switch (nargs) {
+        switch (args.length) {
             case 0: {
                 obj.date = Now();
                 return;
@@ -905,18 +906,18 @@ public class JSDate extends JS {
                 if(isString(a0))
                     date = date_parseString(JS.toString(a0));
                 else
-                    date = _toNumber(a0);
+                    date = _toNumber(args[0]);
                 obj.date = TimeClip(date);
                 return;
             }
             default: {
                 // multiple arguments; year, month, day etc.
                 double array[] = new double[MAXARGS];
-                array[0] = toDouble(a0);
-                array[1] = toDouble(a1);
-                if (nargs >= 2) array[2] = toDouble(a2);
-                for(int i=0; i<nargs; i++) {
-                    double d = _toNumber(i==0?a0:i==1?a1:i==2?a2:rest[i-3]);
+                array[0] = Script.toDouble(args[0]);
+                array[1] = Script.toDouble(args[1]);
+                if (args.length >= 2) array[2] = Script.toDouble(args[2]);
+                for (int i=0; i < args.length; i++) {
+                    double d = _toNumber(args[i]);
                     if (d != d || Double.isInfinite(d)) {
                         obj.date = Double.NaN;
                         return;
index df905e8..2251cef 100644 (file)
@@ -9,9 +9,9 @@ import java.io.*;
 
 /** An exception which can be thrown and caught by JavaScript code */
 public class JSExn extends Exception { 
-    private Vec backtrace = new Vec();
+    private List backtrace = new ArrayList();
     private JS js; 
-    public JSExn(String s) { this(JS.S(s)); }
+    public JSExn(String s) { this(Script.S(s)); }
     public JSExn(JS js) { this(js,null); }
     public JSExn(JS js, Interpreter cx) { this.js = js; fill(cx); }
     
@@ -23,23 +23,23 @@ public class JSExn extends Exception {
     }
     public void printStackTrace() { printStackTrace(System.err); }
     public void printStackTrace(PrintWriter pw) {
-        for(int i=0; i<backtrace.size(); i++) pw.println("    at " + (String) backtrace.elementAt(i));
+        for(int i=0; i<backtrace.size(); i++) pw.println("    at " + (String) backtrace.get(i));
         super.printStackTrace(pw);
     }
     public void printStackTrace(PrintStream ps) {
-        for(int i=0; i<backtrace.size(); i++) ps.println("    at " + (String) backtrace.elementAt(i));
+        for(int i=0; i<backtrace.size(); i++) ps.println("    at " + (String) backtrace.get(i));
         super.printStackTrace(ps);
     }
-    public String toString() { return "JSExn: " + JS.debugToString(js); }
+    public String toString() { return "JSExn: " + Script.str(js); }
     public String getMessage() { return toString(); }
     public JS getObject() { return js; } 
     
-    void addBacktrace(String line) { backtrace.addElement(line); }
+    void addBacktrace(String line) { backtrace.add(line); }
     
     public static class Wrapper extends RuntimeException {
         public final JSExn e;
         public Wrapper(JSExn e) { this.e = e; }
-        public JSExn refill() {
+        public JSExn unwrap() {
             e.addBacktrace("[foreign code]");
             e.fill(null);
             return e;
@@ -49,7 +49,7 @@ public class JSExn extends Exception {
     public static class IO extends JSExn {
         public IO(java.io.IOException ioe) {
             super("ibex.io: " + ioe.toString());
-            JS.warn(ioe);
+            Script.warn(ioe);
         }
     }
 } 
index 5d8838e..e32839a 100644 (file)
@@ -4,13 +4,9 @@
 
 package org.ibex.js;
 
-import java.io.*;
-import org.ibex.util.*;
-
 /** A JavaScript function, compiled into bytecode */
-// FIXME: This shouldn't be public, needed for public add/delTrap (which is needed for the Template.java hack)
-public class JSFunction extends JS implements ByteCodes, Tokens, Task {
-
+class JSFunction extends JS.Immutable implements ByteCodes, Tokens {
+    private static final JS[] emptyArgs = new JS[0];
 
     // Fields and Accessors ///////////////////////////////////////////////
 
@@ -29,11 +25,17 @@ public class JSFunction extends JS implements ByteCodes, Tokens, Task {
 
     // Public //////////////////////////////////////////////////////////////////////////////
 
-    // FEATURE: make sure that this can only be called from the Scheduler...
-    /** if you enqueue a function, it gets invoked in its own pauseable context */
-    public void perform() throws JSExn {
-        Interpreter i = new Interpreter(this, true, new Interpreter.JSArgs(this));
-        i.resume();
+    // FIXME: what needs to be syncrhonized (if anything)?
+    private Interpreter runner = null;
+    public Object run(Object o) throws JSExn {
+        if (runner == null) runner = new Interpreter(this, true, emptyArgs);
+        Object ret = runner.run(o);
+        if (runner.f == null) runner = null;
+        return ret;
+    }
+    public void pause() throws NotPausableException {
+        if (runner == null) throw new NotPausableException();
+        runner.pause();
     }
 
     public JSFunction _cloneWithNewParentScope(JSScope s) {
@@ -48,13 +50,9 @@ public class JSFunction extends JS implements ByteCodes, Tokens, Task {
         return ret;
     }
 
-    /** Note: code gets run in an <i>unpauseable</i> context. */
-    public JS call(JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn {
-        Interpreter cx = new Interpreter(this, false, new Interpreter.JSArgs(a0,a1,a2,rest,nargs,this));
-        return cx.resume();
-    }
+    public JS call(JS[] args) throws JSExn { return (JS)new Interpreter(this, false, args).run(null); }
 
-    public JSScope getParentScope() { return parentScope; }
+    JSScope getParentScope() { return parentScope; }
 
     // Adding and Altering Bytecodes ///////////////////////////////////////////////////
 
index 71f8b18..6b092e0 100644 (file)
@@ -51,7 +51,7 @@ class JSMath extends JS {
                 break;
             }
         }
-        return super.callMethod(method, a0, a1, a2, rest, nargs);
+        return super.call(method, args);
     }
 
     public JS get(JS key) throws JSExn {
index 4419655..4d6bcba 100644 (file)
@@ -4,9 +4,11 @@
 
 package org.ibex.js;
 
-class JSPrimitive extends JS {
-    public JS callMethod(JS method, JS arg0, JS arg1, JS arg2, JS[] rest, int alength) throws JSExn {
-        //#switch(JS.toString(method))
+class JSPrimitive extends JS.Immutable {
+    private static final JS.Method METHOD = new JS.Method();
+
+    public JS callMethod(JS method, JS[] args) throws JSExn {
+        //#switch(Script.str(method))
         case "toFixed": throw new JSExn("toFixed() not implemented");
         case "toExponential": throw new JSExn("toExponential() not implemented");
         case "toPrecision": throw new JSExn("toPrecision() not implemented");
@@ -49,8 +51,8 @@ class JSPrimitive extends JS {
         }
         case "concat": {
             StringBuffer sb = new StringBuffer(slength*2).append(s);
-            for(int i=0;i<alength;i++) sb.append(i==0?arg0:i==1?arg1:i==2?arg2:rest[i-3]);
-            return JS.S(sb.toString());
+            for (int i=0; i < args.length; i++) sb.append(args[i]);
+            return Script.S(sb.toString());
         }
         case "indexOf": {
             String search = alength >= 1 ? JS.toString(arg0) : "null";
@@ -83,7 +85,7 @@ class JSPrimitive extends JS {
             return JS.S(s.substring(a,b));
         }
         //#end
-        return super.callMethod(method,arg0,arg1,arg2,rest,alength);
+        return super.call(method, args);
     }
     
     public JS get(JS key) throws JSExn {
index 617e99f..a987921 100644 (file)
@@ -5,7 +5,9 @@
 package org.ibex.js;
 
 /** A JavaScript regular expression object */
-public class JSRegexp extends JS {
+public class JSRegexp extends JS.Immutable {
+    private static final JS.Method METHOD = new JS.Method();
+
     private boolean global;
     private GnuRegexp.RE re;
     private int lastIndex;
@@ -19,8 +21,8 @@ public class JSRegexp extends JS {
             this.global = r.global;
             this.re = r.re;
             this.lastIndex = r.lastIndex;
-            this.pattern = pattern;
-            this.flags = flags;
+            this.pattern = r.pattern;
+            this.flags = r.flags;
         } else {
             String pattern = JS.toString(arg0);
             String sFlags = null;
@@ -41,10 +43,10 @@ public class JSRegexp extends JS {
         }
     }
 
-    public JS callMethod(JS method, JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn {
-        switch(nargs) {
+    public JS call(JS method, JS[] args) throws JSExn {
+        switch(args.length) {
             case 1: {
-                //#switch(JS.toString(method))
+                //#switch(JS.str(method))
                 case "exec": {
                     String s = JS.toString(a0);
                     int start = global ? lastIndex : 0;
@@ -62,32 +64,37 @@ public class JSRegexp extends JS {
                     lastIndex = match != null ? s.length() : match.getEndIndex();
                     return B(match != null);
                 }
-                case "toString": return JS.S(a0.coerceToString());
-                case "stringMatch": return stringMatch(a0,a1);
-                case "stringSearch": return stringSearch(a0,a1);
+                case "toString": return JS.S(args[0].coerceToString());
                 //#end
                 break;
             }
             case 2: {
-                //#switch(JS.toString(method))
-                case "stringReplace": return stringReplace(a0, a1,a2);
+                //#switch(JS.str(method))
+                case "stringMatch": return stringMatch(args[0], args[1]);
+                case "stringSearch": return stringSearch(args[0], args[1]);
+                //#end
+                break;
+            }
+            case 3: {
+                //#switch(JS.str(method))
+                case "stringReplace": return stringReplace(args[0], args[1], args[2]);
                 //#end
                 break;
             }
         }
-        return super.callMethod(method, a0, a1, a2, rest, nargs);
+        return super.call(method, args);
     }
     
     public JS get(JS key) throws JSExn {
-        //#switch(JS.toString(key))
+        //#switch(JS.str(key))
         case "exec": return METHOD;
         case "test": return METHOD;
         case "toString": return METHOD;
-        case "lastIndex": return N(lastIndex);
+        case "lastIndex": return JS.N(lastIndex);
         case "source": return pattern;
         case "global": return JS.B(global);
-        case "ignoreCase": return B(flags & GnuRegexp.RE.REG_ICASE);
-        case "multiline": return B(flags & GnuRegexp.RE.REG_MULTILINE);
+        case "ignoreCase": return JS.B(flags & GnuRegexp.RE.REG_ICASE);
+        case "multiline": return JS.B(flags & GnuRegexp.RE.REG_MULTILINE);
         //#end
         return super.get(key);
     }
@@ -104,11 +111,11 @@ public class JSRegexp extends JS {
   
     private static JS matchToExecResult(GnuRegexp.REMatch match, GnuRegexp.RE re, String s) {
         try {
-            JS ret = new JS.O();
-            ret.put(JS.S("index"), N(match.getStartIndex()));
-            ret.put(JS.S("input"),JS.S(s));
+            JS ret = new JS.Obj();
+            ret.put(JS.S("index"), JS.N(match.getStartIndex()));
+            ret.put(JS.S("input"), JS.S(s));
             int n = re.getNumSubs();
-            ret.put(JS.S("length"), N(n+1));
+            ret.put(JS.S("length"), JS.N(n+1));
             ret.put(ZERO,JS.S(match.toString()));
             for(int i=1;i<=n;i++) ret.put(JS.N(i),JS.S(match.toString(i)));
             return ret;
@@ -117,7 +124,7 @@ public class JSRegexp extends JS {
         }
     }
     
-    String coerceToString() {
+    public String coerceToString() {
         StringBuffer sb = new StringBuffer();
         sb.append('/');
         sb.append(pattern);
@@ -128,6 +135,7 @@ public class JSRegexp extends JS {
         return sb.toString();
     }
     
+    private static final JS[] execarg = new JS[1];
     static JS stringMatch(JS o, JS arg0) throws JSExn {
         String s = JS.toString(o);
         GnuRegexp.RE re;
@@ -143,11 +151,14 @@ public class JSRegexp extends JS {
             GnuRegexp.REMatch match = re.getMatch(s);
             return matchToExecResult(match,re,s);
         }
-        if(!regexp.global) return regexp.callMethod(JS.S("exec"), o, null, null, null, 1);
-        
-        JSArray ret = new JSArray();
+        try {
+            execarg[0] = o;
+            if(!regexp.global) return regexp.call(JS.S("exec"), execarg);
+        } finally { execarg[0] = null; }
+
         GnuRegexp.REMatch[] matches = re.getAllMatches(s);
-        for(int i=0;i<matches.length;i++) ret.addElement(JS.S(matches[i].toString()));
+        JSArray ret = new JSArray(matches.length);
+        for(int i=0;i<matches.length;i++) ret.add(JS.S(matches[i].toString()));
         regexp.lastIndex = matches.length > 0 ? matches[matches.length-1].getEndIndex() : s.length();
         return ret;
     }
@@ -156,7 +167,7 @@ public class JSRegexp extends JS {
         String s = JS.toString(o);
         GnuRegexp.RE re = arg0 instanceof JSRegexp ? ((JSRegexp)arg0).re : newRE(JS.toString(arg0),0);
         GnuRegexp.REMatch match = re.getMatch(s);
-        return match == null ? N(-1) : N(match.getStartIndex());
+        return match == null ? JS.N(-1) : JS.N(match.getStartIndex());
     }
     
     static JS stringReplace(JS o, JS arg0, JS arg1) throws JSExn {
@@ -202,32 +213,16 @@ public class JSRegexp extends JS {
             if(replaceFunc != null) {
                 int n = (regexp == null ? 0 : re.getNumSubs());
                 int numArgs = 3 + n;
-                JS[] rest = new JS[numArgs - 3];
-                JS a0 = JS.S(match.toString());
-                JS a1 = null;
-                JS a2 = null;
-                for(int j=1;j<=n;j++)
-                    switch(j) {
-                        case 1: a1 = JS.S(match.toString(j)); break;
-                        case 2: a2 = JS.S(match.toString(j)); break;
-                        default: rest[j - 3] = JS.S(match.toString(j)); break;
-                    }
-                switch(numArgs) {
-                    case 3:
-                        a1 = N(match.getStartIndex());
-                        a2 = JS.S(s);
-                        break;
-                    case 4:
-                        a2 = N(match.getStartIndex());
-                        rest[0] = JS.S(s);
-                        break;
-                    default:
-                        rest[rest.length - 2] = N(match.getStartIndex());
-                        rest[rest.length - 1] = JS.S(s);
-                }
+                JS[] args = new JS[3 + n];
+                args[0] = JS.S(match.toString());
+                args[1] = null;
+                args[2] = null;
+                for(int j=1;j<=n;j++) args[j] = JS.S(match.toString(j));
+                args[args.length - 2] = JS.N(match.getStartIndex());
+                args[args.length - 1] = JS.S(s);
 
                 // note: can't perform pausing operations in here
-                sb.append(JS.toString(replaceFunc.call(a0, a1, a2, rest, numArgs)));
+                sb.append(JS.toString(replaceFunc.call(args)));
 
             } else {
                 sb.append(mySubstitute(match,replaceString,s));
@@ -284,7 +279,7 @@ public class JSRegexp extends JS {
         String s = JS.toString(s_);
         int limit = nargs < 2 ? Integer.MAX_VALUE : JS.toInt(arg1);
         if(limit < 0) limit = Integer.MAX_VALUE;
-        if(limit == 0) return new JSArray();
+        if(limit == 0) return new JSArray(0);
         
         GnuRegexp.RE re = null;
         JSRegexp regexp = null;
@@ -341,5 +336,4 @@ public class JSRegexp extends JS {
             throw new JSExn(e.toString());
         }
     }
-
 }
index 6570840..5dee438 100644 (file)
@@ -5,7 +5,7 @@
 package org.ibex.js; 
 
 /** Implementation of a JavaScript Scope */
-class JSScope extends JS.O {
+class JSScope {
 
     private final int base;
     private final JS[] vars;
@@ -27,25 +27,36 @@ class JSScope extends JS.O {
         this.vars = new JS[size];
     }
     
+    final JS get(JS i) throws JSExn {
+        if(i==null) throw new NullPointerException();
+        try {
+            return get(Script.toInt(i));
+        } catch(ArrayIndexOutOfBoundsException e) { 
+            throw new JSExn("scope index out of range");
+        }
+    }
+    final void put(JS i, JS o) throws JSExn {
+        if(i==null) throw new NullPointerException();
+        try {
+            put(Script.toInt(i), o);
+        } catch(ArrayIndexOutOfBoundsException e) { 
+            throw new JSExn("scope index out of range");
+        }
+    }
     JS get(int i) throws JSExn { return i < base ? parent.get(i) : vars[i-base]; }
     void put(int i, JS o) throws JSExn { if(i < base) parent.put(i,o); else vars[i-base] = o; }
     
     JS getGlobal() { return parent.getGlobal(); }
     
-    private JSScope parentScope;
+    /*private JSScope parentScope;
 
     private static final JS NULL_PLACEHOLDER = new JS() { };
 
-    public JSScope(JSScope parentScope) { this(parentScope, 0, 0); }
+    public JSScope(JSScope parentScope) { this.parentScope = parentScope; }
     public void declare(JS s) throws JSExn { super.put(s, NULL_PLACEHOLDER); }
     public JSScope getParentScope() { return parentScope; }
 
     public JS get(JS key) throws JSExn {
-        if (key instanceof JSNumber) try {
-            return get(JS.toInt(key));
-        } catch(ArrayIndexOutOfBoundsException e) { 
-            throw new JSExn("scope index out of range");
-        }
         JS o = super.get(key);
         if (o != null) return o == NULL_PLACEHOLDER ? null : o;
         else return parentScope == null ? null : parentScope.get(key);
@@ -53,11 +64,6 @@ class JSScope extends JS.O {
 
     public boolean has(JS key) throws JSExn { return super.get(key) != null; }
     public void put(JS key, JS val) throws JSExn {
-       if (key instanceof JSNumber) try {
-            put(JS.toInt(key),val);
-        } catch(ArrayIndexOutOfBoundsException e) { 
-            throw new JSExn("scope index out of range");
-        }
         if (parentScope != null && !has(key)) parentScope.put(key, val);
         else super.put(key, val == null ? NULL_PLACEHOLDER : val);
     }
@@ -69,8 +75,8 @@ class JSScope extends JS.O {
     }
 
     public static class Global extends JSScope {
-        private final static JS NaN = JS.N(Double.NaN);
-        private final static JS POSITIVE_INFINITY = JS.N(Double.POSITIVE_INFINITY);
+        private final static JS NaN = N(Double.NaN);
+        private final static JS POSITIVE_INFINITY = N(Double.POSITIVE_INFINITY);
 
         public Global() { super(null); }
         public Global(JSScope p) { super(p); }
@@ -81,37 +87,36 @@ class JSScope extends JS.O {
         // We'll have to do something better with this when Scope is rewritten
         public JS get(JS key) throws JSExn {
             JS ret = _get(key);
-            if(ret == JS.METHOD) return new Interpreter.Stub(this,key);
+            if(ret == METHOD) return new Interpreter.Stub(this,key);
             return ret;
         }
         
         public JS _get(JS key) throws JSExn {
-            //#switch(JS.toString(key))
-            case "JS.NaN": return JS.NaN;
+            #switch(JS.str(key))
+            case "NaN": return NaN;
             case "Infinity": return POSITIVE_INFINITY;
             case "undefined": return null;
-            case "stringFromCharCode": return JS.METHOD;
-            case "parseInt": return JS.METHOD;
-            case "parseFloat": return JS.METHOD;
-            case "isJS.NaN": return JS.METHOD;
-            case "isFinite": return JS.METHOD;
-            case "decodeURI": return JS.METHOD;
-            case "decodeURIComponent": return JS.METHOD;
-            case "encodeURI": return JS.METHOD;
-            case "encodeURIComponent": return JS.METHOD;
-            case "escape": return JS.METHOD;
-            case "unescape": return JS.METHOD;
-           default: return super.get(key);
-            //#end
+            case "stringFromCharCode": return METHOD;
+            case "parseInt": return METHOD;
+            case "parseFloat": return METHOD;
+            case "isNaN": return METHOD;
+            case "isFinite": return METHOD;
+            case "decodeURI": return METHOD;
+            case "decodeURIComponent": return METHOD;
+            case "encodeURI": return METHOD;
+            case "encodeURIComponent": return METHOD;
+            case "escape": return METHOD;
+            case "unescape": return METHOD;
+            #end
             return super.get(key);
         }
 
         public JS callMethod(JS method, JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn {
-            //#switch(JS.toString(method))
+            #switch(JS.str(method))
             case "parseInt": return parseInt(a0, N(0));
             case "parseFloat": return parseFloat(a0);
-            case "isJS.NaN": { double d = JS.toDouble(a0); return d == d ? JS.F : JS.T; }
-            case "isFinite": { double d = JS.toDouble(a0); return (d == d && !Double.isInfinite(d)) ? JS.T : JS.F; }
+            case "isNaN": { double d = toDouble(a0); return d == d ? F : T; }
+            case "isFinite": { double d = toDouble(a0); return (d == d && !Double.isInfinite(d)) ? T : F; }
             case "decodeURI": throw new JSExn("unimplemented");
             case "decodeURIComponent": throw new JSExn("unimplemented");
             case "encodeURI": throw new JSExn("unimplemented");
@@ -119,9 +124,8 @@ class JSScope extends JS.O {
             case "escape": throw new JSExn("unimplemented");
             case "unescape": throw new JSExn("unimplemented");
             case "parseInt": return parseInt(a0, a1);
-            default: return super.callMethod(method, a0, a1, a2, rest, nargs);
-            //#end
-            return super.callMethod(method,a0,a1,a2,rest,nargs);
+            #end
+            return super.callMethod(method, a0, a1, a2, rest, nargs);
         }
 
         private JS parseInt(JS arg, JS r) throws JSExn {
@@ -131,7 +135,7 @@ class JSScope extends JS.O {
             int length = s.length();
             int sign = 1;
             long n = 0;
-            if(radix != 0 && (radix < 2 || radix > 36)) return JS.NaN;
+            if(radix != 0 && (radix < 2 || radix > 36)) return NaN;
             while(start < length && Character.isWhitespace(s.charAt(start))) start++;
             if((length >= start+1) && (s.charAt(start) == '+' || s.charAt(start) == '-')) {
                 sign = s.charAt(start) == '+' ? 1 : -1;
@@ -148,7 +152,7 @@ class JSScope extends JS.O {
                 }
             }
             if(radix == 0) radix = 10;
-            if(length == start || Character.digit(s.charAt(start),radix) == -1) return JS.NaN;
+            if(length == start || Character.digit(s.charAt(start),radix) == -1) return NaN;
             // try the fast way first
             try {
                 String s2 = start == 0 ? s : s.substring(start);
@@ -159,7 +163,7 @@ class JSScope extends JS.O {
                 int digit = Character.digit(s.charAt(i),radix);
                 if(digit < 0) break;
                 n = n*radix + digit;
-                if(n < 0) return JS.NaN; // overflow;
+                if(n < 0) return NaN; // overflow;
             }
             if(n <= Integer.MAX_VALUE) return JS.N(sign*(int)n);
             return JS.N((long)sign*n);
@@ -179,8 +183,8 @@ class JSScope extends JS.O {
                 } catch(NumberFormatException e) { }
                 end--;
             }
-            return JS.NaN;
+            return NaN;
         }
-    }
+    }*/
 }
 
index c98a94e..2e40da8 100644 (file)
@@ -5,6 +5,7 @@
 package org.ibex.js;
 
 import org.ibex.util.*;
+import java.util.*;
 
 class JSString extends JSPrimitive {
     final String s;
@@ -16,13 +17,13 @@ class JSString extends JSPrimitive {
         if(o instanceof JSString) {
             return ((JSString)o).s.equals(s);
         } else if(o instanceof JSNumber) {
-            return o.jsequals(this);
+            return o.equals(this);
         } else {
             return false;
         }
     }
     
-    private final static Hash internHash = new Hash();
+    private final static Map internHash = new HashMap();
     static synchronized JS intern(String s) {
         synchronized(internHash) {
             JS js = (JS)internHash.get(s);
@@ -35,5 +36,5 @@ class JSString extends JSPrimitive {
         protected void finalize() { synchronized(internHash) { internHash.put(s,null); } }
     }
     
-    String coerceToString() { return s; }
+    public String coerceToString() { return s; }
 }
index 1931428..3bf7852 100644 (file)
@@ -4,8 +4,9 @@
 
 package org.ibex.js;
 
-import org.ibex.util.*;
 import java.io.*;
+import java.util.*;
+import org.ibex.util.*;
 
 /**
  *  Parses a stream of lexed tokens into a tree of JSFunction's.
@@ -136,14 +137,14 @@ class Parser extends Lexer implements ByteCodes {
     }
 
     // Local variable management
-    Vec scopeStack = new Vec();
+    List scopeStack = new ArrayList();
     static class ScopeInfo {
         int base;
         int end;
         int newScopeInsn;
-        Hash mapping = new Hash();
+        Map mapping = new HashMap();
     }
-    Hash globalCache = new Hash();
+    Map globalCache = new HashMap();
     JS scopeKey(String name) {
         if(globalCache.get(name) != null) return null;
         for(int i=scopeStack.size()-1;i>=0;i--) {
diff --git a/src/org/ibex/js/Script.java b/src/org/ibex/js/Script.java
new file mode 100644 (file)
index 0000000..3fa9671
--- /dev/null
@@ -0,0 +1,129 @@
+package org.ibex.js;
+
+import java.io.Reader;
+import java.io.IOException;
+import org.ibex.util.*;
+
+public class Script {
+    /** returns a Pausable which will restart the context;
+     *  expects a value to be pushed onto the stack when unpaused. */
+    public static Pausable pause() throws Pausable.NotPausableException {
+        Interpreter i = Interpreter.current();
+        i.pause();
+        return i;
+    }
+
+    /** Coerce a JS object into a boolean. */
+    public static boolean toBoolean(JS o) {
+        if(o == null) return false;
+        if(o instanceof JSNumber) return ((JSNumber)o).toBoolean();
+        if(o instanceof JSString) return ((JSString)o).s.length() != 0;
+        return true;
+    }
+
+    //#repeat long/int/double/float toLong/toInt/toDouble/toFloat Long/Integer/Double/Float parseLong/parseInt/parseDouble/parseFloat
+    /** Coerce a JS object to a long. */
+    public static long toLong(JS o) throws JSExn {
+        if(o == null) return 0;
+        if(o instanceof JSNumber) return ((JSNumber)o).toLong();
+        if(o instanceof JSString) return Long.parseLong(o.coerceToString());
+        throw new JSExn("can't coerce a " + o.getClass().getName() + " to a number");
+    }
+    //#end
+    
+    /** Coerce a JS object to a String. */
+    public static String toString(JS o) throws JSExn {
+        if(o == null) return "null";
+        return o.coerceToString();
+    }
+
+    
+    public static boolean isInt(JS o) {
+        if(o == null) return true;
+        if(o instanceof JSNumber.I) return true;
+        if(o instanceof JSNumber.B) return false;
+        if(o instanceof JSNumber) {
+            JSNumber n = (JSNumber) o;
+            return n.toInt() == n.toDouble();
+        }
+        if(o instanceof JSString) {
+            String s = ((JSString)o).s;
+            for(int i=0;i<s.length();i++)
+                if(s.charAt(i) < '0' || s.charAt(i) > '9') return false;
+            return true;
+        }
+        return false;
+    }
+    
+    public static boolean isString(JS o) {
+        if(o instanceof JSString) return true;
+        return false;
+    }
+    
+    // Instance Methods ////////////////////////////////////////////////////////////////////
+    public final static JS NaN = new JSNumber.D(Double.NaN);
+    public final static JS ZERO = new JSNumber.I(0);
+    public final static JS ONE = new JSNumber.I(1);
+    public final static JS MATH = new JSMath();
+        
+    public static final JS T = new JSNumber.B(true);
+    public static final JS F = new JSNumber.B(false);
+
+    public static final JS B(boolean b) { return b ? T : F; }
+    public static final JS B(int i) { return i==0 ? F : T; }
+    
+    private static final int CACHE_SIZE = 65536 / 4; // must be a power of two
+    private static final JSString[] stringCache = new JSString[CACHE_SIZE];
+    public static final JS S(String s) {
+        if(s == null) return null;
+        int slot = s.hashCode()&(CACHE_SIZE-1);
+        JSString ret = stringCache[slot];
+        if(ret == null || !ret.s.equals(s)) stringCache[slot] = ret = new JSString(s);
+        return ret;
+    }
+    public static final JS S(String s, boolean intern) { return intern ? JSString.intern(s) : S(s); }
+
+    public static final JS N(double d) { return new JSNumber.D(d); }
+    public static final JS N(long l) { return new JSNumber.L(l); }
+    
+    public static final JS N(Number n) {
+        if(n instanceof Integer) return N(n.intValue());
+        if(n instanceof Long) return N(n.longValue());
+        return N(n.doubleValue());
+    }
+
+    private static final JSNumber.I negone = new JSNumber.I(-1);
+    private static final JSNumber.I[] icache = new JSNumber.I[128];
+    static { for (int i=0; i < icache.length; i++) icache[i] = new JSNumber.I(i); }
+    public static final JS N(int i) {
+        return i == -1 ? negone : i >= 0 && i < icache.length ? icache[i] : new JSNumber.I(i);
+    }
+
+    /** Internal method for coercing to String without throwing a JSExn. */
+    static String str(JS o) {
+        try { return toString(o); }
+        catch(JSExn e) { return o.toString(); }
+    }
+
+
+    // Static Interpreter Control Methods ///////////////////////////////////////////////////////////////
+
+    public static JS fromReader(String sourceName, int firstLine, Reader source) throws IOException {
+        return Parser.fromReader(sourceName, firstLine, source); }
+
+    // FIXME
+    public static JS cloneWithNewGlobalScope(JS js, JS s) {
+        if(js instanceof JSFunction)
+            return ((JSFunction)js)._cloneWithNewParentScope(new JSScope.Top(s));
+        else
+            return js;
+    }
+
+    /** log a message with the current JavaScript sourceName/line */
+    public static void log(Object message) { info(message); }
+    public static void debug(Object message) { Log.debug(Interpreter.getSourceName() + ":" + Interpreter.getLine(), message); }
+    public static void info(Object message) { Log.info(Interpreter.getSourceName() + ":" + Interpreter.getLine(), message); }
+    public static void warn(Object message) { Log.warn(Interpreter.getSourceName() + ":" + Interpreter.getLine(), message); }
+    public static void error(Object message) { Log.error(Interpreter.getSourceName() + ":" + Interpreter.getLine(), message); }
+}
index 470bfc2..ea436b9 100644 (file)
@@ -5,9 +5,10 @@
 package org.ibex.js;
 
 import java.io.*;
+import org.ibex.util.*;
 
-public class Test extends JS {
-    static JS.UnpauseCallback up = null;
+public class Test extends JS.Obj {
+    private static final JS.Method METHOD = new JS.Method();
     static String action;
     
     public static void main(String[] args) throws Exception {
@@ -18,30 +19,29 @@ public class Test extends JS {
         s.put(JS.S("sys"),new Test());
         f = JS.cloneWithNewGlobalScope(f,s);
         //JS ret = f.call(null,null,null,null,0);
-        Interpreter i = new Interpreter((JSFunction)f, true, new Interpreter.JSArgs(f));
-        JS ret = i.resume();
-        while(up != null) {
-            JS.UnpauseCallback up = Test.up; Test.up = null;
-            if("throw".equals(action)) ret = up.unpause(new JSExn("this was thrown to a paused context"));
-            else if("bgget".equals(action)) ret = up.unpause(JS.S("I'm returning this from a get request"));
+        Interpreter i = new Interpreter((JSFunction)f, true, new JS[0]);
+        JS ret = (JS)i.run(null);
+        try { while(true) {
+            if("throw".equals(action)) ret = (JS)i.run(new JSExn("this was thrown to a paused context"));
+            else if("bgget".equals(action)) ret = (JS)i.run(Script.S("I'm returning this from a get request"));
             else {
                 System.out.println("got a background put " + action);
-                ret = up.unpause();
+                ret = (JS)i.run(null);
             }
-        }
-        System.out.println("Script returned: " + JS.toString(ret));
+        } } catch (Pausable.AlreadyRunningException e) {}
+        System.out.println("Script returned: " + Script.toString(ret));
     }
     
     public JS get(JS key) throws JSExn {
-        if(!JS.isString(key)) return null;
-        if("print".equals(JS.toString(key))) return METHOD;
-        if("clone".equals(JS.toString(key))) return METHOD;
-        if("firethis".equals(JS.toString(key))) return METHOD;
-        if("bgget".equals(JS.toString(key))) {
+        if(!Script.isString(key)) return null;
+        if("print".equals(Script.toString(key))) return METHOD;
+        if("clone".equals(Script.toString(key))) return METHOD;
+        if("firethis".equals(Script.toString(key))) return METHOD;
+        if("bgget".equals(Script.toString(key))) {
             action = "bgget";
             try {
-                up = JS.pause();
-            } catch(NotPauseableException e) {
+                Script.pause();
+            } catch(Pausable.NotPausableException e) {
                 throw new Error("should never happen");
             }
             return null;
@@ -53,8 +53,8 @@ public class Test extends JS {
         if("bgput".equals(JS.toString(key))) {
             action = JS.toString(val);
             try {
-                up = JS.pause();
-            } catch(NotPauseableException e) {
+                Script.pause();
+            } catch(Pausable.NotPausableException e) {
                 throw new Error("should never happen");
             }
             return;
index f2200fb..580ed69 100644 (file)
@@ -1,7 +1,4 @@
-// Copyright 2000-2005 the Contributors, as shown in the revision logs.
-// Licensed under the Apache Public Source License 2.0 ("the License").
-// You may not use this file except in compliance with the License.
-
+// Copyright 2004 Adam Megacz, see the COPYING file for licensing [GPL]
 package org.ibex.js;
 
 /**