[re]-merged in Brians stuff
authoradam <adam@megacz.com>
Mon, 27 Dec 2004 10:27:50 +0000 (10:27 +0000)
committeradam <adam@megacz.com>
Mon, 27 Dec 2004 10:27:50 +0000 (10:27 +0000)
darcs-hash:20041227102750-5007d-5b154a218c5f7c1def8f15fcce001fa2117255b6.gz

20 files changed:
src/org/ibex/js/ByteCodes.java
src/org/ibex/js/Directory.java
src/org/ibex/js/Fountain.java
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/JSReflection.java
src/org/ibex/js/JSRegexp.java
src/org/ibex/js/JSScope.java
src/org/ibex/js/Lexer.java
src/org/ibex/js/Parser.java
src/org/ibex/js/PropertyFile.java
src/org/ibex/js/SOAP.java
src/org/ibex/js/Tokens.java
src/org/ibex/js/Trap.java
src/org/ibex/js/XMLRPC.java

index e8170f1..84c5926 100644 (file)
@@ -24,10 +24,11 @@ interface ByteCodes {
     /** if given a non-null argument declare its argument in the current scope and push
         it to the stack, else, declares the element on the top of the stack and leaves it
         there */
-    public static final byte DECLARE = -6;       
+    //public static final byte DECLARE = -6;       
 
     /** push a reference to the current scope onto the stack */
-    public static final byte TOPSCOPE = -7;
+    // FIXME: Document this
+    public static final byte GLOBALSCOPE = -7;
 
     /** if given a null literal pop two elements off the stack; push stack[-1].get(stack[top])
         else pop one element off the stack, push stack[top].get(literal) */
@@ -85,10 +86,14 @@ interface ByteCodes {
     /** finish a finally block and carry out whatever instruction initiated the finally block */
     public static final byte MAKE_GRAMMAR = -25;
     
+    // FIXME: Document these and NEWSCOPE/OLDSCOPE/TOPSCOPE changes
+    public static final byte SCOPEGET = -26;
+    public static final byte SCOPEPUT = -27;    
+    
     public static final String[] bytecodeToString = new String[] {
-        "", "", "LITERAL", "ARRAY", "OBJECT", "NEWFUNCTION", "DECLARE", "TOPSCOPE",
+        "", "", "LITERAL", "ARRAY", "OBJECT", "NEWFUNCTION", "DECLARE", "GLOBALSCOPE",
         "GET", "GET_PRESERVE", "PUT", "JT", "JF", "JMP", "POP", "CALL", "PUSHKEYS",
         "SWAP", "NEWSCOPE", "OLDSCOPE", "DUP", "LABEL", "LOOP", "CALLMETHOD",
-        "FINALLY_DONE", "MAKE_GRAMMAR"
+        "FINALLY_DONE", "MAKE_GRAMMAR", "SCOPEGET", "SCOPEPUT"
     };
 }
index 17adab7..98469d5 100644 (file)
@@ -43,6 +43,7 @@ public class Directory extends JS {
      *  Create the directory object.  Existing directories will be
      *  preserved; if a file is present it will be obliterated.
      */ 
+
     public Directory(File f) throws IOException {
         this.f = f;
         if (!f.exists()) new Directory(new File(f.getParent()));
@@ -59,38 +60,34 @@ public class Directory extends JS {
         f.delete();
     }
 
-    public void put(Object key0, Object val) throws JSExn {
+    public void put(JS key0, JS val) throws JSExn {
         try {
             if (key0 == null) return;
             String key = toString(key0);
             File f2 = new File(f.getAbsolutePath() + File.separatorChar + FileNameEncoder.encode(key));
             destroy(f2);
             if (val == null) return;
-            if (val instanceof JS) {
+            if (val instanceof JSPrimitive) {
+                OutputStream out = new FileOutputStream(f2);
+                Writer w = new OutputStreamWriter(out);
+                w.write(toString(val));
+                w.flush();
+                out.close();
+            } else {
                 Directory d2 = new Directory(f2);
-                Enumeration e = ((JS)val).keys();
+                JS.Enumeration e = val.keys();
                 while(e.hasMoreElements()) {
-                    String k = (String)e.nextElement();
-                    Object v = ((JS)val).get(k);
+                    JS k = e.nextElement();
+                    JS v = val.get(k);
                     d2.put(k, v);
                 }
-            } else {
-                OutputStream out = new FileOutputStream(f2);
-                try {
-                    Writer w = new OutputStreamWriter(out);
-                    w.write(toString(val));
-                    w.flush();
-                } finally {
-                    out.close();
-                }
             }
         } catch (IOException ioe) {
             throw new JSExn.IO(ioe);
         }
     }
 
-    public Object get(Object key0) throws JSExn {
-        FileInputStream fis = null;
+    public JS get(JS key0) throws JSExn {
         try {
             if (key0 == null) return null;
             String key = toString(key0);
@@ -99,30 +96,23 @@ public class Directory extends JS {
             if (f2.isDirectory()) return new Directory(f2);
             char[] chars = new char[((int)f2.length()) * 2];
             int numchars = 0;
-            fis = new FileInputStream(f2);
-            Reader r = new InputStreamReader(fis);
+            Reader r = new InputStreamReader(new FileInputStream(f2));
             while(true) {
                 int numread = r.read(chars, numchars, chars.length - numchars);
-                if (numread == -1) return new String(chars, 0, numchars);
+                if (numread == -1) return JS.S(new String(chars, 0, numchars));
                 numchars += numread;
             }
         } catch (IOException ioe) {
             throw new JSExn.IO(ioe);
-        } finally {
-            try {
-                if (fis != null) fis.close();
-            } catch (IOException ioe) {
-                throw new JSExn.IO(ioe);
-            }
         }
     }
 
     public Enumeration keys() {
         final String[] elements = f.list();
-        return new Enumeration() {
+        return new Enumeration(null) {
                 int i = 0;
-                public boolean hasMoreElements() { return i < elements.length; }
-                public Object nextElement() { return FileNameEncoder.decode(elements[i++]); }
+                public boolean _hasMoreElements() { return i < elements.length; }
+                public JS _nextElement() { return JS.S(FileNameEncoder.decode(elements[i++])); }
             };
     }
 }
index 264f2ff..acf617a 100644 (file)
@@ -12,7 +12,7 @@ import org.ibex.net.*;
  *   be totally independent of the others (ie separate stream position
  *   and state) although they draw from the same data source.
  */
-public abstract class Fountain extends JS.Cloneable {
+public abstract class Fountain extends JS.O implements JS.Cloneable {
 
     // Public Interface //////////////////////////////////////////////////////////////////////////////
 
@@ -37,7 +37,7 @@ public abstract class Fountain extends JS.Cloneable {
     /** HTTP or HTTPS resource */
     public static class HTTP extends Fountain {
         private String url;
-        public String toString() { return "Stream.HTTP:" + url; }
+        //public String toString() { return "Stream.HTTP:" + url; }
         public HTTP(String url) { while (url.endsWith("/")) url = url.substring(0, url.length() - 1); this.url = url; }
         public Object _get(Object key) { return new HTTP(url + "/" + (String)key); }
         public String getCacheKey(Vec path) throws NotCacheableException { return url; }
@@ -58,7 +58,7 @@ public abstract class Fountain extends JS.Cloneable {
     public static class File extends Fountain {
         private String path;
         public File(String path) { this.path = path; }
-        public String toString() { return "file:" + path; }
+        //public String toString() { return "file:" + path; }
         public String getCacheKey() throws NotCacheableException { throw new NotCacheableException(); /* already on disk */ }
         public InputStream getInputStream() throws IOException { return new FileInputStream(path); }
         public Object _get(Object key) { return new File(path + java.io.File.separatorChar + (String)key); }
index 94ccab5..d19cd2e 100644 (file)
@@ -6,8 +6,6 @@ import java.util.*;
 
 /** Encapsulates a single JS interpreter (ie call stack) */
 class Interpreter implements ByteCodes, Tokens {
-
-
     // Thread-Interpreter Mapping /////////////////////////////////////////////////////////////////////////
 
     static Interpreter current() { return (Interpreter)threadToInterpreter.get(Thread.currentThread()); }
@@ -19,20 +17,34 @@ class Interpreter implements ByteCodes, Tokens {
     int pausecount;               ///< the number of times pause() has been invoked; -1 indicates unpauseable
     JSFunction f = null;          ///< the currently-executing JSFunction
     JSScope scope;                ///< the current top-level scope (LIFO stack via NEWSCOPE/OLDSCOPE)
-    Vec stack = new Vec();        ///< the object stack
+    final Stack stack = new Stack(); ///< the object stack
     int pc = 0;                   ///< the program counter
 
-    Interpreter(JSFunction f, boolean pauseable, JSArray args) { this(f, pauseable, args, true); }
-    Interpreter(JSFunction f, boolean pauseable, JSArray args, boolean wrap) {
-        stack.push(new Interpreter.CallMarker(this));    // the "root function returned" marker -- f==null
+    Interpreter(JSFunction f, boolean pauseable, JSArgs args) {
         this.f = f;
         this.pausecount = pauseable ? 0 : -1;
-        this.scope = wrap ? new JSScope(f.parentScope) : f.parentScope;
-        stack.push(args);
+        this.scope = f.parentScope;
+        try {
+            stack.push(new CallMarker(null));    // the "root function returned" marker -- f==null
+            stack.push(args);
+        } catch(JSExn e) {
+            throw new Error("should never happen");
+        }
+    }
+    
+    Interpreter(Trap t, JS val, boolean pauseOnPut) {
+        this.pausecount = -1;
+        try {
+            setupTrap(t,val,new TrapMarker(null,t,val,pauseOnPut));
+        } catch(JSExn e) {
+            throw new Error("should never happen");
+        }
     }
     
     /** this is the only synchronization point we need in order to be threadsafe */
-    synchronized Object resume() throws JSExn {
+    synchronized JS resume() throws JSExn {
+        if(f == null) throw new RuntimeException("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);
@@ -56,15 +68,13 @@ class Interpreter implements ByteCodes, Tokens {
 
     private static JSExn je(String s) { return new JSExn(getSourceName() + ":" + getLine() + " " + s); }
 
-    // FIXME: double check the trap logic
-    private Object run() throws JSExn {
+    private JS run() throws JSExn {
 
         // if pausecount changes after a get/put/call, we know we've been paused
         final int initialPauseCount = pausecount;
 
         OUTER: for(;; pc++) {
         try {
-            if (f == null) return stack.pop();
             int op = f.op[pc];
             Object arg = f.arg[pc];
             if(op == FINALLY_DONE) {
@@ -75,28 +85,26 @@ class Interpreter implements ByteCodes, Tokens {
                 arg = fd.arg;
             }
             switch(op) {
-            case LITERAL: stack.push(arg); break;
-            case OBJECT: stack.push(new JS()); break;
-            case ARRAY: stack.push(new JSArray(JS.toNumber(arg).intValue())); break;
-            case DECLARE: scope.declare((String)(arg==null ? stack.peek() : arg)); if(arg != null) stack.push(arg); break;
-            case TOPSCOPE: stack.push(scope); break;
-            case JT: if (JS.toBoolean(stack.pop())) pc += JS.toNumber(arg).intValue() - 1; break;
-            case JF: if (!JS.toBoolean(stack.pop())) pc += JS.toNumber(arg).intValue() - 1; break;
-            case JMP: pc += JS.toNumber(arg).intValue() - 1; break;
+            case LITERAL: stack.push((JS)arg); break;
+            case OBJECT: stack.push(new JS.O()); break;
+            case ARRAY: stack.push(new JSArray(JS.toInt((JS)arg))); break;
+            //case DECLARE: scope.declare((JS)(arg==null ? stack.peek() : arg)); if(arg != null) stack.push((JS)arg); break;
+            case JT: if (JS.toBoolean(stack.pop())) pc += JS.toInt((JS)arg) - 1; break;
+            case JF: if (!JS.toBoolean(stack.pop())) pc += JS.toInt((JS)arg) - 1; break;
+            case JMP: pc += JS.toInt((JS)arg) - 1; break;
             case POP: stack.pop(); break;
-            case SWAP: {
-                int depth = (arg == null ? 1 : JS.toInt(arg));
-                Object save = stack.elementAt(stack.size() - 1);
-                for(int i=stack.size() - 1; i > stack.size() - 1 - depth; i--)
-                    stack.setElementAt(stack.elementAt(i-1), i);
-                stack.setElementAt(save, stack.size() - depth - 1);
-                break; }
+            case SWAP: stack.swap(); break;
             case DUP: stack.push(stack.peek()); break;
-            case NEWSCOPE: scope = new JSScope(scope); break;
-            case OLDSCOPE: scope = scope.getParentScope(); break;
-            case ASSERT:
-                if (JS.checkAssertions && !JS.toBoolean(stack.pop()))
-                    throw je("ibex.assertion.failed" /*FEATURE: line number*/); break;
+            case NEWSCOPE: {
+                int n = JS.toInt((JS)arg);
+                scope = new JSScope(scope,(n>>>16)&0xffff,(n>>>0)&0xffff);
+                break;
+            }
+            case OLDSCOPE: scope = scope.parent; break;
+            case GLOBALSCOPE: stack.push(scope.getGlobal()); break;
+            case SCOPEGET: stack.push(scope.get((JS)arg)); break;
+            case SCOPEPUT: scope.put((JS)arg,stack.peek()); break;
+            case ASSERT: if (!JS.toBoolean(stack.pop())) throw je("ibex.assertion.failed"); break;
             case BITNOT: stack.push(JS.N(~JS.toLong(stack.pop()))); break;
             case BANG: stack.push(JS.B(!JS.toBoolean(stack.pop()))); break;
             case NEWFUNCTION: stack.push(((JSFunction)arg)._cloneWithNewParentScope(scope)); break;
@@ -105,32 +113,28 @@ class Interpreter implements ByteCodes, Tokens {
             case TYPEOF: {
                 Object o = stack.pop();
                 if (o == null) stack.push(null);
-                else if (o instanceof JS) stack.push("object");
-                else if (o instanceof String) stack.push("string");
-                else if (o instanceof Number) stack.push("number");
-                else if (o instanceof Boolean) stack.push("boolean");
-                else throw new Error("this should not happen");
+                else if (o instanceof JSString) stack.push(JS.S("string"));
+                else if (o instanceof JSNumber.B) stack.push(JS.S("boolean"));
+                else if (o instanceof JSNumber) stack.push(JS.S("number"));
+                else stack.push(JS.S("object"));
                 break;
             }
 
             case PUSHKEYS: {
-                Object o = stack.peek();
-                Enumeration e = ((JS)o).keys();
-                JSArray a = new JSArray();
-                while(e.hasMoreElements()) a.addElement(e.nextElement());
-                stack.push(a);
+                JS o = 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(Boolean.TRUE);
+                stack.push(JS.T);
                 break;
 
             case BREAK:
             case CONTINUE:
-                while(stack.size() > 0) {
-                    Object o = stack.pop();
+                while(!stack.empty()) {
+                    JS o = 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
@@ -142,9 +146,9 @@ class Interpreter implements ByteCodes, Tokens {
                     if (o instanceof LoopMarker) {
                         if (arg == null || arg.equals(((LoopMarker)o).label)) {
                             int loopInstructionLocation = ((LoopMarker)o).location;
-                            int endOfLoop = ((Integer)f.arg[loopInstructionLocation]).intValue() + loopInstructionLocation;
+                            int endOfLoop = JS.toInt((JS)f.arg[loopInstructionLocation]) + loopInstructionLocation;
                             scope = ((LoopMarker)o).scope;
-                            if (op == CONTINUE) { stack.push(o); stack.push(Boolean.FALSE); }
+                            if (op == CONTINUE) { stack.push(o); stack.push(JS.F); }
                             pc = op == BREAK ? endOfLoop - 1 : loopInstructionLocation;
                             continue OUTER;
                         }
@@ -162,8 +166,8 @@ class Interpreter implements ByteCodes, Tokens {
             }
 
             case RETURN: {
-                Object retval = stack.pop();
-                while(stack.size() > 0) {
+                JS retval = stack.pop();
+                while(!stack.empty()) {
                     Object o = stack.pop();
                     if (o instanceof TryMarker) {
                         if(((TryMarker)o).finallyLoc < 0) continue;
@@ -173,209 +177,193 @@ class Interpreter implements ByteCodes, Tokens {
                         pc = ((TryMarker)o).finallyLoc - 1;
                         continue OUTER;
                     } else if (o instanceof CallMarker) {
-                        if (scope instanceof Trap.TrapScope) { // handles return component of a read trap
-                            Trap.TrapScope ts = (Trap.TrapScope)scope;
-                            if (retval != null && retval instanceof Boolean && ((Boolean)retval).booleanValue())
-                                ts.cascadeHappened = true;
-                            if (!ts.cascadeHappened) {
-                                ts.cascadeHappened = true;
-                                Trap t = ts.t.next;
-                                while (t != null && t.f.numFormalArgs == 0) t = t.next;
-                                if (t == null) {
-                                    ((JS)ts.t.trapee).put(ts.t.name, ts.val);
-                                    if (pausecount > initialPauseCount) { pc++; return null; }   // we were paused
-                                } else {
-                                    stack.push(o);
-                                    JSArray args = new JSArray();
-                                    args.addElement(ts.val);
-                                    stack.push(args);
-                                    f = t.f;
-                                    scope = new Trap.TrapScope(f.parentScope, t, ts.val);
-                                    pc = -1;
+                        boolean didTrapPut = false;
+                        if (o instanceof TrapMarker) { // handles return component of a write trap
+                            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();
+                                }
+                                if(t != null) {
+                                    tm.t = t; // we reuse the old trap marker
+                                    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);
                                 }
                             }
                         }
-                        scope = ((CallMarker)o).scope;
-                        pc = ((CallMarker)o).pc - 1;
-                        f = (JSFunction)((CallMarker)o).f;
-                        stack.push(retval);
+                        CallMarker cm = (CallMarker) o;
+                        scope = cm.scope;
+                        pc = cm.pc - 1;
+                        f = cm.f;
+                        if (didTrapPut) {
+                            if (((TrapMarker)cm).pauseOnPut) { pc++; return ((TrapMarker)cm).val; }
+                            if (pausecount > initialPauseCount) { pc++; return null; }   // we were paused
+                        } else {
+                            stack.push(retval);
+                        }
+                        if (f == null) return retval;
                         continue OUTER;
                     }
                 }
                 throw new Error("error: RETURN invoked but couldn't find a CallMarker!");
             }
-
-            case PUT: {
-                Object val = stack.pop();
-                Object key = stack.pop();
-                Object target = stack.peek();
-                if (target == null)
-                    throw je("tried to put a value to the " + key + " property on the null value");
-                if (!(target instanceof JS))
-                    throw je("tried to put a value to the " + key + " property on a " + target.getClass().getName());
-                if (key == null)
-                    throw je("tried to assign \"" + (val==null?"(null)":val.toString()) + "\" to the null key");
-
-                Trap t = null;
-                if (target instanceof JSScope && key.equals("cascade")) {
-                   Trap.TrapScope ts = null;
-                    JSScope p = (JSScope)target; // search the scope-path for the trap
-                   if (target instanceof Trap.TrapScope) {
-                       ts = (Trap.TrapScope)target;
-                   }
-                   else {
-                       while (ts == null && p.getParentScope() != null) {
-                           p = p.getParentScope();
-                           if (p instanceof Trap.TrapScope) {
-                               ts = (Trap.TrapScope)p;
-                           }
-                       }
-                   }
-                   t = ts.t.next;
-                   ts.cascadeHappened = true;
-                    while (t != null && t.f.numFormalArgs == 0) t = t.next;
-                    if (t == null) { target = ts.t.trapee; key = ts.t.name; }
-
-                } else if (target instanceof Trap.TrapScope && key.equals(((Trap.TrapScope)target).t.name)) {
-                    throw je("tried to put to " + key + " inside a trap it owns; use cascade instead"); 
-
-                } else if (target instanceof JS) {
-                    if (target instanceof JSScope) {
-                        JSScope p = (JSScope)target; // search the scope-path for the trap
-                        t = p.getTrap(key);
-                        while (t == null && p.getParentScope() != null) { p = p.getParentScope(); t = p.getTrap(key); }
+                
+            case CASCADE: {
+                boolean write = JS.toBoolean((JS)arg);
+                JS val = write ? stack.pop() : null;
+                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;
+                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();
+                // 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(write) {
+                    tm.cascadeHappened = true;
+                    stack.push(val);
+                }
+                if(t != null) {
+                    setupTrap(t,val,new TrapMarker(this,t,val,tm.pauseOnPut));
+                    pc--; // we increment later
+                } else {
+                    if(write) {
+                        if (tm.pauseOnPut) { pc++; return val; }
+                        target.put(key,val);
                     } else {
-                        t = ((JS)target).getTrap(key);
+                        JS ret = target.get(key);
+                        if (ret == JS.METHOD) ret = new Stub(target, key);
+                        stack.push(ret);                        
                     }
-                    while (t != null && t.f.numFormalArgs == 0) t = t.next; // find the first write trap
+                    if (pausecount > initialPauseCount) { pc++; return null; }   // we were paused                    
                 }
-                if (t != null) {
-                    stack.push(new CallMarker(this));
-                    JSArray args = new JSArray();
-                    args.addElement(val);
-                    stack.push(args);
-                    f = t.f;
-                    scope = new Trap.TrapScope(f.parentScope, t, val);
-                    pc = -1;
-                    break;
+                break;
+            }
+                
+            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");
+                
+                Trap t = target.getTrap(key);
+                if(t != null) t = t.writeTrap();
+                
+                if(t == null && target instanceof JS.Clone) {
+                    target = ((JS.Clone)target).clonee;
+                    t = target.getTrap(key);
+                    if(t != null) t = t.writeTrap();
                 }
-                ((JS)target).put(key, val);
-                if (pausecount > initialPauseCount) { pc++; return null; }   // we were paused
+
                 stack.push(val);
+                
+                if(t != null) {
+                    setupTrap(t,val,new TrapMarker(this,t,val));
+                    pc--; // we increment later
+                } else {
+                    target.put(key,val);
+                    if (pausecount > initialPauseCount) { pc++; return null; }   // we were paused
+                }
                 break;
             }
 
             case GET:
             case GET_PRESERVE: {
-                Object o, v;
+                JS target, key;
                 if (op == GET) {
-                    v = arg == null ? stack.pop() : arg;
-                    o = stack.pop();
+                    key = arg == null ? stack.pop() : (JS)arg;
+                    target = stack.pop();
                 } else {
-                    v = stack.pop();
-                    o = stack.peek();
-                    stack.push(v);
+                    key = stack.pop();
+                    target = stack.peek();
+                    stack.push(key);
                 }
-                Object ret = null;
-                if (v == null) throw je("tried to get the null key from " + o);
-                if (o == null) throw je("tried to get property \"" + v + "\" from the null object");
-                if (o instanceof String || o instanceof Number || o instanceof Boolean) {
-                    ret = getFromPrimitive(o,v);
-                    stack.push(ret);
-                    break;
-                } else if (o instanceof JS) {
-                    Trap t = null;
-                    if (o instanceof Trap.TrapScope && v.equals("cascade")) {
-                        t = ((Trap.TrapScope)o).t.next;
-                        while (t != null && t.f.numFormalArgs != 0) t = t.next;
-                        if (t == null) { v = ((Trap.TrapScope)o).t.name; o = ((Trap.TrapScope)o).t.trapee; }
-
-                    } else if (o instanceof JS) {
-                        if (o instanceof JSScope) {
-                            JSScope p = (JSScope)o; // search the scope-path for the trap
-                            t = p.getTrap(v);
-                            while (t == null && p.getParentScope() != null) { p = p.getParentScope(); t = p.getTrap(v); }
-                        } else {
-                            t = ((JS)o).getTrap(v);
-                        }
-                        while (t != null && t.f.numFormalArgs != 0) t = t.next; // get first read trap
-                    }
-                    if (t != null) {
-                        stack.push(new CallMarker(this));
-                        JSArray args = new JSArray();
-                        stack.push(args);
-                        f = t.f;
-                        scope = new Trap.TrapScope(f.parentScope, t, null);
-                        ((Trap.TrapScope)scope).cascadeHappened = true;
-                        pc = -1;
-                        break;
-                    }
-                    ret = ((JS)o).get(v);
-                    if (ret == JS.METHOD) ret = new Stub((JS)o, v);
+                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();
+                
+                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) {
+                    setupTrap(t,null,new TrapMarker(this,t,null));
+                    pc--; // we increment later
+                } else {
+                    ret = target.get(key);
                     if (pausecount > initialPauseCount) { pc++; return null; }   // we were paused
+                    if (ret == JS.METHOD) ret = new Stub(target, key);
                     stack.push(ret);
-                    break;
                 }
-                throw je("tried to get property " + v + " from a " + o.getClass().getName());
+                break;
             }
             
             case CALL: case CALLMETHOD: {
-                int numArgs = JS.toInt(arg);
-                Object method = null;
-                Object ret = null;
-                Object object = stack.pop();
+                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 method = null;
+                JS ret = null;
+                JS object = stack.pop();
 
                 if (op == CALLMETHOD) {
                     if (object == JS.METHOD) {
                         method = stack.pop();
                         object = stack.pop();
                     } else if (object == null) {
-                        Object name = stack.pop();
-                        stack.pop();
-                        throw new JSExn("function '"+name+"' not found");
+                        method = stack.pop();
+                        object = stack.pop();
+                        throw new JSExn("function '"+JS.debugToString(method)+"' not found in " + object.getClass().getName());
                     } else {
                         stack.pop();
                         stack.pop();
                     }
                 }
-                Object[] rest = numArgs > 3 ? new Object[numArgs - 3] : null;
-                for(int i=numArgs - 1; i>2; i--) rest[i-3] = stack.pop();
-                Object a2 = numArgs <= 2 ? null : stack.pop();
-                Object a1 = numArgs <= 1 ? null : stack.pop();
-                Object a0 = numArgs <= 0 ? null : stack.pop();
 
-                if (object instanceof String || object instanceof Number || object instanceof Boolean) {
-                    ret = callMethodOnPrimitive(object, method, a0, a1, a2, null, numArgs);
-
-                } else if (object instanceof JSFunction) {
-                    // FIXME: use something similar to call0/call1/call2 here
-                    JSArray arguments = new JSArray();
-                    for(int i=0; i<numArgs; i++) arguments.addElement(i==0?a0:i==1?a1:i==2?a2:rest[i-3]);
+                if (object instanceof JSFunction) {
                     stack.push(new CallMarker(this));
-                    stack.push(arguments);
+                    stack.push(new JSArgs(a0,a1,a2,rest,numArgs,object));
                     f = (JSFunction)object;
-                    scope = new JSScope(f.parentScope);
+                    scope = f.parentScope;
                     pc = -1;
                     break;
-
-                } else if (object instanceof JS) {
+                } else {
                     JS c = (JS)object;
                     ret = method == null ? c.call(a0, a1, a2, rest, numArgs) : c.callMethod(method, a0, a1, a2, rest, numArgs);
-
-                } else {
-                    throw new JSExn("can't call a " + object + " @" + pc + "\n" + f.dump());
-
                 }
+                
                 if (pausecount > initialPauseCount) { pc++; return null; }
                 stack.push(ret);
                 break;
             }
 
             case THROW:
-                throw new JSExn(stack.pop(), stack, f, pc, scope);
+                throw new JSExn(stack.pop(), this);
 
-                /* FIXME
+                /* FIXME GRAMMAR
             case MAKE_GRAMMAR: {
                 final Grammar r = (Grammar)arg;
                 final JSScope final_scope = scope;
@@ -399,49 +387,44 @@ class Interpreter implements ByteCodes, Tokens {
             }
                 */
             case ADD_TRAP: case DEL_TRAP: {
-                Object val = stack.pop();
-                Object key = stack.pop();
-                Object obj = stack.peek();
+                JS val = stack.pop();
+                JS key = stack.pop();
+                JS js = stack.peek();
                 // A trap addition/removal
-                JS js = obj instanceof JSScope ? ((JSScope)obj).top() : (JS) obj;
+                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);
                 break;
             }
 
-            case ASSIGN_SUB: case ASSIGN_ADD: {
-                Object val = stack.pop();
-                Object key = stack.pop();
-                Object obj = stack.peek();
-                // The following setup is VERY important. The generated bytecode depends on the stack
-                // being setup like this (top to bottom) KEY, OBJ, VAL, KEY, OBJ
-                stack.push(key);
-                stack.push(val);
-                stack.push(obj);
-                stack.push(key);
-                break;
-            }
-
             case ADD: {
-                int count = ((Number)arg).intValue();
+                int count = ((JSNumber)arg).toInt();
                 if(count < 2) throw new Error("this should never happen");
                 if(count == 2) {
                     // common case
-                    Object right = stack.pop();
-                    Object left = stack.pop();
-                    if(left instanceof String || right instanceof String)
-                        stack.push(JS.toString(left).concat(JS.toString(right)));
-                    else stack.push(JS.N(JS.toDouble(left) + JS.toDouble(right)));
+                    JS right = stack.pop();
+                    JS left = stack.pop();
+                    JS ret;
+                    if(left instanceof JSString || right instanceof JSString)
+                        ret = JS.S(JS.toString(left).concat(JS.toString(right)));
+                    else if(left instanceof JSNumber.D || right instanceof JSNumber.D)
+                        ret = JS.N(JS.toDouble(left) + JS.toDouble(right));
+                    else {
+                        long l = JS.toLong(left) + JS.toLong(right);
+                        if(l < Integer.MIN_VALUE || l > Integer.MAX_VALUE) ret = JS.N(l);
+                        ret = JS.N((int)l);
+                    }
+                    stack.push(ret);
                 } else {
-                    Object[] args = new Object[count];
+                    JS[] args = new JS[count];
                     while(--count >= 0) args[count] = stack.pop();
-                    if(args[0] instanceof String) {
+                    if(args[0] instanceof JSString) {
                         StringBuffer sb = new StringBuffer(64);
                         for(int i=0;i<args.length;i++) sb.append(JS.toString(args[i]));
-                        stack.push(sb.toString());
+                        stack.push(JS.S(sb.toString()));
                     } else {
                         int numStrings = 0;
-                        for(int i=0;i<args.length;i++) if(args[i] instanceof String) numStrings++;
+                        for(int i=0;i<args.length;i++) if(args[i] instanceof JSString) numStrings++;
                         if(numStrings == 0) {
                             double d = 0.0;
                             for(int i=0;i<args.length;i++) d += JS.toDouble(args[i]);
@@ -449,15 +432,15 @@ class Interpreter implements ByteCodes, Tokens {
                         } else {
                             int i=0;
                             StringBuffer sb = new StringBuffer(64);
-                            if(!(args[0] instanceof String || args[1] instanceof String)) {
+                            if(!(args[0] instanceof JSString || args[1] instanceof JSString)) {
                                 double d=0.0;
                                 do {
                                     d += JS.toDouble(args[i++]);
-                                } while(!(args[i] instanceof String));
+                                } while(!(args[i] instanceof JSString));
                                 sb.append(JS.toString(JS.N(d)));
                             }
                             while(i < args.length) sb.append(JS.toString(args[i++]));
-                            stack.push(sb.toString());
+                            stack.push(JS.S(sb.toString()));
                         }
                     }
                 }
@@ -465,8 +448,8 @@ class Interpreter implements ByteCodes, Tokens {
             }
 
             default: {
-                Object right = stack.pop();
-                Object left = stack.pop();
+                JS right = stack.pop();
+                JS left = stack.pop();
                 switch(op) {
                         
                 case BITOR: stack.push(JS.N(JS.toLong(left) | JS.toLong(right))); break;
@@ -482,32 +465,21 @@ class Interpreter implements ByteCodes, Tokens {
                 case RSH: stack.push(JS.N(JS.toLong(left) >> JS.toLong(right))); break;
                 case URSH: stack.push(JS.N(JS.toLong(left) >>> JS.toLong(right))); break;
                         
-                case LT: case LE: case GT: case GE: {
-                    if (left == null) left = JS.N(0);
-                    if (right == null) right = JS.N(0);
-                    int result = 0;
-                    if (left instanceof String || right instanceof String) {
-                        result = left.toString().compareTo(right.toString());
-                    } else {
-                        result = (int)java.lang.Math.ceil(JS.toDouble(left) - JS.toDouble(right));
-                    }
-                    stack.push(JS.B((op == LT && result < 0) || (op == LE && result <= 0) ||
-                               (op == GT && result > 0) || (op == GE && result >= 0)));
-                    break;
+                //#repeat </<=/>/>= LT/LE/GT/GE
+                case LT: {
+                    if(left instanceof JSString && right instanceof JSString)
+                        stack.push(JS.B(JS.toString(left).compareTo(JS.toString(right)) < 0));
+                    else
+                        stack.push(JS.B(JS.toDouble(left) < JS.toDouble(right)));
                 }
+                //#end
                     
                 case EQ:
                 case NE: {
-                    Object l = left;
-                    Object r = right;
                     boolean ret;
-                    if (l == null) { Object tmp = r; r = l; l = tmp; }
-                    if (l == null && r == null) ret = true;
-                    else if (r == null) ret = false; // l != null, so its false
-                    else if (l instanceof Boolean) ret = JS.B(JS.toBoolean(r)).equals(l);
-                    else if (l instanceof Number) ret = JS.toNumber(r).doubleValue() == JS.toNumber(l).doubleValue();
-                    else if (l instanceof String) ret = r != null && l.equals(r.toString());
-                    else ret = l.equals(r);
+                    if(left == null && right == null) ret = true;
+                    else if(left == null || right == null) ret = false;
+                    else ret = left.jsequals(right);
                     stack.push(JS.B(op == EQ ? ret : !ret)); break;
                 }
 
@@ -516,66 +488,107 @@ class Interpreter implements ByteCodes, Tokens {
             }
 
         } catch(JSExn e) {
-            while(stack.size() > 0) {
-                Object o = stack.pop();
-                if (o instanceof CatchMarker || o instanceof TryMarker) {
-                    boolean inCatch = o instanceof CatchMarker;
-                    if(inCatch) {
-                        o = stack.pop();
-                        if(((TryMarker)o).finallyLoc < 0) continue; // no finally block, keep going
-                    }
-                    if(!inCatch && ((TryMarker)o).catchLoc >= 0) {
-                        // run the catch block, this will implicitly run the finally block, if it exists
-                        stack.push(o);
-                        stack.push(catchMarker);
-                        stack.push(e.getObject());
-                        f = ((TryMarker)o).f;
-                        scope = ((TryMarker)o).scope;
-                        pc = ((TryMarker)o).catchLoc - 1;
-                        continue OUTER;
-                    } else {
-                        stack.push(new FinallyData(e));
-                        f = ((TryMarker)o).f;
-                        scope = ((TryMarker)o).scope;
-                        pc = ((TryMarker)o).finallyLoc - 1;
-                        continue OUTER;
-                    }
-                }
-            }
-            throw e;
+            catchException(e);
+            pc--; // it'll get incremented on the next iteration
         } // end try/catch
         } // end for
     }
+    
+    /** tries to find a handler withing the call chain for this exception
+        if a handler is found the interpreter is setup to call the exception handler
+        if a handler is not found the exception is thrown
+    */
+    void catchException(JSExn e) throws JSExn {
+        while(!stack.empty()) {
+            JS o = stack.pop();
+            if (o instanceof CatchMarker || o instanceof TryMarker) {
+                boolean inCatch = o instanceof CatchMarker;
+                if(inCatch) {
+                    o = stack.pop();
+                    if(((TryMarker)o).finallyLoc < 0) continue; // no finally block, keep going
+                }
+                if(!inCatch && ((TryMarker)o).catchLoc >= 0) {
+                    // run the catch block, this will implicitly run the finally block, if it exists
+                    stack.push(o);
+                    stack.push(catchMarker);
+                    stack.push(e.getObject());
+                    f = ((TryMarker)o).f;
+                    scope = ((TryMarker)o).scope;
+                    pc = ((TryMarker)o).catchLoc;
+                    return;
+                } else {
+                    stack.push(new FinallyData(e));
+                    f = ((TryMarker)o).f;
+                    scope = ((TryMarker)o).scope;
+                    pc = ((TryMarker)o).finallyLoc;
+                    return;
+                }
+            }
+        }
+        throw e;
+    }
 
+    void setupTrap(Trap t, JS val, CallMarker cm) throws JSExn {
+        stack.push(cm);
+        stack.push(new TrapArgs(t,val));
+        f = t.f;
+        scope = f.parentScope;
+        pc = 0;
+    }
 
 
     // Markers //////////////////////////////////////////////////////////////////////
 
-    public static class CallMarker {
-        int pc;
-        JSScope scope;
-        JSFunction f;
-        public CallMarker(Interpreter cx) { pc = cx.pc + 1; scope = cx.scope; f = cx.f; }
+    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 CallMarker extends Marker {
+        final int pc;
+        final JSScope scope;
+        final JSFunction f;
+        public CallMarker(Interpreter cx) {
+            pc = cx == null ? -1 : cx.pc + 1;
+            scope = cx == null ? null : cx.scope;
+            f = cx == null ? null : cx.f;
+        }
     }
     
-    public static class CatchMarker { }
+    static class TrapMarker extends CallMarker {
+        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) {
+            super(cx);
+            this.t = t;
+            this.val = val;
+            this.pauseOnPut = pauseOnPut;
+        }
+    }
+    
+    static class CatchMarker extends Marker { }
     private static CatchMarker catchMarker = new CatchMarker();
     
-    public static class LoopMarker {
-        public int location;
-        public String label;
-        public JSScope scope;
+    static class LoopMarker extends Marker {
+        final public int location;
+        final public String label;
+        final public JSScope scope;
         public LoopMarker(int location, String label, JSScope scope) {
             this.location = location;
             this.label = label;
             this.scope = scope;
         }
     }
-    public static class TryMarker {
-        public int catchLoc;
-        public int finallyLoc;
-        public JSScope scope;
-        public JSFunction f;
+    static class TryMarker extends Marker {
+        final public int catchLoc;
+        final public int finallyLoc;
+        final public JSScope scope;
+        final public JSFunction f;
         public TryMarker(int catchLoc, int finallyLoc, Interpreter cx) {
             this.catchLoc = catchLoc;
             this.finallyLoc = finallyLoc;
@@ -583,163 +596,110 @@ class Interpreter implements ByteCodes, Tokens {
             this.f = cx.f;
         }
     }
-    public static class FinallyData {
-        public int op;
-        public Object arg;
-        public JSExn exn;
+    static class FinallyData extends Marker {
+        final public int op;
+        final public Object arg;
+        final public JSExn exn;
         public FinallyData(int op) { this(op,null); }
-        public FinallyData(int op, Object arg) { this.op = op; this.arg = arg; }
-        public FinallyData(JSExn exn) { this.exn = exn; } // Just throw this exn
+        public FinallyData(int op, Object arg) { this.op = op; this.arg = arg; this.exn = null; }
+        public FinallyData(JSExn exn) { this.exn = exn; this.op = -1; this.arg = null; } // Just throw this exn
     }
 
-
-    // Operations on Primitives //////////////////////////////////////////////////////////////////////
-
-    static Object callMethodOnPrimitive(Object o, Object method, Object arg0, Object arg1, Object arg2, Object[] rest, int alength) throws JSExn {
-        if (method == null || !(method instanceof String) || "".equals(method))
-            throw new JSExn("attempt to call a non-existant method on a primitive");
-
-        if (o instanceof Number) {
-            //#switch(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");
-            case "toString": {
-                int radix = alength >= 1 ? JS.toInt(arg0) : 10;
-                return Long.toString(((Number)o).longValue(),radix);
-            }
+    static class TrapArgs extends JS {
+        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;
             //#end
-        } else if (o instanceof Boolean) {
-            // No methods for Booleans
-            throw new JSExn("attempt to call a method on a Boolean");
-        }
-
-        String s = JS.toString(o);
-        int slength = s.length();
-        //#switch(method)
-        case "substring": {
-            int a = alength >= 1 ? JS.toInt(arg0) : 0;
-            int b = alength >= 2 ? JS.toInt(arg1) : slength;
-            if (a > slength) a = slength;
-            if (b > slength) b = slength;
-            if (a < 0) a = 0;
-            if (b < 0) b = 0;
-            if (a > b) { int tmp = a; a = b; b = tmp; }
-            return s.substring(a,b);
-        }
-        case "substr": {
-            int start = alength >= 1 ? JS.toInt(arg0) : 0;
-            int len = alength >= 2 ? JS.toInt(arg1) : Integer.MAX_VALUE;
-            if (start < 0) start = slength + start;
-            if (start < 0) start = 0;
-            if (len < 0) len = 0;
-            if (len > slength - start) len = slength - start;
-            if (len <= 0) return "";
-            return s.substring(start,start+len);
-        }
-        case "charAt": {
-            int p = alength >= 1 ? JS.toInt(arg0) : 0;
-            if (p < 0 || p >= slength) return "";
-            return s.substring(p,p+1);
-        }
-        case "charCodeAt": {
-            int p = alength >= 1 ? JS.toInt(arg0) : 0;
-            if (p < 0 || p >= slength) return JS.N(Double.NaN);
-            return JS.N(s.charAt(p));
-        }
-        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 sb.toString();
-        }
-        case "indexOf": {
-            String search = alength >= 1 ? arg0.toString() : "null";
-            int start = alength >= 2 ? JS.toInt(arg1) : 0;
-            // Java's indexOf handles an out of bounds start index, it'll return -1
-            return JS.N(s.indexOf(search,start));
-        }
-        case "lastIndexOf": {
-            String search = alength >= 1 ? arg0.toString() : "null";
-            int start = alength >= 2 ? JS.toInt(arg1) : 0;
-            // Java's indexOf handles an out of bounds start index, it'll return -1
-            return JS.N(s.lastIndexOf(search,start));            
+            return super.get(key);
         }
-        case "match": return JSRegexp.stringMatch(s,arg0);
-        case "replace": return JSRegexp.stringReplace(s,arg0,arg1);
-        case "search": return JSRegexp.stringSearch(s,arg0);
-        case "split": return JSRegexp.stringSplit(s,arg0,arg1,alength);
-        case "toLowerCase": return s.toLowerCase();
-        case "toUpperCase": return s.toUpperCase();
-        case "toString": return s;
-        case "slice": {
-            int a = alength >= 1 ? JS.toInt(arg0) : 0;
-            int b = alength >= 2 ? JS.toInt(arg1) : slength;
-            if (a < 0) a = slength + a;
-            if (b < 0) b = slength + b;
-            if (a < 0) a = 0;
-            if (b < 0) b = 0;
-            if (a > slength) a = slength;
-            if (b > slength) b = slength;
-            if (a > b) return "";
-            return s.substring(a,b);
-        }
-        //#end
-        throw new JSExn("Attempted to call non-existent method: " + method);
     }
     
-    static Object getFromPrimitive(Object o, Object key) throws JSExn {
-        boolean returnJS = false;
-        if (o instanceof Boolean) {
-            throw new JSExn("Booleans do not have properties");
-        } else if (o instanceof Number) {
-            if (key.equals("toPrecision") || key.equals("toExponential") || key.equals("toFixed"))
-                returnJS = true;
-        }
-        if (!returnJS) {
-            // the string stuff applies to everything
-            String s = o.toString();
-            
-            // this is sort of ugly, but this list should never change
-            // These should provide a complete (enough) implementation of the ECMA-262 String object
-
-            //#switch(key)
-            case "length": return JS.N(s.length());
-            case "substring": returnJS = true; break; 
-            case "charAt": returnJS = true; break; 
-            case "charCodeAt": returnJS = true; break; 
-            case "concat": returnJS = true; break; 
-            case "indexOf": returnJS = true; break; 
-            case "lastIndexOf": returnJS = true; break; 
-            case "match": returnJS = true; break; 
-            case "replace": returnJS = true; break; 
-            case "search": returnJS = true; break; 
-            case "slice": returnJS = true; break; 
-            case "split": returnJS = true; break; 
-            case "toLowerCase": returnJS = true; break; 
-            case "toUpperCase": returnJS = true; break; 
-            case "toString": returnJS = true; break; 
-            case "substr": returnJS = true; break;  
-           //#end
+    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;
         }
-        if (returnJS) {
-            final Object target = o;
-            final String method = key.toString();
-            return new JS() {
-                    public Object call(Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn {
-                        if (nargs > 2) throw new JSExn("cannot call that method with that many arguments");
-                        return callMethodOnPrimitive(target, method, a0, a1, a2, rest, nargs);
-                    }
-            };
+        
+        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);
         }
-        return null;
     }
 
-    private static class Stub extends JS {
-        private Object method;
+    static class Stub extends JS {
+        private JS method;
         JS obj;
-        public Stub(JS obj, Object method) { this.obj = obj; this.method = method; }
-        public Object call(Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn {
+        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);
         }
     }
+    
+    static class Stack {
+        private static final int MAX_STACK_SIZE = 512;
+        private JS[] stack = new JS[64];
+        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 swap() throws JSExn {
+            if(sp < 2) throw new JSExn("stack overflow");
+            JS tmp = stack[sp-2];
+            stack[sp-2] = stack[sp-1];
+            stack[sp-1] = tmp;
+        }
+        CallMarker findCall() {
+            for(int i=sp-1;i>=0;i--) if(stack[i] instanceof CallMarker) return (CallMarker) stack[i];
+            return null;
+        }
+        void grow() throws JSExn {
+            if(stack.length >= MAX_STACK_SIZE) throw new JSExn("Stack overflow");
+            JS[] stack2 = new JS[stack.length * 2];
+            System.arraycopy(stack,0,stack2,0,stack.length);
+        }       
+        
+        void backtrace(JSExn e) {
+            for(int i=sp-1;i>=0;i--) {
+                if (stack[i] instanceof CallMarker) {
+                    CallMarker cm = (CallMarker)stack[i];
+                    if(cm.f == null) break;
+                    String s = cm.f.sourceName + ":" + cm.f.line[cm.pc-1];
+                    if(cm instanceof Interpreter.TrapMarker) 
+                        s += " (trap on " + JS.debugToString(((Interpreter.TrapMarker)cm).t.key) + ")";
+                    e.addBacktrace(s);
+                }
+            }
+        }
+    }
 }
index 9aa9bd4..538fc1a 100644 (file)
@@ -6,50 +6,115 @@ import java.io.*;
 import java.util.*;
 
 /** The minimum set of functionality required for objects which are manipulated by JavaScript */
-public class JS /*extends org.ibex.util.BalancedTree*/ implements Serializable { 
+public abstract class JS { 
+    public static final JS METHOD = new JS() { };
 
-    public static final long serialVersionUID = 572634985343962922L;
-    public static boolean checkAssertions = false;
-
-    public static final Object METHOD = new Object();
-    public final JS unclone() { return _unclone(); }
-    public Enumeration keys() throws JSExn { return entries == null ? emptyEnumeration : entries.keys(); }
-    public Object get(Object key) throws JSExn { return entries == null ? null : entries.get(key, null); }
-    public void put(Object key, Object val) throws JSExn { (entries==null?entries=new Hash():entries).put(key,null,val); }
-    public Object callMethod(Object method, Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn {
-        throw new JSExn("attempted to call the null value (method "+method+")");
-    }    
-    public Object call(Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn {
+    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 Cloneable extends JS {
-        public Object jsclone() throws JSExn {
-            return new Clone(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 static class Clone extends JS.Cloneable {
-        protected JS.Cloneable clonee = null;
-        JS _unclone() { return clonee.unclone(); }
-        public JS.Cloneable getClonee() { return clonee; }
-        public Clone(JS.Cloneable clonee) { this.clonee = clonee; }
-        public boolean equals(Object o) {
-            if (!(o instanceof JS)) return false;
-            return unclone() == ((JS)o).unclone();
+    
+    public interface Cloneable { }
+        
+    public static class Clone extends O {
+        protected final JS clonee;
+        public Clone(JS clonee) throws JSExn {
+            if(!(clonee instanceof Cloneable)) throw new JSExn("" + clonee.getClass().getName() + " isn't cloneable");
+            this.clonee = clonee;
         }
+        JS _unclone() { return clonee.unclone(); }
+        boolean jsequals(JS o) { return clonee.jsequals(o); }
+        
         public Enumeration keys() throws JSExn { return clonee.keys(); }
-        public Object get(Object key) throws JSExn { return clonee.get(key); }
-        public void put(Object key, Object val) throws JSExn { clonee.put(key, val); }
-        public Object callMethod(Object method, Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn {
-            return clonee.callMethod(method, a0, a1, a2, rest, nargs);
-        }    
-        public Object call(Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn {
+        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 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 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 ///////////////////////////////////////////////////////////////
 
     /** log a message with the current JavaScript sourceName/line */
@@ -65,18 +130,30 @@ public class JS /*extends org.ibex.util.BalancedTree*/ implements Serializable {
     public static UnpauseCallback pause() throws NotPauseableException {
         Interpreter i = Interpreter.current();
         if (i.pausecount == -1) throw new NotPauseableException();
+        boolean get;
+        switch(i.f.op[i.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");
+        }
         i.pausecount++;
-        return new JS.UnpauseCallback(i);
+        return new JS.UnpauseCallback(i,get);
     }
 
     public static class UnpauseCallback implements Task {
-        Interpreter i;
-        UnpauseCallback(Interpreter i) { this.i = i; }
-        public void perform() throws JSExn { unpause(null); }
-        public void unpause(Object o) throws JSExn {
-            // FIXME: if o instanceof JSExn, throw it into the JSworld
-            i.stack.push(o);
-            i.resume();
+        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 JS unpause(JSExn e) throws JSExn {
+            i.catchException(e);
+            return i.resume();
         }
     }
 
@@ -85,167 +162,156 @@ public class JS /*extends org.ibex.util.BalancedTree*/ implements Serializable {
     // Static Helper Methods ///////////////////////////////////////////////////////////////////////////////////
 
     /** coerce an object to a Boolean */
-    public static boolean toBoolean(Object o) {
-        if (o == null) return false;
-        if (o instanceof Boolean) return ((Boolean)o).booleanValue();
-        if (o instanceof Long) return ((Long)o).longValue() != 0;
-        if (o instanceof Integer) return ((Integer)o).intValue() != 0;
-        if (o instanceof Number) {
-            double d = ((Number) o).doubleValue();
-            // NOTE: d == d is a test for NaN. It should be faster than Double.isNaN()
-            return d != 0.0 && d == d;
-        }
-        if (o instanceof String) return ((String)o).length() != 0;
+    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;
     }
-
-    /** coerce an object to a Long */
-    public static long toLong(Object o) { return toNumber(o).longValue(); }
-
-    /** coerce an object to an Int */
-    public static int toInt(Object o) { return toNumber(o).intValue(); }
-
-    /** coerce an object to a Double */
-    public static double toDouble(Object o) { return toNumber(o).doubleValue(); }
-
+    //#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 Number toNumber(Object o) {
-        if (o == null) return ZERO;
-        if (o instanceof Number) return ((Number)o);
-
-        // NOTE: There are about 3 pages of rules in ecma262 about string to number conversions
-        //       We aren't even close to following all those rules.  We probably never will be.
-        if (o instanceof String) try { return N((String)o); } catch (NumberFormatException e) { return N(Double.NaN); }
-        if (o instanceof Boolean) return ((Boolean)o).booleanValue() ? N(1) : ZERO;
-        throw new Error("toNumber() got object of type " + o.getClass().getName() + " which we don't know how to handle");
+    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");
     }
-
-    /** coerce an object to a String */
-    public static String toString(Object o) {
+    //#end
+    
+    public static String toString(JS o) throws JSExn {
         if(o == null) return "null";
-        if(o instanceof String) return (String) o;
-        if(o instanceof Integer || o instanceof Long || o instanceof Boolean) return o.toString();
-        if(o instanceof JSArray) return o.toString();
-        if(o instanceof JSDate) return o.toString();
-        if(o instanceof Double || o instanceof Float) {
-            double d = ((Number)o).doubleValue();
-            if((int)d == d) return Integer.toString((int)d);
-            return o.toString();
+        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 JS) return ((JS)o).coerceToString();   // HACK for now, this will probably go away
-        throw new RuntimeException("can't coerce "+o+" [" + o.getClass().getName() + "] to type String.");
+        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 String coerceToString() {
-        throw new RuntimeException("can't coerce "+this+" [" + getClass().getName() + "] to type String.");
+    
+    public static boolean isString(JS o) {
+        if(o instanceof JSString) return true;
+        return false;
     }
-
+    
     // Instance Methods ////////////////////////////////////////////////////////////////////
-
-    public static final Integer ZERO = new Integer(0);
  
-    // this gets around a wierd fluke in the Java type checking rules for ?..:
-    public static final Object T = Boolean.TRUE;
-    public static final Object F = Boolean.FALSE;
-
-    public static final Boolean B(boolean b) { return b ? Boolean.TRUE : Boolean.FALSE; }
-    public static final Boolean B(int i) { return i==0 ? Boolean.FALSE : Boolean.TRUE; }
-    public static final Number N(String s) { return s.indexOf('.') == -1 ? N(Integer.parseInt(s)) : new Double(s); }
-    public static final Number N(double d) { return (int)d == d ? N((int)d) : new Double(d); }
-    public static final Number N(long l) { return N((int)l); }
-
-    private static final Integer[] smallIntCache = new Integer[65535 / 4];
-    private static final Integer[] largeIntCache = new Integer[65535 / 4];
-    public static final Number N(int i) {
-        Integer ret = null;
+    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[] 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 < smallIntCache.length && idx > 0) {
+        if (idx < CACHE_SIZE && idx > 0) {
             ret = smallIntCache[idx];
             if (ret != null) return ret;
         }
-        else ret = largeIntCache[Math.abs(idx % largeIntCache.length)];
-        if (ret == null || ret.intValue() != i) {
-            ret = new Integer(i);
+        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 % largeIntCache.length)] = ret;
+            else largeIntCache[Math.abs(idx % CACHE_SIZE)] = ret;
         }
         return ret;
     }
     
-    private static Enumeration emptyEnumeration = new Enumeration() {
-            public boolean hasMoreElements() { return false; }
-            public Object nextElement() { throw new NoSuchElementException(); }
-        };
+    private static Enumeration EMPTY_ENUMERATION = new EmptyEnumeration(null);
     
-    private Hash entries = null;
-
     public static JS fromReader(String sourceName, int firstLine, Reader sourceCode) throws IOException {
-        return JSFunction._fromReader(sourceName, firstLine, sourceCode);
+        return Parser.fromReader(sourceName, firstLine, sourceCode);
     }
 
-    // HACK: caller can't know if the argument is a JSFunction or not...
-    public static JS cloneWithNewParentScope(JS j, JSScope s) {
-        return ((JSFunction)j)._cloneWithNewParentScope(s);
-    }
-
-    // HACK
-    public static Vec getFormalArgs(JS j) { return ((JSFunction)j).formalArgs; }
-
-    // HACK
-    public static JSScope getParentScope(JS j) { return ((JSFunction)j).parentScope; }
-
-    public static Object eval(JS j) throws JSExn {
-        Interpreter cx = new Interpreter((JSFunction)j, false, new JSArray(), false);
-        return cx.resume();
+    public static JS cloneWithNewGlobalScope(JS js, JS s) {
+        if(js instanceof JSFunction)
+            return ((JSFunction)js)._cloneWithNewParentScope(new JSScope.Top(s));
+        else
+            return js;
     }
 
 
     // Trap support //////////////////////////////////////////////////////////////////////////////
 
-    /** override and return true to allow placing traps on this object.
-     *  if isRead true, this is a read trap, otherwise write trap
-     **/
-    protected boolean isTrappable(Object name, boolean isRead) { return true; }
-
     /** performs a put, triggering traps if present; traps are run in an unpauseable interpreter */
-    public void putAndTriggerTraps(Object key, Object value) throws JSExn {
+    public final void putAndTriggerTraps(JS key, JS value) throws JSExn {
         Trap t = getTrap(key);
-        if (t != null) t.invoke(value);
-        else put(key, value);
+        if(t == null || (t = t.writeTrap()) == null) put(key,value);
+        else new Interpreter(t,value,false).resume();
     }
 
     /** performs a get, triggering traps if present; traps are run in an unpauseable interpreter */
-    public Object getAndTriggerTraps(Object key) throws JSExn {
+    public final JS getAndTriggerTraps(JS key) throws JSExn {
         Trap t = getTrap(key);
-        if (t != null) return t.invoke();
-        else return get(key);
-    }
-
-    /** retrieve a trap from the entries hash */
-    protected final Trap getTrap(Object key) {
-        return entries == null ? null : (Trap)entries.get(key, Trap.class);
+        if (t == null || (t = t.readTrap()) == null) return get(key);
+        else return new Interpreter(t,null,false).resume();
     }
-
-    /** retrieve a trap from the entries hash */
-    protected final void putTrap(Object key, Trap value) {
-        if (entries == null) entries = new Hash();
-        entries.put(key, Trap.class, value);
+    
+    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();
     }
 
     /** adds a trap, avoiding duplicates */
-    protected final void addTrap(Object name, JSFunction f) throws JSExn {
+    // 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;
-        if (!isTrappable(name, isRead)) throw new JSExn("not allowed "+(isRead?"read":"write")+" trap on property: "+name);
-        for(Trap t = getTrap(name); t != null; t = t.next) if (t.f == f) return;
-        putTrap(name, new Trap(this, name.toString(), f, (Trap)getTrap(name)));
+        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)));
     }
 
     /** deletes a trap, if present */
-    protected final void delTrap(Object name, JSFunction f) {
-        Trap t = (Trap)getTrap(name);
+    // 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.name, t.next); 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; }
     }
 
index 07cdc40..7038726 100644 (file)
@@ -5,15 +5,14 @@ import org.ibex.util.*;
 import java.util.*;
 
 /** A JavaScript JSArray */
-public class JSArray extends JS {
-
+public class JSArray extends JS.O {
     private static final Object NULL = new Object();
-    private Vec vec = new Vec();
-
+    private final BalancedTree bt = new BalancedTree();
+    
     public JSArray() { }
     public JSArray(int size) { setSize(size); }
     
-    private static int intVal(Object o) {
+    /*private static int intVal(Object o) {
         if (o instanceof Number) {
             int intVal = ((Number)o).intValue();
             if (intVal == ((Number)o).doubleValue()) return intVal;
@@ -23,10 +22,10 @@ public class JSArray extends JS {
         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 Object callMethod(Object method, Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn {
-        //#switch(method)
+    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;
@@ -61,13 +60,13 @@ public class JSArray extends JS {
         return super.callMethod(method, a0, a1, a2, rest, nargs);
     }
         
-    public Object get(Object key) throws JSExn {
-        int i = intVal(key);
-        if (i != Integer.MIN_VALUE) {
+    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(key)
+        //#switch(JS.toString(key))
         case "pop": return METHOD;
         case "reverse": return METHOD;
         case "toString": return METHOD;
@@ -83,12 +82,9 @@ public class JSArray extends JS {
         return super.get(key);
     }
 
-    public void put(Object key, Object val) throws JSExn {
-        if (key.equals("length")) setSize(toInt(val));
-        int i = intVal(key);
-        if (i == Integer.MIN_VALUE)
-            super.put(key, val);
-        else {
+    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);
@@ -96,16 +92,23 @@ public class JSArray extends JS {
                 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);
     }
 
-    public Enumeration keys() {
-        return new Enumeration() {
-                private int n = size();
-                public boolean hasMoreElements() { return n > 0; }
-                public Object nextElement() {
-                    if(n == 0) throw new NoSuchElementException();
-                    return new Integer(--n);
+    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++);
                 }
             };
     }
@@ -118,57 +121,55 @@ public class JSArray extends JS {
     }
     
     public final int length() { return size(); }
-    public final Object elementAt(int i) { 
+    public final JS elementAt(int i) { 
         if(i < 0 || i >= size()) throw new ArrayIndexOutOfBoundsException(i);
-        Object o = vec.elementAt(i);
-        return o == NULL ? null : o;
+        Object o = bt.getNode(i);
+        return o == NULL ? (JS)null : (JS)o;
     }
-    public final void addElement(Object o) { 
-        vec.insertElementAt(o==null ? NULL : o, size());
+    public final void addElement(JS o) { 
+        bt.insertNode(size(),o==null ? NULL : o);
     }
-    public final void setElementAt(Object o, int i) {
+    public final void setElementAt(JS o, int i) {
         if(i < 0 || i >= size()) throw new ArrayIndexOutOfBoundsException(i);
-        vec.setElementAt(o==null ? NULL : o,i);
+        bt.replaceNode(i,o==null ? NULL : o);
     }
-    public final void insertElementAt(Object o, int i) {
+    public final void insertElementAt(JS o, int i) {
         if(i < 0 || i > size()) throw new ArrayIndexOutOfBoundsException(i);
-        vec.insertElementAt(o==null ? NULL : o, i);
+        bt.insertNode(i,o==null ? NULL : o);
     }
-    public final Object removeElementAt(int i) {
+    public final JS removeElementAt(int i) {
         if(i < 0 || i >= size()) throw new ArrayIndexOutOfBoundsException(i);
-        Object o = vec.elementAt(i);
-        vec.removeElementAt(i);
-        return o == NULL ? null : o;
+        Object o = bt.deleteNode(i);
+        return o == NULL ? (JS)null : (JS)o;
     }
     
-    public final int size() { return vec.size(); }
-    public String typeName() { return "array"; }
-        
-    private Object join(String sep) {
+    public final int size() { return bt.treeSize(); }
+    
+    private JS join(String sep) throws JSExn {
         int length = size();
-        if(length == 0) return "";
+        if(length == 0) return JS.S("");
         StringBuffer sb = new StringBuffer(64);
         int i=0;
         while(true) {
-            Object o = elementAt(i);
+            JS o = elementAt(i);
             if(o != null) sb.append(JS.toString(o));
             if(++i == length) break;
             sb.append(sep);
         }
-        return sb.toString();
+        return JS.S(sb.toString());
     }
     
     // FEATURE: Implement this more efficiently
-    private Object reverse() {
+    private JS reverse() {
         int size = size();
         if(size < 2) return this;
         Vec vec = toVec();
-        vec.removeAllElements();
-        for(int i=size-1,j=0;i>=0;i--,j++) insertElementAt(vec.elementAt(i),j);
+        bt.clear();
+        for(int i=size-1,j=0;i>=0;i--,j++) insertElementAt((JS)vec.elementAt(i),j);
         return this;
     }
     
-    private Object slice(int start, int end) {
+    private JS slice(int start, int end) {
         int length = length();
         if(start < 0) start = length+start;
         if(end < 0) end = length+end;
@@ -181,37 +182,37 @@ public class JSArray extends JS {
             a.setElementAt(elementAt(start+i),i);
         return a;
     }
-    
+        
     private static final Vec.CompareFunc defaultSort = new Vec.CompareFunc() {
         public int compare(Object a, Object b) {
-            return JS.toString(a).compareTo(JS.toString(b));
+            try {
+                return JS.toString((JS)a).compareTo(JS.toString((JS)b));
+            } catch(JSExn e) { throw new JSExn.Wrapper(e); }
         }
     };
-    private Object sort(Object tmp) throws JSExn {
+    private JS sort(JS tmp) throws JSExn {
         Vec vec = toVec();
-        if(tmp instanceof JS) {
-            final JSArray funcArgs = new JSArray(2);
-            final JS jsFunc = (JS) tmp;
-            vec.sort(new Vec.CompareFunc() {
-                public int compare(Object a, Object b) {
-                    try {
-                        funcArgs.setElementAt(a,0);
-                        funcArgs.setElementAt(b,1);
-                        return JS.toInt(jsFunc.call(a, b, null, null, 2));
-                    } catch (Exception e) {
-                        // FIXME
-                        throw new JSRuntimeExn(e.toString());
+        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);
+                });
+            } else {
+                vec.sort(defaultSort);
+            }
+        } catch(JSExn.Wrapper e) {
+            throw e.refill();
         }
         setFromVec(vec);
         return this;
     }
     
-    private Object splice(JSArray args) {
+    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));
@@ -241,7 +242,25 @@ public class JSArray extends JS {
         return ret;
     }
     
-    public Vec toVec() { return (Vec)vec.clone(); }
-    public void setFromVec(Vec vec) { this.vec = (Vec)vec.clone(); }
-    public String toString() { return JS.toString(join(",")); }
+    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;
+    }
+    
+    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);
+        }
+    }
+    
+    String coerceToString() throws JSExn { return JS.toString(join(",")); }
 }
index 68e893e..dc6d839 100644 (file)
@@ -55,29 +55,19 @@ public class JSDate extends JS {
         }
     }
 
-    public JSDate(long now) {
-        if (thisTimeZone == null) {
-            // j.u.TimeZone is synchronized, so setting class statics from it
-            // should be OK.
-            thisTimeZone = java.util.TimeZone.getDefault();
-            LocalTZA = thisTimeZone.getRawOffset();
-        }
-        date = (double)now;
-    }
-
-    public String toString() { return date_format(date, FORMATSPEC_FULL); }
+    String coerceToString() { return date_format(date, FORMATSPEC_FULL); }
 
-    public Object callMethod(Object method, Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn {
+    public JS callMethod(JS method, JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn {
         switch(nargs) {
             case 0: {
-                //#switch(method)
-                case "toString": return date_format(date, FORMATSPEC_FULL);
-                case "toTimeString": return date_format(date, FORMATSPEC_TIME);
-                case "toDateString": return date_format(date, FORMATSPEC_DATE);
-                case "toLocaleString": return toLocaleString(date);
-                case "toLocaleTimeString": return toLocaleTimeString(date);
-                case "toLocaleDateString": return toLocaleDateString(date);
-                case "toUTCString": return toUTCString(date);
+                //#switch(JS.toString(method))
+                case "toString": return JS.S(date_format(date, FORMATSPEC_FULL));
+                case "toTimeString": return JS.S(date_format(date, FORMATSPEC_TIME));
+                case "toDateString": return JS.S(date_format(date, FORMATSPEC_DATE));
+                case "toLocaleString": return JS.S(toLocaleString(date));
+                case "toLocaleTimeString": return JS.S(toLocaleTimeString(date));
+                case "toLocaleDateString": return JS.S(toLocaleDateString(date));
+                case "toUTCString": return JS.S(toUTCString(date));
                 case "valueOf": return N(this.date);
                 case "getTime": return N(this.date);
                 case "getYear": return N(getYear(date));
@@ -102,16 +92,16 @@ public class JSDate extends JS {
                 return super.callMethod(method, a0, a1, a2, rest, nargs);
             }
             case 1: {
-                //#switch(method)
+                //#switch(JS.toString(method))
                 case "setTime": return N(this.setTime(toDouble(a0)));
                 case "setYear": return N(this.setYear(toDouble(a0)));
                 //#end
                 // fall through
             }
             default: {
-                Object[] args = new Object[nargs];
+                JS[] args = new JS[nargs];
                 for(int i=0; i<nargs; i++) args[i] = i==0 ? a0 : i==1 ? a1 : i==2 ? a2 : rest[i-3];
-                //#switch(method)
+                //#switch(JS.toString(method))
                 case "setMilliseconds": return N(this.makeTime(args, 1, true));
                 case "setUTCMilliseconds": return N(this.makeTime(args, 1, false));
                 case "setSeconds": return N(this.makeTime(args, 2, true));
@@ -132,8 +122,8 @@ public class JSDate extends JS {
         return super.callMethod(method, a0, a1, a2, rest, nargs);
     }
 
-    public Object get(Object key) throws JSExn {
-        //#switch(key)
+    public JS get(JS key) throws JSExn {
+        //#switch(JS.toString(key))
         case "toString": return METHOD;
         case "toTimeString": return METHOD;
         case "toDateString": return METHOD;
@@ -538,7 +528,7 @@ public class JSDate extends JS {
 
 
     private static final int MAXARGS = 7;
-    private static double jsStaticJSFunction_UTC(Object[] args) {
+    private static double jsStaticJSFunction_UTC(JS[] args) throws JSExn {
         double array[] = new double[MAXARGS];
         int loop;
         double d;
@@ -898,11 +888,11 @@ public class JSDate extends JS {
         return result.toString();
     }
 
-    private static double _toNumber(Object o) { return JS.toDouble(o); }
-    private static double _toNumber(Object[] o, int index) { return JS.toDouble(o[index]); }
+    private static double _toNumber(JS o) throws JSExn { return JS.toDouble(o); }
+    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(Object a0, Object a1, Object a2, Object[] rest, int nargs) {
+    public JSDate(JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn {
 
         JSDate obj = this;
         switch (nargs) {
@@ -912,16 +902,10 @@ public class JSDate extends JS {
             }
             case 1: {
                 double date;
-                if (a0 instanceof JS)
-                    a0 = ((JS) a0).toString();
-                if (!(a0 instanceof String)) {
-                    // if it's not a string, use it as a millisecond date
+                if(isString(a0))
+                    date = date_parseString(JS.toString(a0));
+                else
                     date = _toNumber(a0);
-                } else {
-                    // it's a string; parse it.
-                    String str = (String) a0;
-                    date = date_parseString(str);
-                }
                 obj.date = TimeClip(date);
                 return;
             }
@@ -1065,7 +1049,7 @@ public class JSDate extends JS {
         return this.date;
     }
 
-    private double makeTime(Object[] args, int maxargs, boolean local) {
+    private double makeTime(JS[] args, int maxargs, boolean local) throws JSExn {
         int i;
         double conv[] = new double[4];
         double hour, min, sec, msec;
@@ -1089,7 +1073,7 @@ public class JSDate extends JS {
          * d.setMilliseconds()" returns NaN.  Blech.
          */
         if (args.length == 0)
-            args = new Object[] { null };
+            args = new JS[] { null };
 
         for (i = 0; i < args.length && i < maxargs; i++) {
             conv[i] = _toNumber(args[i]);
@@ -1141,15 +1125,15 @@ public class JSDate extends JS {
         return date;
     }
 
-    private double setHours(Object[] args) {
+    private double setHours(JS[] args) throws JSExn {
         return makeTime(args, 4, true);
     }
 
-    private double setUTCHours(Object[] args) {
+    private double setUTCHours(JS[] args) throws JSExn {
         return makeTime(args, 4, false);
     }
 
-    private double makeDate(Object[] args, int maxargs, boolean local) {
+    private double makeDate(JS[] args, int maxargs, boolean local) throws JSExn {
         int i;
         double conv[] = new double[3];
         double year, month, day;
@@ -1160,7 +1144,7 @@ public class JSDate extends JS {
 
         /* See arg padding comment in makeTime.*/
         if (args.length == 0)
-            args = new Object[] { null };
+            args = new JS[] { null };
 
         for (i = 0; i < args.length && i < maxargs; i++) {
             conv[i] = _toNumber(args[i]);
index 1fda3c7..2afcef1 100644 (file)
@@ -7,27 +7,16 @@ 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 Object js = null; 
-    public JSExn(Object js) {
-        this.js = js;
-        if (Interpreter.current() != null)
-            fill(Interpreter.current().stack, Interpreter.current().f, Interpreter.current().pc, Interpreter.current().scope);
-    }
-    public JSExn(Object js, Vec stack, JSFunction f, int pc, JSScope scope) { this.js = js; fill(stack, f, pc, scope); }
-    private void fill(Vec stack, JSFunction f, int pc, JSScope scope) {
-        addBacktrace(f.sourceName + ":" + f.line[pc]);
-        if (scope != null && scope instanceof Trap.TrapScope)
-            addBacktrace("trap on property \"" + ((Trap.TrapScope)scope).t.name + "\"");
-        for(int i=stack.size()-1; i>=0; i--) {
-            Object element = stack.elementAt(i);
-            if (element instanceof Interpreter.CallMarker) {
-                Interpreter.CallMarker cm = (Interpreter.CallMarker)element;
-                if (cm.f != null)
-                    addBacktrace(cm.f.sourceName + ":" + cm.f.line[cm.pc-1]);
-                if (cm.scope != null && cm.scope instanceof Trap.TrapScope)
-                    addBacktrace("trap on property \"" + ((Trap.TrapScope)cm.scope).t.name + "\"");
-            }
-        }
+    private JS js; 
+    public JSExn(String s) { this(JS.S(s)); }
+    public JSExn(JS js) { this(js,null); }
+    public JSExn(JS js, Interpreter cx) { this.js = js; fill(cx); }
+    
+    private void fill(Interpreter cx) {
+        if(cx == null) cx = Interpreter.current();
+        if(cx == null) return;
+        addBacktrace(cx.f.sourceName + ":" + cx.f.line[cx.pc]);
+        cx.stack.backtrace(this);
     }
     public void printStackTrace() { printStackTrace(System.err); }
     public void printStackTrace(PrintWriter pw) {
@@ -38,12 +27,22 @@ public class JSExn extends Exception {
         for(int i=0; i<backtrace.size(); i++) ps.println("    at " + (String) backtrace.elementAt(i));
         super.printStackTrace(ps);
     }
-    public String toString() { return "JSExn: " + js; }
+    public String toString() { return "JSExn: " + JS.debugToString(js); }
     public String getMessage() { return toString(); }
-    public Object getObject() { return js; } 
-    public void addBacktrace(String line) { backtrace.addElement(line); }
-
-
+    public JS getObject() { return js; } 
+    
+    void addBacktrace(String line) { backtrace.addElement(line); }
+    
+    public static class Wrapper extends RuntimeException {
+        public final JSExn e;
+        public Wrapper(JSExn e) { this.e = e; }
+        public JSExn refill() {
+            e.addBacktrace("[foreign code]");
+            e.fill(null);
+            return e;
+        }
+    }
+    
     public static class IO extends JSExn {
         public IO(java.io.IOException ioe) {
             super("ibex.io: " + ioe.toString());
index 9dfc5db..d50e566 100644 (file)
@@ -5,7 +5,8 @@ import java.io.*;
 import org.ibex.util.*;
 
 /** A JavaScript function, compiled into bytecode */
-class JSFunction extends JS implements ByteCodes, Tokens, Task {
+// 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 {
 
 
     // Fields and Accessors ///////////////////////////////////////////////
@@ -21,32 +22,17 @@ class JSFunction extends JS implements ByteCodes, Tokens, Task {
     int size = 0;                  ///< the number of instruction/argument pairs
 
     JSScope parentScope;           ///< the default scope to use as a parent scope when executing this
-    Vec formalArgs = new Vec();
+
 
     // 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 JSArray());
+        Interpreter i = new Interpreter(this, true, new Interpreter.JSArgs(this));
         i.resume();
     }
 
-    /** parse and compile a function */
-    public static JSFunction _fromReader(String sourceName, int firstLine, Reader sourceCode) throws IOException {
-        JSFunction ret = new JSFunction(sourceName, firstLine, null);
-        if (sourceCode == null) return ret;
-        Parser p = new Parser(sourceCode, sourceName, firstLine);
-        while(true) {
-            int s = ret.size;
-            p.parseStatement(ret, null);
-            if (s == ret.size) break;
-        }
-        ret.add(-1, LITERAL, null); 
-        ret.add(-1, RETURN);
-        return ret;
-    }
-
     public JSFunction _cloneWithNewParentScope(JSScope s) {
         JSFunction ret = new JSFunction(sourceName, firstLine, s);
         // Reuse the same op, arg, line, and size variables for the new "instance" of the function
@@ -55,19 +41,13 @@ class JSFunction extends JS implements ByteCodes, Tokens, Task {
         ret.arg = this.arg;
         ret.line = this.line;
         ret.size = this.size;
-        ret.formalArgs = this.formalArgs;
         ret.numFormalArgs = this.numFormalArgs;
         return ret;
     }
 
     /** Note: code gets run in an <i>unpauseable</i> context. */
-    public Object call(Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn {
-        JSArray args = new JSArray();
-        if (nargs > 0) args.addElement(a0);
-        if (nargs > 1) args.addElement(a1);
-        if (nargs > 2) args.addElement(a2);
-        for(int i=3; i<nargs; i++) args.addElement(rest[i-3]);
-        Interpreter cx = new Interpreter(this, false, args);
+    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();
     }
 
@@ -104,23 +84,30 @@ class JSFunction extends JS implements ByteCodes, Tokens, Task {
 
     // Debugging //////////////////////////////////////////////////////////////////////
 
-    public String toString() { return "JSFunction [" + sourceName + ":" + firstLine + "]"; }
+    String extendedToString() { return "[" + sourceName + ":" + firstLine + "]"; }
 
-    public String dump() {
+    String dump() { return dump(""); }
+    private  String dump(String prefix) {
         StringBuffer sb = new StringBuffer(1024);
         sb.append("\n" + sourceName + ": " + firstLine + "\n");
         for (int i=0; i < size; i++) {
-            sb.append(i).append(" (").append(line[i]).append(") :");
+            sb.append(prefix);
+            sb.append(i).append(" (").append(line[i]).append("): ");
             if (op[i] < 0) sb.append(bytecodeToString[-op[i]]);
             else sb.append(codeToString[op[i]]);
             sb.append(" ");
-            sb.append(arg[i] == null ? "(no arg)" : arg[i]);
+            sb.append(arg[i] == null ? "(no arg)" : arg[i] instanceof JS ? JS.debugToString((JS)arg[i]) : arg[i]);
             if((op[i] == JF || op[i] == JT || op[i] == JMP) && arg[i] != null && arg[i] instanceof Number) {
                 sb.append(" jump to ").append(i+((Number) arg[i]).intValue());
             } else  if(op[i] == TRY) {
                 int[] jmps = (int[]) arg[i];
                 sb.append(" catch: ").append(jmps[0] < 0 ? "No catch block" : ""+(i+jmps[0]));
                 sb.append(" finally: ").append(jmps[1] < 0 ? "No finally block" : ""+(i+jmps[1]));
+            } else if(op[i] == NEWFUNCTION) {
+                sb.append(((JSFunction) arg[i]).dump(prefix + "     "));
+            } else if(op[i] == NEWSCOPE) {
+                int n = ((JSNumber)arg[i]).toInt();
+                sb.append(" base: " + (n>>>16) + " size: " + (n&0xffff));
             }
             sb.append("\n");
         }
index 04828ed..6b442b6 100644 (file)
@@ -3,51 +3,48 @@
 package org.ibex.js; 
 
 /** The JavaScript Math object */
-public class JSMath extends JS {
+class JSMath extends JS {
+    private static final JS E       = JS.N(java.lang.Math.E);
+    private static final JS PI      = JS.N(java.lang.Math.PI);
+    private static final JS LN10    = JS.N(java.lang.Math.log(10));
+    private static final JS LN2     = JS.N(java.lang.Math.log(2));
+    private static final JS LOG10E  = JS.N(1/java.lang.Math.log(10));
+    private static final JS LOG2E   = JS.N(1/java.lang.Math.log(2));
+    private static final JS SQRT1_2 = JS.N(1/java.lang.Math.sqrt(2));
+    private static final JS SQRT2   = JS.N(java.lang.Math.sqrt(2));
 
-    public static JSMath singleton = new JSMath();
-
-    private static final Double E       = new Double(java.lang.Math.E);
-    private static final Double PI      = new Double(java.lang.Math.PI);
-    private static final Double LN10    = new Double(java.lang.Math.log(10));
-    private static final Double LN2     = new Double(java.lang.Math.log(2));
-    private static final Double LOG10E  = new Double(1/java.lang.Math.log(10));
-    private static final Double LOG2E   = new Double(1/java.lang.Math.log(2));
-    private static final Double SQRT1_2 = new Double(1/java.lang.Math.sqrt(2));
-    private static final Double SQRT2   = new Double(java.lang.Math.sqrt(2));
-
-    public Object callMethod(Object method, Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn {
+    public JS callMethod(JS method, JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn {
         switch(nargs) {
             case 0: {
-                //#switch(method)
-                case "random": return new Double(java.lang.Math.random());
+                //#switch(JS.toString(method))
+                case "random": return JS.N(java.lang.Math.random());
                 //#end
                 break;
             }
             case 1: {
-                //#switch(method)
-                case "ceil": return new Long((long)java.lang.Math.ceil(toDouble(a0)));
-                case "floor": return new Long((long)java.lang.Math.floor(toDouble(a0)));
-                case "round": return new Long((long)java.lang.Math.round(toDouble(a0)));
-                case "abs": return new Double(java.lang.Math.abs(toDouble(a0)));
-                case "sin": return new Double(java.lang.Math.sin(toDouble(a0)));
-                case "cos": return new Double(java.lang.Math.cos(toDouble(a0)));
-                case "tan": return new Double(java.lang.Math.tan(toDouble(a0)));
-                case "asin": return new Double(java.lang.Math.asin(toDouble(a0)));
-                case "acos": return new Double(java.lang.Math.acos(toDouble(a0)));
-                case "atan": return new Double(java.lang.Math.atan(toDouble(a0)));
-                case "sqrt": return new Double(java.lang.Math.sqrt(toDouble(a0)));
-                case "exp": return new Double(java.lang.Math.exp(toDouble(a0)));
-                case "log": return new Double(java.lang.Math.log(toDouble(a0)));
+                //#switch(JS.toString(method))
+                case "ceil": return JS.N((long)java.lang.Math.ceil(toDouble(a0)));
+                case "floor": return JS.N((long)java.lang.Math.floor(toDouble(a0)));
+                case "round": return JS.N((long)java.lang.Math.round(toDouble(a0)));
+                case "abs": return JS.N(java.lang.Math.abs(toDouble(a0)));
+                case "sin": return JS.N(java.lang.Math.sin(toDouble(a0)));
+                case "cos": return JS.N(java.lang.Math.cos(toDouble(a0)));
+                case "tan": return JS.N(java.lang.Math.tan(toDouble(a0)));
+                case "asin": return JS.N(java.lang.Math.asin(toDouble(a0)));
+                case "acos": return JS.N(java.lang.Math.acos(toDouble(a0)));
+                case "atan": return JS.N(java.lang.Math.atan(toDouble(a0)));
+                case "sqrt": return JS.N(java.lang.Math.sqrt(toDouble(a0)));
+                case "exp": return JS.N(java.lang.Math.exp(toDouble(a0)));
+                case "log": return JS.N(java.lang.Math.log(toDouble(a0)));
                 //#end
                 break;
             }
             case 2: {
-                //#switch(method)
-                case "min": return new Double(java.lang.Math.min(toDouble(a0), toDouble(a1)));
-                case "max": return new Double(java.lang.Math.max(toDouble(a0), toDouble(a1)));
-                case "pow": return new Double(java.lang.Math.pow(toDouble(a0), toDouble(a1)));
-                case "atan2": return new Double(java.lang.Math.atan2(toDouble(a0), toDouble(a1)));
+                //#switch(JS.toString(method))
+                case "min": return JS.N(java.lang.Math.min(toDouble(a0), toDouble(a1)));
+                case "max": return JS.N(java.lang.Math.max(toDouble(a0), toDouble(a1)));
+                case "pow": return JS.N(java.lang.Math.pow(toDouble(a0), toDouble(a1)));
+                case "atan2": return JS.N(java.lang.Math.atan2(toDouble(a0), toDouble(a1)));
                 //#end
                 break;
             }
@@ -55,10 +52,8 @@ public class JSMath extends JS {
         return super.callMethod(method, a0, a1, a2, rest, nargs);
     }
 
-    public void put(Object key, Object val) { }
-
-    public Object get(Object key) throws JSExn {
-        //#switch(key)
+    public JS get(JS key) throws JSExn {
+        //#switch(JS.toString(key))
         case "E": return E;
         case "LN10": return LN10;
         case "LN2": return LN2;
index 364792d..52f7f9c 100644 (file)
@@ -9,12 +9,12 @@ import java.lang.reflect.*;
 /** Automatic JS-ification via Reflection (not for use in the core) */
 public class JSReflection extends JS {
 
-    public static Object wrap(Object o) throws JSExn {
+    public static JS wrap(Object o) throws JSExn {
         if (o == null) return null;
-        if (o instanceof String) return o;
-        if (o instanceof Boolean) return o;
-        if (o instanceof Number) return o;
-        if (o instanceof JS) return o;
+        if (o instanceof String) return JS.S((String)o);
+        if (o instanceof Boolean) return JS.B(((Boolean)o).booleanValue());
+        if (o instanceof Number) return JS.N((Number)o);
+        if (o instanceof JS) return (JS)o;
         if (o instanceof Object[]) {
             // FIXME: get element type here
         }
@@ -24,15 +24,24 @@ public class JSReflection extends JS {
     public static class Array extends JS {
         final Object[] arr;
         public Array(Object[] arr) { this.arr = arr; }
-        public Enumeration keys() throws JSExn { return new CounterEnumeration(arr.length); }
-        public Object get(Object key) throws JSExn { return wrap(arr[toInt(key)]); }
-        public void put(Object key, Object val) throws JSExn { throw new JSExn("can't write to org.ibex.js.Reflection.Array's"); }
+        // FEATURE: Add a JSCounterEnumeration
+        public Enumeration keys() throws JSExn {
+            return new Enumeration(null) {
+                private int n = 0;
+                public boolean _hasMoreElements() { return n < arr.length; }
+                public JS _nextElement() {
+                    return n >= arr.length ? null : JS.N(n++);
+                }
+            };
+        }
+        public JS get(JS key) throws JSExn { return wrap(arr[toInt(key)]); }
+        public void put(JS key, JS val) throws JSExn { throw new JSExn("can't write to org.ibex.js.Reflection.Array's"); }
     }
 
     // FIXME public static class Hash { }
     // FIXME public Enumeration keys() throws JSExn {  }
 
-    public Object get(Object key) throws JSExn {
+    public JS get(JS key) throws JSExn {
         String k = toString(key);
         try {
             Field f = this.getClass().getField(k);
@@ -40,6 +49,7 @@ public class JSReflection extends JS {
         } catch (NoSuchFieldException nfe) {
         } catch (IllegalAccessException nfe) {
         } catch (SecurityException nfe) { }
+
         try {
             Method[] methods = this.getClass().getMethods();
             for(int i=0; i<methods.length; i++) if (methods[i].getName().equals(k)) return METHOD;
@@ -47,11 +57,11 @@ public class JSReflection extends JS {
         return null;
     }
 
-    public void put(Object key, Object val) throws JSExn {
+    public void put(JS key, JS val) throws JSExn {
         throw new JSExn("put() not supported yet");
     }
 
-    public Object callMethod(Object method, Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn {
+    public JS callMethod(JS method, JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn {
         String k = toString(method);
         try {
             Method[] methods = this.getClass().getMethods();
@@ -75,5 +85,5 @@ public class JSReflection extends JS {
             throw new JSExn("unhandled reflected exception: " + ite.toString());
         } catch (SecurityException nfe) { }
         throw new JSExn("called a reflection method with the wrong number of arguments");
-    }    
+    }
 } 
index fb9e67d..c7ed118 100644 (file)
@@ -6,18 +6,23 @@ public class JSRegexp extends JS {
     private boolean global;
     private GnuRegexp.RE re;
     private int lastIndex;
+
+    private JS pattern;
+    private int flags;
     
-    public JSRegexp(Object arg0, Object arg1) throws JSExn {
+    public JSRegexp(JS arg0, JS arg1) throws JSExn {
         if(arg0 instanceof JSRegexp) {
             JSRegexp r = (JSRegexp) arg0;
             this.global = r.global;
             this.re = r.re;
             this.lastIndex = r.lastIndex;
+            this.pattern = pattern;
+            this.flags = flags;
         } else {
-            String pattern = (String)arg0;
+            String pattern = JS.toString(arg0);
             String sFlags = null;
             int flags = 0;
-            if(arg1 != null) sFlags = (String)arg1;
+            if(arg1 != null) sFlags = JS.toString(arg1);
             if(sFlags == null) sFlags = "";
             for(int i=0;i<sFlags.length();i++) {
                 switch(sFlags.charAt(i)) {
@@ -28,19 +33,17 @@ public class JSRegexp extends JS {
                 }
             }
             re = newRE(pattern,flags);
-            put("source", pattern);
-            put("global", B(global));
-            put("ignoreCase", B(flags & GnuRegexp.RE.REG_ICASE));
-            put("multiline", B(flags & GnuRegexp.RE.REG_MULTILINE));
+            this.pattern = JS.S(pattern);
+            this.flags = flags;
         }
     }
 
-    public Object callMethod(Object method, Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn {
+    public JS callMethod(JS method, JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn {
         switch(nargs) {
             case 1: {
-                //#switch(method)
+                //#switch(JS.toString(method))
                 case "exec": {
-                    String s = (String)a0;
+                    String s = JS.toString(a0);
                     int start = global ? lastIndex : 0;
                     if(start < 0 || start >= s.length()) { lastIndex = 0; return null; }
                     GnuRegexp.REMatch match = re.getMatch(s,start);
@@ -48,7 +51,7 @@ public class JSRegexp extends JS {
                     return match == null ? null : matchToExecResult(match,re,s);
                 }
                 case "test": {
-                    String s = (String)a0;
+                    String s = JS.toString(a0);
                     if (!global) return B(re.getMatch(s) != null);
                     int start = global ? lastIndex : 0;
                     if(start < 0 || start >= s.length()) { lastIndex = 0; return null; }
@@ -56,14 +59,14 @@ public class JSRegexp extends JS {
                     lastIndex = match != null ? s.length() : match.getEndIndex();
                     return B(match != null);
                 }
-                case "toString": return toString(a0);
+                case "toString": return JS.S(a0.coerceToString());
                 case "stringMatch": return stringMatch(a0,a1);
                 case "stringSearch": return stringSearch(a0,a1);
                 //#end
                 break;
             }
             case 2: {
-                //#switch(method)
+                //#switch(JS.toString(method))
                 case "stringReplace": return stringReplace(a0, a1,a2);
                 //#end
                 break;
@@ -72,84 +75,89 @@ public class JSRegexp extends JS {
         return super.callMethod(method, a0, a1, a2, rest, nargs);
     }
     
-    public Object get(Object key) throws JSExn {
-        //#switch(key)
+    public JS get(JS key) throws JSExn {
+        //#switch(JS.toString(key))
         case "exec": return METHOD;
         case "test": return METHOD;
         case "toString": return METHOD;
         case "lastIndex": return 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);
         //#end
         return super.get(key);
     }
     
-    public void put(Object key, Object value) throws JSExn {
-        if(key.equals("lastIndex")) lastIndex = JS.toNumber(value).intValue();
+    public void put(JS key, JS value) throws JSExn {
+        if(JS.isString(key)) {
+            if(JS.toString(key).equals("lastIndex")) {
+                lastIndex = JS.toInt(value);
+                return;
+            }
+        }
         super.put(key,value);
     }
   
-    private static Object matchToExecResult(GnuRegexp.REMatch match, GnuRegexp.RE re, String s) {
+    private static JS matchToExecResult(GnuRegexp.REMatch match, GnuRegexp.RE re, String s) {
         try {
-            JS ret = new JS();
-            ret.put("index", N(match.getStartIndex()));
-            ret.put("input",s);
+            JS ret = new JS.O();
+            ret.put(JS.S("index"), N(match.getStartIndex()));
+            ret.put(JS.S("input"),JS.S(s));
             int n = re.getNumSubs();
-            ret.put("length", N(n+1));
-            ret.put("0",match.toString());
-            for(int i=1;i<=n;i++) ret.put(Integer.toString(i),match.toString(i));
+            ret.put(JS.S("length"), 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;
         } catch (JSExn e) {
             throw new Error("this should never happen");
         }
     }
     
-    public String toString() {
-        try {
-            StringBuffer sb = new StringBuffer();
-            sb.append('/');
-            sb.append(get("source"));
-            sb.append('/');
-            if(global) sb.append('g');
-            if(Boolean.TRUE.equals(get("ignoreCase"))) sb.append('i');
-            if(Boolean.TRUE.equals(get("multiline"))) sb.append('m');
-            return sb.toString();
-        } catch (JSExn e) {
-            throw new Error("this should never happen");
-        }
+    String coerceToString() {
+        StringBuffer sb = new StringBuffer();
+        sb.append('/');
+        sb.append(pattern);
+        sb.append('/');
+        if(global) sb.append('g');
+        if((flags & GnuRegexp.RE.REG_ICASE) != 0) sb.append('i');
+        if((flags & GnuRegexp.RE.REG_MULTILINE) != 0) sb.append('m');
+        return sb.toString();
     }
     
-    public static Object stringMatch(Object o, Object arg0) throws JSExn {
-        String s = o.toString();
+    static JS stringMatch(JS o, JS arg0) throws JSExn {
+        String s = JS.toString(o);
         GnuRegexp.RE re;
         JSRegexp regexp = null;
         if(arg0 instanceof JSRegexp) {
             regexp = (JSRegexp) arg0;
             re = regexp.re;
         } else {
-            re = newRE(arg0.toString(),0);
+            re = newRE(JS.toString(arg0),0);
         }
         
         if(regexp == null) {
             GnuRegexp.REMatch match = re.getMatch(s);
             return matchToExecResult(match,re,s);
         }
-        if(!regexp.global) return regexp.callMethod("exec", s, null, null, null, 1);
+        if(!regexp.global) return regexp.callMethod(JS.S("exec"), o, null, null, null, 1);
         
         JSArray ret = new JSArray();
         GnuRegexp.REMatch[] matches = re.getAllMatches(s);
-        for(int i=0;i<matches.length;i++) ret.addElement(matches[i].toString());
+        for(int i=0;i<matches.length;i++) ret.addElement(JS.S(matches[i].toString()));
         regexp.lastIndex = matches.length > 0 ? matches[matches.length-1].getEndIndex() : s.length();
         return ret;
     }
     
-    public static Object stringSearch(Object o, Object arg0) throws JSExn  {
-        String s = o.toString();
-        GnuRegexp.RE re = arg0 instanceof JSRegexp ? ((JSRegexp)arg0).re : newRE(arg0.toString(),0);
+    static JS stringSearch(JS o, JS arg0) throws JSExn  {
+        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());
     }
     
-    public static Object stringReplace(Object o, Object arg0, Object arg1) throws JSExn {
-        String s = o.toString();
+    static JS stringReplace(JS o, JS arg0, JS arg1) throws JSExn {
+        String s = JS.toString(o);
         GnuRegexp.RE re;
         JSFunction replaceFunc = null;
         String replaceString = null;
@@ -163,7 +171,7 @@ public class JSRegexp extends JS {
         if(arg1 instanceof JSFunction)
             replaceFunc = (JSFunction) arg1;
         else
-            replaceString = JS.toString(arg1.toString());
+            replaceString = JS.toString(arg1);
         GnuRegexp.REMatch[] matches;
         if(regexp != null && regexp.global) {
             matches = re.getAllMatches(s);
@@ -191,32 +199,32 @@ public class JSRegexp extends JS {
             if(replaceFunc != null) {
                 int n = (regexp == null ? 0 : re.getNumSubs());
                 int numArgs = 3 + n;
-                Object[] rest = new Object[numArgs - 3];
-                Object a0 = match.toString();
-                Object a1 = null;
-                Object a2 = null;
+                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 = match.toString(j); break;
-                        case 2: a2 = match.toString(j); break;
-                        default: rest[j - 3] = match.toString(j); break;
+                        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 = s;
+                        a2 = JS.S(s);
                         break;
                     case 4:
                         a2 = N(match.getStartIndex());
-                        rest[0] = s;
+                        rest[0] = JS.S(s);
                         break;
                     default:
                         rest[rest.length - 2] = N(match.getStartIndex());
-                        rest[rest.length - 1] = s;
+                        rest[rest.length - 1] = JS.S(s);
                 }
 
                 // note: can't perform pausing operations in here
-                sb.append((String)replaceFunc.call(a0, a1, a2, rest, numArgs));
+                sb.append(JS.toString(replaceFunc.call(a0, a1, a2, rest, numArgs)));
 
             } else {
                 sb.append(mySubstitute(match,replaceString,s));
@@ -224,7 +232,7 @@ public class JSRegexp extends JS {
         }
         int end = matches.length == 0 ? 0 : matches[matches.length-1].getEndIndex();
         sb.append(sa,end,sa.length-end);
-        return sb.toString();
+        return JS.S(sb.toString());
     }
     
     private static String mySubstitute(GnuRegexp.REMatch match, String s, String source) {
@@ -269,7 +277,8 @@ public class JSRegexp extends JS {
     }
                     
     
-    public static Object stringSplit(String s, Object arg0, Object arg1, int nargs) {
+    static JS stringSplit(JS s_, JS arg0, JS arg1, int nargs) throws JSExn {
+        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();
@@ -284,7 +293,7 @@ public class JSRegexp extends JS {
             regexp = (JSRegexp) arg0;
             re = regexp.re;
         } else {
-            sep = arg0.toString();
+            sep = JS.toString(arg0);
         }
         
         // special case this for speed. additionally, the code below doesn't properly handle
@@ -292,7 +301,7 @@ public class JSRegexp extends JS {
         if(sep != null && sep.length()==0) {
             int len = s.length();
             for(int i=0;i<len;i++)
-                ret.addElement(s.substring(i,i+1));
+                ret.addElement(JS.S(s.substring(i,i+1)));
             return ret;
         }
         
@@ -301,27 +310,27 @@ public class JSRegexp extends JS {
                 GnuRegexp.REMatch m = re.getMatch(s,p);
                 if(m == null) break OUTER;
                 boolean zeroLength = m.getStartIndex() == m.getEndIndex();
-                ret.addElement(s.substring(p,zeroLength ? m.getStartIndex()+1 : m.getStartIndex()));
+                ret.addElement(JS.S(s.substring(p,zeroLength ? m.getStartIndex()+1 : m.getStartIndex())));
                 p = zeroLength ? p + 1 : m.getEndIndex();
                 if(!zeroLength) {
                     for(int i=1;i<=re.getNumSubs();i++) {
-                        ret.addElement(m.toString(i));
+                        ret.addElement(JS.S(m.toString(i)));
                         if(ret.length() == limit) break OUTER;
                     }
                 }
             } else {
                 int x = s.indexOf(sep,p);
                 if(x == -1) break OUTER;
-                ret.addElement(s.substring(p,x));
+                ret.addElement(JS.S(s.substring(p,x)));
                 p = x + sep.length();
             }
             if(ret.length() == limit) break;
         }
         if(p < s.length() && ret.length() != limit)
-            ret.addElement(s.substring(p));
+            ret.addElement(JS.S(s.substring(p)));
         return ret;
     }
-    
+   
     public static GnuRegexp.RE newRE(String pattern, int flags) throws JSExn {
         try {
             return new GnuRegexp.RE(pattern,flags,GnuRegexp.RESyntax.RE_SYNTAX_PERL5);
@@ -329,6 +338,5 @@ public class JSRegexp extends JS {
             throw new JSExn(e.toString());
         }
     }
-    
-    public String typeName() { return "regexp"; }
+
 }
index 675ddc7..09d2cae 100644 (file)
@@ -1,26 +1,60 @@
 // Copyright 2004 Adam Megacz, see the COPYING file for licensing [GPL] 
 package org.ibex.js; 
 
-// FIXME: should allow parentScope to be a JS, not a JSScope
 /** Implementation of a JavaScript Scope */
-public class JSScope extends JS { 
+class JSScope extends JS.O {
 
+    private final int base;
+    private final JS[] vars;
+    final JSScope parent;
+
+    public static class Top extends JSScope {
+        private final JS global;
+        public Top(JS global) { super(null,0,0); this.global = global; }
+        JS get(int i) throws JSExn { throw new JSExn("scope index out of range"); }
+        void put(int i, JS o) throws JSExn { throw new JSExn("scope index out of range"); }
+        JS getGlobal() { return global; }
+    };
+        
+    // NOTE: We can't just set base to parent.base + parent.vars.length
+    // sometimes we only access part of a parent's scope
+    public JSScope(JSScope parent, int base, int size) {
+        this.parent = parent;
+        this.base = base;
+        this.vars = new JS[size];
+    }
+    
+    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 static final Object NULL_PLACEHOLDER = new Object();
+    private static final JS NULL_PLACEHOLDER = new JS() { };
 
-    public JSScope(JSScope parentScope) { this.parentScope = parentScope; }
-    public void declare(String s) throws JSExn { super.put(s, NULL_PLACEHOLDER); }
+    public JSScope(JSScope parentScope) { this(parentScope, 0, 0); }
+    public void declare(JS s) throws JSExn { super.put(s, NULL_PLACEHOLDER); }
     public JSScope getParentScope() { return parentScope; }
 
-    public Object get(Object key) throws JSExn {
-        Object o = super.get(key);
+    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);
     }
 
-    public boolean has(Object key) throws JSExn { return super.get(key) != null; }
-    public void put(Object key, Object val) throws JSExn {
+    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);
     }
@@ -32,73 +66,69 @@ public class JSScope extends JS {
     }
 
     public static class Global extends JSScope {
-        private final static Double NaN = new Double(Double.NaN);
-        private final static Double POSITIVE_INFINITY = new Double(Double.POSITIVE_INFINITY);
+        private final static JS NaN = JS.N(Double.NaN);
+        private final static JS POSITIVE_INFINITY = JS.N(Double.POSITIVE_INFINITY);
 
         public Global() { super(null); }
-        public Object get(Object key) throws JSExn {
-            //#switch(key)
-            case "NaN": return NaN;
+        public Global(JSScope p) { super(p); }
+        
+        public void declare(JS k) throws JSExn { throw new JSExn("can't declare variables in the global scope"); }
+        
+        // HACK: "this" gets lost on the way back through the scope chain
+        // 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);
+            return ret;
+        }
+        
+        public JS _get(JS key) throws JSExn {
+            //#switch(JS.toString(key))
+            case "JS.NaN": return JS.NaN;
             case "Infinity": return POSITIVE_INFINITY;
             case "undefined": return null;
-            case "stringFromCharCode": return METHOD;
-            case "parseInt": 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;
-            case "parseInt": return METHOD;
+            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
             return super.get(key);
         }
 
-        public Object callMethod(Object method, Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn {
-            switch(nargs) {
-                case 0: {
-                    //#switch(method)
-                    case "stringFromCharCode":
-                        char buf[] = new char[nargs];
-                        for(int i=0; i<nargs; i++) buf[i] = (char)(JS.toInt(i==0?a0:i==1?a1:i==2?a2:rest[i-3]) & 0xffff);
-                        return new String(buf);
-                    //#end
-                    break;
-                }
-                case 1: {
-                    //#switch(method)
-                    case "parseInt": return parseInt(a0, N(0));
-                    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");
-                    case "encodeURIComponent": throw new JSExn("unimplemented");
-                    case "escape": throw new JSExn("unimplemented");
-                    case "unescape": throw new JSExn("unimplemented");
-                    //#end
-                    break;
-                }
-                case 2: {
-                    //#switch(method)
-                    case "parseInt": return parseInt(a0, a1);
-                    //#end
-                    break;
-                }
-            }
-            return super.callMethod(method, a0, a1, a2, rest, nargs);
+        public JS callMethod(JS method, JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn {
+            //#switch(JS.toString(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 "decodeURI": throw new JSExn("unimplemented");
+            case "decodeURIComponent": throw new JSExn("unimplemented");
+            case "encodeURI": throw new JSExn("unimplemented");
+            case "encodeURIComponent": throw new JSExn("unimplemented");
+            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);
         }
 
-        private Object parseInt(Object arg, Object r) {
+        private JS parseInt(JS arg, JS r) throws JSExn {
             int radix = JS.toInt(r);
-            String s = (String)arg;
+            String s = JS.toString(arg);
             int start = 0;
             int length = s.length();
             int sign = 1;
             long n = 0;
-            if(radix != 0 && (radix < 2 || radix > 36)) return NaN;
+            if(radix != 0 && (radix < 2 || radix > 36)) return JS.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;
@@ -115,7 +145,7 @@ public class JSScope extends JS {
                 }
             }
             if(radix == 0) radix = 10;
-            if(length == start || Character.digit(s.charAt(start),radix) == -1) return NaN;
+            if(length == start || Character.digit(s.charAt(start),radix) == -1) return JS.NaN;
             // try the fast way first
             try {
                 String s2 = start == 0 ? s : s.substring(start);
@@ -126,14 +156,14 @@ public class JSScope extends JS {
                 int digit = Character.digit(s.charAt(i),radix);
                 if(digit < 0) break;
                 n = n*radix + digit;
-                if(n < 0) return NaN; // overflow;
+                if(n < 0) return JS.NaN; // overflow;
             }
             if(n <= Integer.MAX_VALUE) return JS.N(sign*(int)n);
             return JS.N((long)sign*n);
         }
 
-        private Object parseFloat(Object arg) {
-            String s = (String)arg;
+        private JS parseFloat(JS arg) throws JSExn {
+            String s = JS.toString(arg);
             int start = 0;
             int length = s.length();
             while(start < length && Character.isWhitespace(s.charAt(0))) start++;
@@ -142,11 +172,11 @@ public class JSScope extends JS {
             // trailing garbage
             while(start < end) {
                 try {
-                    return JS.N(s.substring(start,length));
+                    return JS.N(new Double(s.substring(start,length)));
                 } catch(NumberFormatException e) { }
                 end--;
             }
-            return NaN;
+            return JS.NaN;
         }
     }
 }
index ac6f6e1..42ec2c7 100644 (file)
@@ -137,6 +137,7 @@ class Lexer implements Tokens {
         case "implements": return RESERVED;
         case "instanceof": return RESERVED;
         case "synchronized": return RESERVED;
+        case "cascade": return CASCADE;
         //#end
         return -1;
     }
@@ -194,8 +195,9 @@ class Lexer implements Tokens {
             }
         }
         
-        if (!isInteger) this.number = JS.N(dval);
-        else this.number = JS.N(longval);
+        if (!isInteger) this.number = new Double(dval);
+        else if(longval >= Integer.MIN_VALUE && longval <= Integer.MAX_VALUE) this.number = new Integer((int)longval);
+        else this.number = new Long(longval);
         return NUMBER;
     }
     
index 9f62439..6293107 100644 (file)
@@ -69,7 +69,7 @@ class Parser extends Lexer implements ByteCodes {
 
     // Constructors //////////////////////////////////////////////////////
 
-    public Parser(Reader r, String sourceName, int line) throws IOException { super(r, sourceName, line); }
+    private Parser(Reader r, String sourceName, int line) throws IOException { super(r, sourceName, line); }
 
     /** for debugging */
     public static void main(String[] s) throws IOException {
@@ -78,7 +78,6 @@ class Parser extends Lexer implements ByteCodes {
         System.out.println(block);
     }
 
-
     // Statics ////////////////////////////////////////////////////////////
 
     static byte[] precedence = new byte[MAX_TOKEN + 1];
@@ -133,8 +132,68 @@ class Parser extends Lexer implements ByteCodes {
         precedence[DOT] = precedence[LB] = precedence[LP] =  precedence[INC] = precedence[DEC] = 15;
     }
 
-
+    // Local variable management
+    Vec scopeStack = new Vec();
+    static class ScopeInfo {
+        int base;
+        int end;
+        int newScopeInsn;
+        Hash mapping = new Hash();
+    }
+    Hash globalCache = new Hash();
+    JS scopeKey(String name) {
+        if(globalCache.get(name) != null) return null;
+        for(int i=scopeStack.size()-1;i>=0;i--) {
+            JS key = (JS)((ScopeInfo) scopeStack.elementAt(i)).mapping.get(name);
+            if(key != null) return key;
+        }
+        globalCache.put(name,Boolean.TRUE);
+        return null;
+    }
+    void scopeDeclare(String name) throws IOException {
+        ScopeInfo si = (ScopeInfo) scopeStack.lastElement();
+        if(si.mapping.get(name) != null) throw pe("" + name + " already declared in this scope");
+        si.mapping.put(name,JS.N(si.end++));
+        globalCache.put(name,null);
+    }
+    void scopePush(JSFunction b) {
+        ScopeInfo prev = (ScopeInfo) scopeStack.lastElement();
+        ScopeInfo si = new ScopeInfo();
+        si.base = prev.end;
+        si.end = si.base;
+        si.newScopeInsn = b.size;
+        scopeStack.push(si);
+        b.add(parserLine, NEWSCOPE);
+    }
+    void scopePop(JSFunction b) {
+        ScopeInfo si = (ScopeInfo) scopeStack.pop();
+        b.add(parserLine, OLDSCOPE);
+        b.set(si.newScopeInsn,JS.N((si.base<<16)|((si.end-si.base)<<0))); 
+    }
+    
+    
     // Parsing Logic /////////////////////////////////////////////////////////
+    
+    /** parse and compile a function */
+    public static JSFunction fromReader(String sourceName, int firstLine, Reader sourceCode) throws IOException {
+        JSFunction ret = new JSFunction(sourceName, firstLine, null);
+        if (sourceCode == null) return ret;
+        Parser p = new Parser(sourceCode, sourceName, firstLine);
+        p.scopeStack.setSize(0);
+        p.scopeStack.push(new ScopeInfo());
+        p.scopePush(ret);
+        while(true) {
+            //int s = ret.size;
+            if(p.peekToken() == -1) break; // FIXME: Check this logic one more time
+            p.parseStatement(ret, null);
+            //if (s == ret.size) break;
+        }
+        p.scopePop(ret);
+        if(p.scopeStack.size() != 1) throw new Error("scopeStack height mismatch");
+        ret.add(-1, LITERAL, null); 
+        ret.add(-1, RETURN);
+        return ret;
+    }
 
     /** gets a token and throws an exception if it is not <tt>code</tt> */
     private void consume(int code) throws IOException {
@@ -169,20 +228,18 @@ class Parser extends Lexer implements ByteCodes {
         case -1: throw pe("expected expression");
 
         // all of these simply push values onto the stack
-        case NUMBER: b.add(parserLine, LITERAL, number); break;
-        case STRING: b.add(parserLine, LITERAL, string); break;
+        case NUMBER: b.add(parserLine, LITERAL, JS.N(number)); break;
+        case STRING: b.add(parserLine, LITERAL, JSString.intern(string)); break;
         case NULL: b.add(parserLine, LITERAL, null); break;
-        case TRUE: case FALSE: b.add(parserLine, LITERAL, JS.B(tok == TRUE)); break;
+        case TRUE: case FALSE: b.add(parserLine, LITERAL, tok == TRUE ? JS.T : JS.F); break;
 
         // (.foo) syntax
         case DOT: {
             consume(NAME);
-            b.add(parserLine, TOPSCOPE);
-            b.add(parserLine, LITERAL, "");
-            b.add(parserLine, GET);
-            b.add(parserLine, LITERAL, string);
-            b.add(parserLine, GET);
-            continueExpr(b, minPrecedence);
+            b.add(parserLine, GLOBALSCOPE);
+            b.add(parserLine, GET, JS.S("",true));
+            b.add(parserLine, LITERAL, JS.S(string,true));
+            continueExprAfterAssignable(b,minPrecedence,null);
             break;
         }
 
@@ -206,9 +263,17 @@ class Parser extends Lexer implements ByteCodes {
             consume(RB);
             break;
         }
-        case SUB: {  // negative literal (like "3 * -1")
-            consume(NUMBER);
-            b.add(parserLine, LITERAL, JS.N(number.doubleValue() * -1));
+        case SUB: case ADD: {
+            if(peekToken() == NUMBER) {   // literal
+                consume(NUMBER);
+                b.add(parserLine, LITERAL, JS.N(number.doubleValue() * (tok == SUB ? -1 : 1)));
+            } else { // unary +/- operator
+                if(tok == SUB) b.add(parserLine, LITERAL, JS.ZERO);
+                // BITNOT has the same precedence as the unary +/- operators
+                startExpr(b,precedence[BITNOT]);
+                if(tok == ADD) b.add(parserLine, LITERAL, JS.ZERO); // HACK to force expr into a numeric context
+                b.add(parserLine, SUB);
+            }
             break;
         }
         case LP: {  // grouping (not calling)
@@ -219,18 +284,23 @@ class Parser extends Lexer implements ByteCodes {
         case INC: case DEC: {  // prefix (not postfix)
             startExpr(b, precedence[tok]);
             int prev = b.size - 1;
+            boolean sg = b.get(prev) == SCOPEGET;
             if (b.get(prev) == GET && b.getArg(prev) != null)
                 b.set(prev, LITERAL, b.getArg(prev));
             else if(b.get(prev) == GET)
                 b.pop();
-            else
+            else if(!sg)
                 throw pe("prefixed increment/decrement can only be performed on a valid assignment target");
-            b.add(parserLine, GET_PRESERVE, Boolean.TRUE);
+            if(!sg) b.add(parserLine, GET_PRESERVE, Boolean.TRUE);
             b.add(parserLine, LITERAL, JS.N(1));
             b.add(parserLine, tok == INC ? ADD : SUB, JS.N(2));
-            b.add(parserLine, PUT, null);
-            b.add(parserLine, SWAP, null);
-            b.add(parserLine, POP, null);
+            if(sg) {
+                b.add(parserLine, SCOPEPUT, b.getArg(prev));
+            } else {
+                b.add(parserLine, PUT, null);
+                b.add(parserLine, SWAP, null);
+                b.add(parserLine, POP, null);
+            }
             break;
         }
         case BANG: case BITNOT: case TYPEOF: {
@@ -245,7 +315,7 @@ class Parser extends Lexer implements ByteCodes {
                     if (peekToken() != NAME && peekToken() != STRING)
                         throw pe("expected NAME or STRING");
                     getToken();
-                    b.add(parserLine, LITERAL, string);                          // grab the key
+                    b.add(parserLine, LITERAL, JSString.intern(string));                          // grab the key
                     consume(COLON);
                     startExpr(b, NO_COMMA);                                      // grab the value
                     b.add(parserLine, PUT);                                      // put the value into the object
@@ -258,11 +328,25 @@ class Parser extends Lexer implements ByteCodes {
             break;
         }
         case NAME: {
-            b.add(parserLine, TOPSCOPE);
-            b.add(parserLine, LITERAL, string);
-            continueExprAfterAssignable(b,minPrecedence);
+            JS varKey = scopeKey(string);
+            if(varKey == null) {
+                b.add(parserLine, GLOBALSCOPE);
+                b.add(parserLine, LITERAL, JSString.intern(string));
+            }
+            continueExprAfterAssignable(b,minPrecedence,varKey);
+            break;
+        }
+        case CASCADE: {
+            if(peekToken() == ASSIGN) {
+                consume(ASSIGN);
+                startExpr(b, precedence[ASSIGN]);
+                b.add(parserLine, CASCADE, JS.T);
+            } else {
+                b.add(parserLine, CASCADE, JS.F);
+            }
             break;
         }
+                
         case FUNCTION: {
             consume(LP);
             int numArgs = 0;
@@ -270,27 +354,20 @@ class Parser extends Lexer implements ByteCodes {
             b.add(parserLine, NEWFUNCTION, b2);
 
             // function prelude; arguments array is already on the stack
-            b2.add(parserLine, TOPSCOPE);
-            b2.add(parserLine, SWAP);
-            b2.add(parserLine, DECLARE, "arguments");                     // declare arguments (equivalent to 'var arguments;')
-            b2.add(parserLine, SWAP);                                     // set this.arguments and leave the value on the stack
-            b2.add(parserLine, PUT);
+            scopePush(b2);
+            scopeDeclare("arguments");
+            b2.add(parserLine, SCOPEPUT,scopeKey("arguments"));
 
             while(peekToken() != RP) {                                    // run through the list of argument names
                 numArgs++;
                 if (peekToken() == NAME) {
                     consume(NAME);                                        // a named argument
-                    String varName = string;
-                    b2.formalArgs.push(varName);
+                    
                     b2.add(parserLine, DUP);                              // dup the args array 
                     b2.add(parserLine, GET, JS.N(numArgs - 1));   // retrieve it from the arguments array
-                    b2.add(parserLine, TOPSCOPE);
-                    b2.add(parserLine, SWAP);
-                    b2.add(parserLine, DECLARE, varName);                  // declare the name
-                    b2.add(parserLine, SWAP);
-                    b2.add(parserLine, PUT); 
-                    b2.add(parserLine, POP);                               // pop the value
-                    b2.add(parserLine, POP);                               // pop the scope                  
+                    scopeDeclare(string);
+                    b2.add(parserLine, SCOPEPUT, scopeKey(string));
+                    b2.add(parserLine, POP);
                 }
                 if (peekToken() == RP) break;
                 consume(COMMA);
@@ -299,13 +376,13 @@ class Parser extends Lexer implements ByteCodes {
 
             b2.numFormalArgs = numArgs;
             b2.add(parserLine, POP);                                      // pop off the arguments array
-            b2.add(parserLine, POP);                                      // pop off TOPSCOPE
             
            if(peekToken() != LC)
                 throw pe("JSFunctions must have a block surrounded by curly brackets");
                 
             parseBlock(b2, null);                                   // the function body
-
+            
+            scopePop(b2);
             b2.add(parserLine, LITERAL, null);                        // in case we "fall out the bottom", return NULL
             b2.add(parserLine, RETURN);
 
@@ -356,12 +433,12 @@ class Parser extends Lexer implements ByteCodes {
      *  expression that modifies the assignable.  This method always
      *  decreases the stack depth by exactly one element.
      */
-    private void continueExprAfterAssignable(JSFunction b,int minPrecedence) throws IOException {
+    private void continueExprAfterAssignable(JSFunction b,int minPrecedence, JS varKey) throws IOException {
         int saveParserLine = parserLine;
-        _continueExprAfterAssignable(b,minPrecedence);
+        _continueExprAfterAssignable(b,minPrecedence,varKey);
         parserLine = saveParserLine;
     }
-    private void _continueExprAfterAssignable(JSFunction b,int minPrecedence) throws IOException {
+    private void _continueExprAfterAssignable(JSFunction b,int minPrecedence, JS varKey) throws IOException {
         if (b == null) throw new Error("got null b; this should never happen");
         int tok = getToken();
         if (minPrecedence != -1 && (precedence[tok] < minPrecedence || (precedence[tok] == minPrecedence && !isRightAssociative[tok])))
@@ -385,52 +462,71 @@ class Parser extends Lexer implements ByteCodes {
             */
         case ASSIGN_BITOR: case ASSIGN_BITXOR: case ASSIGN_BITAND: case ASSIGN_LSH: case ASSIGN_RSH: case ASSIGN_URSH:
         case ASSIGN_MUL: case ASSIGN_DIV: case ASSIGN_MOD: case ASSIGN_ADD: case ASSIGN_SUB: case ADD_TRAP: case DEL_TRAP: {
-            if (tok != ADD_TRAP && tok != DEL_TRAP) b.add(parserLine, GET_PRESERVE);
+            if (tok != ADD_TRAP && tok != DEL_TRAP) 
+                b.add(parserLine, varKey == null ? GET_PRESERVE : SCOPEGET, varKey);
             
             startExpr(b,  precedence[tok]);
             
             if (tok != ADD_TRAP && tok != DEL_TRAP) {
                 // tok-1 is always s/^ASSIGN_// (0 is BITOR, 1 is ASSIGN_BITOR, etc) 
                 b.add(parserLine, tok - 1, tok-1==ADD ? JS.N(2) : null);
-                b.add(parserLine, PUT);
-                b.add(parserLine, SWAP);
-                b.add(parserLine, POP);
+                if(varKey == null) {
+                    b.add(parserLine, PUT);
+                    b.add(parserLine, SWAP);
+                    b.add(parserLine, POP);
+                } else {
+                    b.add(parserLine, SCOPEPUT, varKey);
+                }
             } else {
+                if(varKey != null) throw pe("cannot place traps on local variables");
                 b.add(parserLine, tok);
             }
             break;
         }
         case INC: case DEC: { // postfix
-            b.add(parserLine, GET_PRESERVE, Boolean.TRUE);
-            b.add(parserLine, LITERAL, JS.N(1));
-            b.add(parserLine, tok == INC ? ADD : SUB, JS.N(2));
-            b.add(parserLine, PUT, null);
-            b.add(parserLine, SWAP, null);
-            b.add(parserLine, POP, null);
-            b.add(parserLine, LITERAL, JS.N(1));
-            b.add(parserLine, tok == INC ? SUB : ADD, JS.N(2));   // undo what we just did, since this is postfix
+            if(varKey == null) {
+                b.add(parserLine, GET_PRESERVE, Boolean.TRUE);
+                b.add(parserLine, LITERAL, JS.N(1));
+                b.add(parserLine, tok == INC ? ADD : SUB, JS.N(2));
+                b.add(parserLine, PUT, null);
+                b.add(parserLine, SWAP, null);
+                b.add(parserLine, POP, null);
+                b.add(parserLine, LITERAL, JS.N(1));
+                b.add(parserLine, tok == INC ? SUB : ADD, JS.N(2));   // undo what we just did, since this is postfix
+            } else {
+                b.add(parserLine, SCOPEGET, varKey);
+                b.add(parserLine, DUP);
+                b.add(parserLine, LITERAL, JS.ONE);
+                b.add(parserLine, tok == INC ? ADD : SUB, JS.N(2));
+                b.add(parserLine, SCOPEPUT, varKey);
+            }
             break;
         }
         case ASSIGN: {
             startExpr(b, precedence[tok]);
-            b.add(parserLine, PUT);
-            b.add(parserLine, SWAP);
-            b.add(parserLine, POP);
+            if(varKey == null) {
+                b.add(parserLine, PUT);
+                b.add(parserLine, SWAP);
+                b.add(parserLine, POP);
+            } else {
+                b.add(parserLine, SCOPEPUT, varKey);
+            }
             break;
         }
         case LP: {
-
             // Method calls are implemented by doing a GET_PRESERVE
             // first.  If the object supports method calls, it will
             // return JS.METHOD
-            int n = parseArgs(b, 2);
-            b.add(parserLine, GET_PRESERVE);
-            b.add(parserLine, CALLMETHOD, JS.N(n));
+            b.add(parserLine, varKey == null ? GET_PRESERVE : SCOPEGET, varKey);
+            int n = parseArgs(b);
+            b.add(parserLine, varKey == null ? CALLMETHOD : CALL, JS.N(n));
             break;
         }
         default: {
             pushBackToken();
-            if(b.get(b.size-1) == LITERAL && b.getArg(b.size-1) != null)
+            if(varKey != null)
+                b.add(parserLine, SCOPEGET, varKey);
+            else if(b.get(b.size-1) == LITERAL && b.getArg(b.size-1) != null)
                 b.set(b.size-1,GET,b.getArg(b.size-1));
             else
                 b.add(parserLine, GET);
@@ -467,7 +563,7 @@ class Parser extends Lexer implements ByteCodes {
 
         switch (tok) {
         case LP: {  // invocation (not grouping)
-            int n = parseArgs(b, 1);
+            int n = parseArgs(b);
             b.add(parserLine, CALL, JS.N(n));
             break;
         }
@@ -507,14 +603,14 @@ class Parser extends Lexer implements ByteCodes {
             } else {
                 consume(NAME);
             }
-            b.add(parserLine, LITERAL, string);
-            continueExprAfterAssignable(b,minPrecedence);
+            b.add(parserLine, LITERAL, JSString.intern(string));
+            continueExprAfterAssignable(b,minPrecedence,null);
             break;
         }
         case LB: { // subscripting (not array constructor)
             startExpr(b, -1);
             consume(RB);
-            continueExprAfterAssignable(b,minPrecedence);
+            continueExprAfterAssignable(b,minPrecedence,null);
             break;
         }
         case HOOK: {
@@ -545,14 +641,12 @@ class Parser extends Lexer implements ByteCodes {
     }
     
     // parse a set of comma separated function arguments, assume LP has already been consumed
-    // if swap is true, (because the function is already on the stack) we will SWAP after each argument to keep it on top
-    private int parseArgs(JSFunction b, int pushdown) throws IOException {
+    private int parseArgs(JSFunction b) throws IOException {
         int i = 0;
         while(peekToken() != RP) {
             i++;
             if (peekToken() != COMMA) {
                 startExpr(b, NO_COMMA);
-                b.add(parserLine, SWAP, JS.N(pushdown));
                 if (peekToken() == RP) break;
             }
             consume(COMMA);
@@ -605,22 +699,19 @@ class Parser extends Lexer implements ByteCodes {
             break;
         }
         case VAR: {
-            b.add(parserLine, TOPSCOPE);                         // push the current scope
             while(true) {
                 consume(NAME);
-                b.add(parserLine, DECLARE, string);               // declare it
+                String var = string;
+                scopeDeclare(var);
                 if (peekToken() == ASSIGN) {                     // if there is an '=' after the variable name
                     consume(ASSIGN);
                     startExpr(b, NO_COMMA);
-                    b.add(parserLine, PUT);                      // assign it
+                    b.add(parserLine, SCOPEPUT, scopeKey(var)); // assign it
                     b.add(parserLine, POP);                      // clean the stack
-                } else {
-                    b.add(parserLine, POP);                      // pop the string pushed by declare
-                }   
+                }
                 if (peekToken() != COMMA) break;
                 consume(COMMA);
             }
-            b.add(parserLine, POP);                              // pop off the topscope
             if ((mostRecentlyReadToken != RC || peekToken() == SEMI) && peekToken() != -1 && mostRecentlyReadToken != SEMI) consume(SEMI);
             break;
         }
@@ -741,7 +832,7 @@ class Parser extends Lexer implements ByteCodes {
                         // extended Ibex catch block: catch(e faultCode "foo.bar.baz")
                         consume(NAME);
                         b.add(parserLine, DUP);
-                        b.add(parserLine, LITERAL, string);
+                        b.add(parserLine, LITERAL, JSString.intern(string));
                         b.add(parserLine, GET);
                         b.add(parserLine, DUP);
                         b.add(parserLine, LITERAL, null);
@@ -774,17 +865,12 @@ class Parser extends Lexer implements ByteCodes {
                     }
                     consume(RP);
                     // the exception is on top of the stack; put it to the chosen name
-                    b.add(parserLine, NEWSCOPE);
-                    b.add(parserLine, TOPSCOPE);
-                    b.add(parserLine, SWAP);
-                    b.add(parserLine, LITERAL,exceptionVar);
-                    b.add(parserLine, DECLARE);
-                    b.add(parserLine, SWAP);
-                    b.add(parserLine, PUT);
-                    b.add(parserLine, POP);
+                    scopePush(b);
+                    scopeDeclare(exceptionVar);
+                    b.add(parserLine, SCOPEPUT, scopeKey(exceptionVar));
                     b.add(parserLine, POP);
                     parseBlock(b, null);
-                    b.add(parserLine, OLDSCOPE);
+                    scopePop(b);
                     
                     b.add(parserLine, JMP);
                     catchEnds.addElement(new Integer(b.size-1));
@@ -841,70 +927,51 @@ class Parser extends Lexer implements ByteCodes {
                 consume(RP);
                 
                 b.add(parserLine, PUSHKEYS);
-                b.add(parserLine, DUP); 
-                b.add(parserLine, LITERAL, "length"); 
-                b.add(parserLine, GET);
-                // Stack is now: n, keys, obj, ...
                 
                 int size = b.size;
                 b.add(parserLine, LOOP);
                 b.add(parserLine, POP);
-                // Stack is now: LoopMarker, n, keys, obj, ...
-                // NOTE: This will break if the interpreter ever becomes more strict 
-                //       and prevents bytecode from messing with the Markers
-                b.add(parserLine, SWAP, JS.N(3));
-                // Stack is now: Tn, keys, obj, LoopMarker, ...
                 
-                b.add(parserLine, LITERAL, JS.N(1));
-                b.add(parserLine, SUB);
-                b.add(parserLine, DUP);
-                // Stack is now: index, keys, obj, LoopMarker
-                b.add(parserLine, LITERAL, JS.ZERO);
-                b.add(parserLine, LT);
-                // Stack is now index<0, index, keys, obj, LoopMarker, ...
+                b.add(parserLine,SWAP); // get the keys enumeration object on top
+                b.add(parserLine,DUP);
+                b.add(parserLine,GET,JS.S("hasMoreElements"));
+                int size2 = b.size;
+                b.add(parserLine,JT);
+                b.add(parserLine,SWAP);
+                b.add(parserLine,BREAK);
+                b.set(size2, JS.N(b.size - size2));
+                b.add(parserLine,DUP);
+                b.add(parserLine,GET,JS.S("nextElement"));
+
+                scopePush(b);
                 
-                b.add(parserLine, JF, JS.N(5)); // if we're >= 0 jump 5 down (to NEWSCOPE)
-                // Move the LoopMarker back  into place - this is sort of ugly
-                b.add(parserLine, SWAP, JS.N(3));
-                b.add(parserLine, SWAP, JS.N(3));
-                b.add(parserLine, SWAP, JS.N(3));
-                // Stack is now: LoopMarker, -1, keys, obj, ...
-                b.add(parserLine, BREAK);
+                if(hadVar) scopeDeclare(varName);
+                JS varKey = scopeKey(varName);
                 
-                b.add(parserLine, NEWSCOPE);
-                if(hadVar) {
-                    b.add(parserLine, DECLARE, varName);
-                    b.add(parserLine, POP);
+                if(varKey == null) {
+                    b.add(parserLine,GLOBALSCOPE);
+                    b.add(parserLine,SWAP);
+                    b.add(parserLine, LITERAL, JSString.intern(varName));
+                    b.add(parserLine,SWAP);
+                    b.add(parserLine,PUT);
+                    b.add(parserLine,POP);
+                } else {
+                    b.add(parserLine, SCOPEPUT, varKey);
                 }
-                
-                // Stack is now: index, keys, obj, LoopMarker, ...
-                b.add(parserLine, GET_PRESERVE);     // key, index, keys, obj, LoopMarker, ...
-                b.add(parserLine, TOPSCOPE);         // scope, key, index, keys, obj, LoopMarker, ...
-                b.add(parserLine, SWAP);             // key, scope, index, keys, obj, LoopMarker, ...
-                b.add(parserLine, LITERAL, varName); // varName, key, scope, index, keys, obj, LoopMaker, ...
-                b.add(parserLine, SWAP);             // key, varName, scope, index, keys, obj, LoopMarker, ...
-                b.add(parserLine, PUT);              // key, scope, index, keys, obj, LoopMarker, ...
-                b.add(parserLine, POP);              // scope, index, keys, obj, LoopMarker
-                b.add(parserLine, POP);              // index, keys, obj, LoopMarker, ...
-                // Move the LoopMarker back into place - this is sort of ugly
-                b.add(parserLine, SWAP, JS.N(3));
-                b.add(parserLine, SWAP, JS.N(3));
-                b.add(parserLine, SWAP, JS.N(3));
+                b.add(parserLine,POP);  // pop the put'ed value
+                b.add(parserLine,SWAP); // put CallMarker back into place
                 
                 parseStatement(b, null);
                 
-                b.add(parserLine, OLDSCOPE);
+                scopePop(b);
                 b.add(parserLine, CONTINUE);
                 // jump here on break
                 b.set(size, JS.N(b.size - size));
                 
-                b.add(parserLine, POP); // N
-                b.add(parserLine, POP); // KEYS
-                b.add(parserLine, POP); // OBJ
-                
+                b.add(parserLine, POP);
             } else {
                 if (hadVar) pushBackToken(VAR, null);                    // yeah, this actually matters
-                b.add(parserLine, NEWSCOPE);                             // grab a fresh scope
+                scopePush(b);                             // grab a fresh scope
                     
                 parseStatement(b, null);                                 // initializer
                 JSFunction e2 =                                    // we need to put the incrementor before the test
@@ -912,7 +979,7 @@ class Parser extends Lexer implements ByteCodes {
                 if (peekToken() != SEMI)
                     startExpr(e2, -1);
                 else
-                    e2.add(parserLine, JSFunction.LITERAL, Boolean.TRUE);         // handle the for(foo;;foo) case
+                    e2.add(parserLine, JSFunction.LITERAL, JS.T);         // handle the for(foo;;foo) case
                 consume(SEMI);
                 if (label != null) b.add(parserLine, LABEL, label);
                 b.add(parserLine, LOOP);
@@ -934,7 +1001,7 @@ class Parser extends Lexer implements ByteCodes {
                 b.add(parserLine, CONTINUE);                             // if we fall out the bottom, CONTINUE
                 b.set(size2 - 1, JS.N(b.size - size2 + 1));     // end of the loop
                     
-                b.add(parserLine, OLDSCOPE);                             // get our scope back
+                scopePop(b);                            // get our scope back
             }
             break;
         }
@@ -958,9 +1025,9 @@ class Parser extends Lexer implements ByteCodes {
 
         case LC: {  // blocks are statements too
             pushBackToken();
-            b.add(parserLine, NEWSCOPE);
+            scopePush(b);
             parseBlock(b, label);
-            b.add(parserLine, OLDSCOPE);
+            scopePop(b);
             break;
         }
 
index 3227922..ee7be2e 100644 (file)
@@ -5,10 +5,12 @@ import org.ibex.util.*;
 import java.util.*;
 import java.io.*;
 
+// FEATURE: Update for new api
+
 /** A JS interface to a Java '.properties' file; very crude */
 public class PropertyFile extends JS {
 
-    private final Properties p = new Properties();
+    /*private final Properties p = new Properties();
     private final Hash cache = new Hash(10, 3);
     private File f;
 
@@ -47,5 +49,5 @@ public class PropertyFile extends JS {
         Object ret = p.get(toString(key));
         if (ret != null) return ret;
         return new Minion(escape(toString(key)));
-    }
+    }*/
 }
index c2764d2..6843f53 100644 (file)
@@ -41,7 +41,7 @@ public class SOAP extends XMLRPC {
         if (name.equals("SOAP-ENV:Fault")) fault = true;
  
         // add a generic struct; we'll change this if our type is different
-        objects.addElement(new JS());
+        objects.addElement(new JS.O());
 
         for(int i=0; i<keys.length; i++) {
             String key = keys[i];
@@ -49,16 +49,16 @@ public class SOAP extends XMLRPC {
             if (key.endsWith("ype")) {
                 if (value.endsWith("boolean")) {
                     objects.removeElementAt(objects.size() - 1);
-                    objects.addElement(Boolean.FALSE);
+                    objects.addElement(JS.B(true));
                 } else if (value.endsWith("int")) {
                     objects.removeElementAt(objects.size() - 1);
-                    objects.addElement(new Integer(0));
+                    objects.addElement(JS.N(0));
                 } else if (value.endsWith("double")) {
                     objects.removeElementAt(objects.size() - 1);
-                    objects.addElement(new Double(0.0));
+                    objects.addElement(JS.N(0.0));
                 } else if (value.endsWith("string")) {
                     objects.removeElementAt(objects.size() - 1);
-                    objects.addElement("");
+                    objects.addElement(JS.S(""));
                 } else if (value.endsWith("base64")) {
                     objects.removeElementAt(objects.size() - 1);
                     objects.addElement(new byte[] { });
@@ -136,10 +136,10 @@ public class SOAP extends XMLRPC {
         }
         
         // remove ourselves
-        Object me = objects.elementAt(objects.size() - 1);
+        JS me = (JS)objects.elementAt(objects.size() - 1);
 
         // find our parent
-        Object parent = objects.size() > 1 ? objects.elementAt(objects.size() - 2) : null;
+        JS parent = objects.size() > 1 ? (JS)objects.elementAt(objects.size() - 2) : (JS)null;
 
         // we want to fold stuff back into the fault object
         if (objects.size() < 2) return;
@@ -152,7 +152,7 @@ public class SOAP extends XMLRPC {
         } else if (parent != null && parent instanceof JS) {
             objects.removeElementAt(objects.size() - 1);
             try {
-                ((JS)parent).put(name, me);
+                ((JS)parent).put(JS.S(name), me);
             } catch (JSExn e) {
                 throw new Error("this should never happen");
             }
@@ -235,7 +235,7 @@ public class SOAP extends XMLRPC {
             Enumeration e = j.keys();
             while(e.hasMoreElements()) {
                 Object key = e.nextElement();
-                appendObject((String)key, j.get(key), sb);
+                appendObject((String)key, j.get((JS)key), sb);
             }
             sb.append("</" + name + ">\r\n");
 
@@ -260,8 +260,8 @@ public class SOAP extends XMLRPC {
         if (args.length() > 0) {
             Enumeration e = ((JS)args.elementAt(0)).keys();
             while(e.hasMoreElements()) {
-                Object key = e.nextElement();
-                appendObject((String)key, ((JS)args.elementAt(0)).get(key), content);
+                JS key = (JS)e.nextElement();
+                appendObject(((JSString)key).coerceToString(), ((JS)args.elementAt(0)).get(key), content);
             }
         }
         content.append("    </" + method + "></SOAP-ENV:Body></SOAP-ENV:Envelope>\r\n");
index 8970e14..126a10b 100644 (file)
@@ -96,6 +96,7 @@ interface Tokens {
     public static final int GRAMMAR       = 78;  // the grammar-definition operator (::=)
     public static final int ADD_TRAP      = 79;  // the add-trap operator (++=)
     public static final int DEL_TRAP      = 80;  // the del-trap operator (--=)
+    public static final int CASCADE       = 81;  // cascade expression - arg==true for write cascade
  
     public static final int MAX_TOKEN = DEL_TRAP;
 
@@ -112,7 +113,7 @@ interface Tokens {
         "HOOK", "COLON", "INC", "DEC", "DOT", "FUNCTION", "IF",
         "ELSE", "SWITCH", "CASE", "DEFAULT", "WHILE", "DO", "FOR",
         "VAR", "WITH", "CATCH", "FINALLY", "RESERVED", "GRAMMAR",
-        "ADD_TRAP", "DEL_TRAP"
+        "ADD_TRAP", "DEL_TRAP", "CASCADE"
     };
 
 }
index 77476f6..580ed69 100644 (file)
@@ -7,55 +7,22 @@ package org.ibex.js;
  *  linked list stack, with the most recently placed trap at the head
  *  of the list.
  */
-class Trap {
+final class Trap {
 
-    JS trapee = null;          ///< the box on which this trap was placed
-    Object name = null;        ///< the property that the trap was placed on
+    final JS target;          ///< the box on which this trap was placed
+    final JS key;             ///< the property that the trap was placed on
 
-    JSFunction f = null;       ///< the function for this trap
-    Trap next = null;          ///< the next trap down the trap stack
+    final JSFunction f;       ///< the function for this trap
+    Trap next;                ///< the next trap down the trap stack
 
-    Trap(JS b, String n, JSFunction f, Trap nx) {
-        trapee = b; name = n; this.f = f; this.next = nx;
-    }
-
-    static final JSFunction putInvoker = new JSFunction("putInvoker", 0, null);
-    static final JSFunction getInvoker = new JSFunction("getInvoker", 0, null);
-
-    static {
-        putInvoker.add(1, ByteCodes.PUT, null);
-        putInvoker.add(2, Tokens.RETURN, null);
-        getInvoker.add(1, ByteCodes.GET, null);
-        getInvoker.add(2, Tokens.RETURN, null);
+    Trap(JS b, JS n, JSFunction f, Trap nx) {
+        target = b; key = n; this.f = f; this.next = nx;
     }
     
-    void invoke(Object value) throws JSExn {
-        Interpreter i = new Interpreter(putInvoker, false, null);
-        i.stack.push(trapee);
-        i.stack.push(name);
-        i.stack.push(value);
-        i.resume();
-    }
-
-    Object invoke() throws JSExn {
-        Interpreter i = new Interpreter(getInvoker, false, null);
-        i.stack.push(trapee);
-        i.stack.push(name);
-        return i.resume();
-    }
-
-    // FIXME: review; is necessary?
-    static class TrapScope extends JSScope {
-        Trap t;
-        Object val = null;
-        boolean cascadeHappened = false;
-        public TrapScope(JSScope parent, Trap t, Object val) { super(parent); this.t = t; this.val = val; }
-        public Object get(Object key) throws JSExn {
-            if (key.equals("trapee")) return t.trapee;
-            if (key.equals("callee")) return t.f;
-            if (key.equals("trapname")) return t.name;
-            return super.get(key);
-        }
-    }
+    boolean isReadTrap()  { return f.numFormalArgs == 0; }
+    boolean isWriteTrap() { return f.numFormalArgs != 0; }
+    Trap readTrap()  { Trap t = this; while(t!=null && t.isWriteTrap()) t = t.next; return t; }
+    Trap writeTrap() { Trap t = this; while(t!=null && t.isReadTrap())  t = t.next; return t; }
+    Trap nextReadTrap()  { return next == null ? null : next.readTrap();  }
+    Trap nextWriteTrap() { return next == null ? null : next.writeTrap(); }
 }
-
index 6f50328..6db685c 100644 (file)
@@ -38,8 +38,8 @@ public class XMLRPC extends JS {
     }
     public XMLRPC(String url, String method, XMLRPC httpSource) {
         this.http = httpSource.http; this.url = url; this.method = method; }
-    public Object get(Object name) {
-        return new XMLRPC(url, (method.equals("") ? "" : method + ".") + name.toString(), this); }
+    public JS get(JS name) throws JSExn {
+        return new XMLRPC(url, (method.equals("") ? "" : method + ".") + JS.toString(name), this); }
 
 
     /** this holds character content as we read it in -- since there is only one per instance, we don't support mixed content */
@@ -84,7 +84,7 @@ public class XMLRPC extends JS {
             content.reset();
             //#switch(c.getLocalName())
             case "fault": fault = true;
-            case "struct": objects.setElementAt(new JS(), objects.size() - 1);
+            case "struct": objects.setElementAt(new JS.O(), objects.size() - 1);
             case "array": objects.setElementAt(null, objects.size() - 1);
             case "value": objects.addElement("");
             //#end
@@ -129,8 +129,8 @@ public class XMLRPC extends JS {
                                     "the server sent a <dateTime.iso8601> tag which was malformed: " + s);
                 }
             case "member":
-                Object memberValue = objects.elementAt(objects.size() - 1);
-                String memberName = (String)objects.elementAt(objects.size() - 2);
+                JS memberValue = (JS)objects.elementAt(objects.size() - 1);
+                JS memberName = (JS)objects.elementAt(objects.size() - 2);
                 JS struct = (JS)objects.elementAt(objects.size() - 3);
                 try {
                     struct.put(memberName, memberValue);
@@ -143,7 +143,7 @@ public class XMLRPC extends JS {
                 for(i=objects.size() - 1; objects.elementAt(i) != null; i--);
                 JSArray arr = new JSArray();
                 try {
-                    for(int j = i + 1; j<objects.size(); j++) arr.put(new Integer(j - i - 1), objects.elementAt(j));
+                    for(int j = i + 1; j<objects.size(); j++) arr.put(JS.N(j - i - 1), (JS)objects.elementAt(j));
                 } catch (JSExn e) {
                     throw new Error("this should never happen");
                 }
@@ -272,7 +272,7 @@ public class XMLRPC extends JS {
 
         } else if (o instanceof JSArray) {
             if (tracker.get(o) != null) throw new JSExn("attempted to send multi-ref data structure via XML-RPC");
-            tracker.put(o, Boolean.TRUE);
+            tracker.put(o, JS.B(true));
             sb.append("                <value><array><data>\n");
             JSArray a = (JSArray)o;
             for(int i=0; i<a.length(); i++) appendObject(a.elementAt(i), sb);
@@ -280,14 +280,14 @@ public class XMLRPC extends JS {
 
         } else if (o instanceof JS) {
             if (tracker.get(o) != null) throw new JSExn("attempted to send multi-ref data structure via XML-RPC");
-            tracker.put(o, Boolean.TRUE);
+            tracker.put(o, JS.B(true));
             JS j = (JS)o;
             sb.append("                <value><struct>\n");
             Enumeration e = j.keys();
             while(e.hasMoreElements()) {
                 Object key = e.nextElement();
                 sb.append("                <member><name>" + key + "</name>\n");
-                appendObject(j.get(key), sb);
+                appendObject(j.get((JS)key), sb);
                 sb.append("                </member>\n");
             }
             sb.append("                </struct></value>\n");
@@ -302,7 +302,7 @@ public class XMLRPC extends JS {
     // Call Sequence //////////////////////////////////////////////////////////////////////////
 
     /* FIXME this has been disabled to make XMLRPC usable without Scheduler
-    public final Object call(Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn {
+    public final Object call(JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn {
         JSArray args = new JSArray();
         for(int i=0; i<nargs; i++) args.addElement(i==0?a0:i==1?a1:i==2?a2:rest[i-3]);
         return call(args);
@@ -331,7 +331,8 @@ public class XMLRPC extends JS {
             BufferedReader br = new BufferedReader(new InputStreamReader(is));
             try {
                 new Helper().parse(br);
-                final Object result = fault ? new JSExn(objects.elementAt(0)) : objects.size() == 0 ? null : objects.elementAt(0);
+               if (fault) throw new JSExn((JS)objects.elementAt(0));
+                final JS result = (objects.size() == 0 ? (JS)null : ((JS)objects.elementAt(0)));
                 return (new Task() { public void perform() throws JSExn { callback.unpause(result); }});
             } finally {
                 tracker.clear();
@@ -339,13 +340,13 @@ public class XMLRPC extends JS {
             }
         } catch (final JSExn e) {
             final Exception e2 = e;
-            return (new Task() { public void perform() throws JSExn { callback.unpause(e2); }});
+            return (new Task() { public void perform() throws JSExn { callback.unpause((JSExn)e2); }});
         } catch (final IOException e) {
             final Exception e2 = e;
-            return (new Task() { public void perform() throws JSExn { callback.unpause(new JSExn(e2)); }});
+            return (new Task() { public void perform() throws JSExn { callback.unpause(new JSExn(e2.getMessage())); }});
         } catch (final XML.Exn e) {
             final Exception e2 = e;
-            return (new Task() { public void perform() throws JSExn { callback.unpause(new JSExn(e2)); }});
+            return (new Task() { public void perform() throws JSExn { callback.unpause(new JSExn(e2.getMessage())); }});
         }
     }
 }