local scope vars indexed by number
[org.ibex.core.git] / src / org / ibex / js / Interpreter.java
index 83ff539..04f6784 100644 (file)
@@ -6,8 +6,6 @@ import java.util.*;
 
 /** Encapsulates a single JS interpreter (ie call stack) */
 class Interpreter implements ByteCodes, Tokens {
-    private static final JS CASCADE = JSString.intern("cascade");
-    
     // Thread-Interpreter Mapping /////////////////////////////////////////////////////////////////////////
 
     static Interpreter current() { return (Interpreter)threadToInterpreter.get(Thread.currentThread()); }
@@ -25,7 +23,7 @@ class Interpreter implements ByteCodes, Tokens {
     Interpreter(JSFunction f, boolean pauseable, JSArgs args) {
         this.f = f;
         this.pausecount = pauseable ? 0 : -1;
-        this.scope = new JSScope(f.parentScope);
+        this.scope = f.parentScope;
         try {
             stack.push(new CallMarker(null));    // the "root function returned" marker -- f==null
             stack.push(args);
@@ -46,6 +44,7 @@ class Interpreter implements ByteCodes, Tokens {
     /** this is the only synchronization point we need in order to be threadsafe */
     synchronized JS resume() throws JSExn {
         if(f == null) throw new RuntimeException("function already finished");
+        if(scope == null) throw new RuntimeException("scope is null");
         Thread t = Thread.currentThread();
         Interpreter old = (Interpreter)threadToInterpreter.get(t);
         threadToInterpreter.put(t, this);
@@ -89,16 +88,22 @@ class Interpreter implements ByteCodes, Tokens {
             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 TOPSCOPE: stack.push(scope); 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: stack.swap(); break;
             case DUP: stack.push(stack.peek()); break;
-            case NEWSCOPE: scope = new JSScope(scope); break;
-            case OLDSCOPE: scope = scope.getParentScope(); 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;
@@ -173,7 +178,7 @@ class Interpreter implements ByteCodes, Tokens {
                         continue OUTER;
                     } else if (o instanceof CallMarker) {
                         boolean didTrapPut = false;
-                        if (o instanceof TrapMarker) { // handles return component of a read trap
+                        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) {
@@ -209,7 +214,44 @@ class Interpreter implements ByteCodes, Tokens {
                 }
                 throw new Error("error: RETURN invoked but couldn't find a CallMarker!");
             }
-
+                
+            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 {
+                        JS ret = target.get(key);
+                        if (ret == JS.METHOD) ret = new Stub(target, key);
+                        stack.push(ret);                        
+                    }
+                    if (pausecount > initialPauseCount) { pc++; return null; }   // we were paused                    
+                }
+                break;
+            }
+                
             case PUT: {
                 JS val = stack.pop();
                 JS key = stack.pop();
@@ -217,24 +259,9 @@ class Interpreter implements ByteCodes, Tokens {
                 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 = null;
-                TrapMarker tm = null;
-                if(target instanceof JSScope && key.jsequals(CASCADE)) {
-                    CallMarker o = stack.findCall();
-                    if(o instanceof TrapMarker) {
-                        tm = (TrapMarker) o;
-                        target = tm.t.target;
-                        key = tm.t.key;
-                        tm.cascadeHappened = true;
-                        t = tm.t;
-                        if(t.isReadTrap()) throw new JSExn("can't do a write cascade in a read trap");
-                        t = t.nextWriteTrap();
-                    }
-                }
-                if(tm == null) { // not cascading
-                    t = target instanceof JSScope ? t = ((JSScope)target).top().getTrap(key) : target.getTrap(key);
-                    if(t != null) t = t.writeTrap();
-                }
+                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);
@@ -244,15 +271,13 @@ class Interpreter implements ByteCodes, Tokens {
                 stack.push(val);
                 
                 if(t != null) {
-                    setupTrap(t,val,new TrapMarker(this,t,val, tm != null && tm.pauseOnPut));
+                    setupTrap(t,val,new TrapMarker(this,t,val));
                     pc--; // we increment later
-                    break;
                 } else {
-                    if (tm != null && tm.pauseOnPut) { pc++; return val; }
                     target.put(key,val);
                     if (pausecount > initialPauseCount) { pc++; return null; }   // we were paused
-                    break;
                 }
+                break;
             }
 
             case GET:
@@ -267,26 +292,12 @@ class Interpreter implements ByteCodes, Tokens {
                     stack.push(key);
                 }
                 JS ret = null;
-                if (key == null) throw je("tried to get the null key from " + target);
-                if (target == null) throw je("tried to get property \"" + key + "\" from the null object");
+                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();
                 
-                Trap t = null;
-                TrapMarker tm = null;
-                if(target instanceof JSScope && key.jsequals(CASCADE)) {
-                    CallMarker o = stack.findCall();
-                    if(o instanceof TrapMarker) {
-                        tm = (TrapMarker) o;
-                        target = tm.t.target;
-                        key = tm.t.key;
-                        t = tm.t;
-                        if(t.isWriteTrap()) throw new JSExn("can't do a read cascade in a write trap");
-                        t = t.nextReadTrap();
-                    }
-                }
-                if(tm == null) { // not cascading
-                    t = target instanceof JSScope ? t = ((JSScope)target).top().getTrap(key) : ((JS)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);
@@ -296,14 +307,13 @@ class Interpreter implements ByteCodes, Tokens {
                 if(t != null) {
                     setupTrap(t,null,new TrapMarker(this,t,null));
                     pc--; // we increment later
-                    break;
                 } 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;
                 }
+                break;
             }
             
             case CALL: case CALLMETHOD: {
@@ -337,7 +347,7 @@ class Interpreter implements ByteCodes, Tokens {
                     stack.push(new CallMarker(this));
                     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 {
@@ -382,14 +392,6 @@ class Interpreter implements ByteCodes, Tokens {
                 JS js = stack.peek();
                 // A trap addition/removal
                 if(!(val instanceof JSFunction)) throw new JSExn("tried to add/remove a non-function trap");
-                if(js instanceof JSScope) {
-                    JSScope s = (JSScope) js;
-                    while(s.getParentScope() != null) {
-                        if(s.has(key)) throw new JSExn("cannot trap a variable that isn't at the top level scope");
-                        s = s.getParentScope();
-                    }
-                    js = s;
-                }
                 if(op == ADD_TRAP) js.addTrap(key, (JSFunction)val);
                 else js.delTrap(key, (JSFunction)val);
                 break;
@@ -528,9 +530,9 @@ class Interpreter implements ByteCodes, Tokens {
 
     void setupTrap(Trap t, JS val, CallMarker cm) throws JSExn {
         stack.push(cm);
-        stack.push(t.isWriteTrap() ? new JSArgs(val,t.f) : new JSArgs(t.f));
+        stack.push(new TrapArgs(t,val));
         f = t.f;
-        scope = new TrapScope(t.f.parentScope,t);
+        scope = f.parentScope;
         pc = 0;
     }
 
@@ -603,16 +605,17 @@ class Interpreter implements ByteCodes, Tokens {
         public FinallyData(JSExn exn) { this.exn = exn; this.op = -1; this.arg = null; } // Just throw this exn
     }
 
-    static class TrapScope extends JSScope {
+    static class TrapArgs extends JS {
         private Trap t;
-        public TrapScope(JSScope parent, Trap t) {
-            super(parent); this.t = 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;
             //#jswitch(key)
             case "trapee": return t.target;
             case "callee": return t.f;
             case "trapname": return t.key;
+            case "length": return t.isWriteTrap() ? ONE : ZERO;
             //#end
             return super.get(key);
         }