move JS's Hashtable to JS.O
[org.ibex.core.git] / src / org / ibex / js / Interpreter.java
index 1b679ca..d16c582 100644 (file)
@@ -6,7 +6,7 @@ import java.util.*;
 
 /** Encapsulates a single JS interpreter (ie call stack) */
 class Interpreter implements ByteCodes, Tokens {
-
+    private static final int MAX_STACK_SIZE = 512;
 
     // Thread-Interpreter Mapping /////////////////////////////////////////////////////////////////////////
 
@@ -75,7 +75,7 @@ class Interpreter implements ByteCodes, Tokens {
             }
             switch(op) {
             case LITERAL: stack.push(arg); break;
-            case OBJECT: stack.push(new JS()); break;
+            case OBJECT: stack.push(new JS.O()); 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;
@@ -93,7 +93,9 @@ class Interpreter implements ByteCodes, Tokens {
             case DUP: stack.push(stack.peek()); break;
             case NEWSCOPE: scope = new JSScope(scope); break;
             case OLDSCOPE: scope = scope.getParentScope(); break;
-            case ASSERT: if (!JS.toBoolean(stack.pop())) throw je("ibex.assertion.failed" /*FEATURE: line number*/); break;
+            case ASSERT:
+                if (JS.checkAssertions && !JS.toBoolean(stack.pop()))
+                    throw je("ibex.assertion.failed" /*FEATURE: line number*/); 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;
@@ -170,26 +172,24 @@ 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);
+                        if (o instanceof TrapMarker) { // handles return component of a read trap
+                            TrapMarker tm = (TrapMarker) o;
+                            boolean cascade = tm.t.writeTrap() && !tm.cascadeHappened && !JS.toBoolean(retval);
+                            if(cascade) {
+                                Trap t = tm.t.next;
+                                while(t != null && t.readTrap()) t = t.next;
+                                if(t != null) {
+                                    tm.t = t;
+                                    stack.push(tm);
                                     JSArray args = new JSArray();
-                                    args.addElement(ts.val);
+                                    args.addElement(tm.val);
                                     stack.push(args);
                                     f = t.f;
-                                    scope = new Trap.TrapScope(f.parentScope, t, ts.val);
+                                    scope = new JSScope(f.parentScope);
                                     pc = -1;
                                     continue OUTER;
+                                } else {
+                                    tm.trapee.put(tm.key,tm.val);
                                 }
                             }
                         }
@@ -197,6 +197,7 @@ class Interpreter implements ByteCodes, Tokens {
                         pc = ((CallMarker)o).pc - 1;
                         f = (JSFunction)((CallMarker)o).f;
                         stack.push(retval);
+                        if (pausecount > initialPauseCount) { pc++; return null; }   // we were paused
                         continue OUTER;
                     }
                 }
@@ -213,102 +214,109 @@ class Interpreter implements ByteCodes, Tokens {
                     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");
+                
+                if (target instanceof String || target instanceof Number || target instanceof Boolean) throw new JSExn("can't put values to primitives");
+                if(!(target instanceof JS)) throw new Error("should never happen");
 
                 Trap t = null;
-                if (target instanceof Trap.TrapScope && key.equals("cascade")) {
-                    Trap.TrapScope ts = (Trap.TrapScope)target;
-                    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); }
-                    } else {
-                        t = ((JS)target).getTrap(key);
-                    }
-
-                    while (t != null && t.f.numFormalArgs == 0) t = t.next; // find the first write trap
-                    if (t != null) {
-                        stack.push(new CallMarker(this));
-                        JSArray args = new JSArray();
-                        args.addElement(val);
-                        stack.push(args);
+                TrapMarker tm = null;
+                if(target instanceof JSScope && key.equals("cascade")) {
+                    Object o=null;
+                    int i;
+                    for(i=stack.size()-1;i>=0;i--) if((o = stack.elementAt(i)) instanceof CallMarker) break;
+                    if(i==0) throw new Error("didn't find a call marker while doing cascade");
+                    if(o instanceof TrapMarker) {
+                        tm = (TrapMarker) o;
+                        target = tm.trapee;
+                        key = tm.key;
+                        tm.cascadeHappened = true;
+                        t = tm.t;
+                        if(t.readTrap()) throw new JSExn("can't put to cascade in a read trap");
+                        t = t.next;
+                        while(t != null && t.readTrap()) t = t.next;
                     }
                 }
-                if (t != null) {
+                if(tm == null) { // didn't find a trap marker, try to find a trap
+                    t = target instanceof JSScope ? t = ((JSScope)target).top().getTrap(key) : ((JS)target).getTrap(key);
+                    while(t != null && t.readTrap()) t = t.next;
+                }
+                
+                stack.push(val);
+                
+                if(t != null) {
+                    stack.push(new TrapMarker(this,t,(JS)target,key,val));
+                    JSArray args = new JSArray();
+                    args.addElement(val);
+                    stack.push(args);
                     f = t.f;
-                    scope = new Trap.TrapScope(f.parentScope, t, val);
+                    scope = new JSScope(f.parentScope);
                     pc = -1;
                     break;
+                } else {
+                    ((JS)target).put(key,val);
+                    if (pausecount > initialPauseCount) { pc++; return null; }   // we were paused
+                    break;
                 }
-                ((JS)target).put(key, val);
-                if (pausecount > initialPauseCount) { pc++; return null; }   // we were paused
-                stack.push(val);
-                break;
             }
 
             case GET:
             case GET_PRESERVE: {
-                Object o, v;
+                Object target, key;
                 if (op == GET) {
-                    v = arg == null ? stack.pop() : arg;
-                    o = stack.pop();
+                    key = arg == null ? stack.pop() : 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);
+                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 (target instanceof String || target instanceof Number || target instanceof Boolean) {
+                    ret = getFromPrimitive(target,key);
                     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);
-                        }
-                    }
-                    if (t != null) {
-                        f = t.f;
-                        scope = new Trap.TrapScope(f.parentScope, t, null);
-                        ((Trap.TrapScope)scope).cascadeHappened = true;
-                        pc = -1;
-                        break;
+                }
+                if(!(target instanceof JS)) throw new Error("should never happen");
+                
+                Trap t = null;
+                TrapMarker tm = null;
+                if(target instanceof JSScope && key.equals("cascade")) {
+                    Object o=null;
+                    int i;
+                    for(i=stack.size()-1;i>=0;i--) if((o = stack.elementAt(i)) instanceof CallMarker) break;
+                    if(i==0) throw new Error("didn't find a call marker while doing cascade");
+                    if(o instanceof TrapMarker) {
+                        tm = (TrapMarker) o;
+                        target = tm.trapee;
+                        key = tm.key;
+                        t = tm.t;
+                        if(t.writeTrap()) throw new JSExn("can't do a write cascade in a read trap");
+                        t = t.next;
+                        while(t != null && t.writeTrap()) t = t.next;
+                        if(t != null) tm.cascadeHappened = true;
                     }
-                    ret = ((JS)o).get(v);
-                    if (ret == JS.METHOD) ret = new Stub((JS)o, v);
-                    if (pausecount > initialPauseCount) { pc++; return null; }   // we were paused
+                }
+                if(tm == null) { // didn't find a trap marker, try to find a trap
+                    t = target instanceof JSScope ? t = ((JSScope)target).top().getTrap(key) : ((JS)target).getTrap(key);
+                    while(t != null && t.writeTrap()) t = t.next;
+                }
+                
+                if(t != null) {
+                    stack.push(new TrapMarker(this,t,(JS)target,key,null));
+                    stack.push(new JSArray());
+                    f = t.f;
+                    scope = new JSScope(f.parentScope);
+                    pc = -1;
+                    break;
+                } else {
+                    ret = ((JS)target).get(key);
+                    if (ret == JS.METHOD) ret = new Stub((JS)target, key);
                     stack.push(ret);
+                    if (pausecount > initialPauseCount) { pc++; return null; }   // we were paused
                     break;
                 }
-                throw je("tried to get property " + v + " from a " + o.getClass().getName());
             }
             
             case CALL: case CALLMETHOD: {
@@ -344,6 +352,7 @@ class Interpreter implements ByteCodes, Tokens {
                     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]);
                     stack.push(new CallMarker(this));
+                    if(stack.size() > MAX_STACK_SIZE) throw new JSExn("stack overflow");
                     stack.push(arguments);
                     f = (JSFunction)object;
                     scope = new JSScope(f.parentScope);
@@ -364,8 +373,9 @@ class Interpreter implements ByteCodes, Tokens {
             }
 
             case THROW:
-                throw new JSExn(stack.pop());
+                throw new JSExn(stack.pop(), stack, f, pc, scope);
 
+                /* FIXME
             case MAKE_GRAMMAR: {
                 final Grammar r = (Grammar)arg;
                 final JSScope final_scope = scope;
@@ -387,13 +397,23 @@ class Interpreter implements ByteCodes, Tokens {
                 stack.push(r2);
                 break;
             }
-
+                */
             case ADD_TRAP: case DEL_TRAP: {
                 Object val = stack.pop();
                 Object key = stack.pop();
                 Object obj = stack.peek();
                 // A trap addition/removal
-                JS js = obj instanceof JSScope ? ((JSScope)obj).top() : (JS) obj;
+                JS js = (JS) obj;
+                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;
+                }
+                // might want this?
+                // if(!js.has(key)) throw new JSExn("tried to add/remove a trap from an uninitialized variable");
                 if(op == ADD_TRAP) js.addTrap(key, (JSFunction)val);
                 else js.delTrap(key, (JSFunction)val);
                 break;
@@ -455,6 +475,7 @@ class Interpreter implements ByteCodes, Tokens {
             }
 
             default: {
+                if(op == BITOR) throw new Error("pc: " + pc + " of " + f);
                 Object right = stack.pop();
                 Object left = stack.pop();
                 switch(op) {
@@ -477,7 +498,7 @@ class Interpreter implements ByteCodes, Tokens {
                     if (right == null) right = JS.N(0);
                     int result = 0;
                     if (left instanceof String || right instanceof String) {
-                        result = left.toString().compareTo(right.toString());
+                        result = JS.toString(left).compareTo(JS.toString(right));
                     } else {
                         result = (int)java.lang.Math.ceil(JS.toDouble(left) - JS.toDouble(right));
                     }
@@ -488,6 +509,7 @@ class Interpreter implements ByteCodes, Tokens {
                     
                 case EQ:
                 case NE: {
+                    // FIXME: This is not correct, see ECMA-262 11.9.3
                     Object l = left;
                     Object r = right;
                     boolean ret;
@@ -496,7 +518,7 @@ class Interpreter implements ByteCodes, Tokens {
                     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 if (l instanceof String) ret = r != null && l.equals(JS.toString(r));
                     else ret = l.equals(r);
                     stack.push(JS.B(op == EQ ? ret : !ret)); break;
                 }
@@ -506,7 +528,6 @@ class Interpreter implements ByteCodes, Tokens {
             }
 
         } catch(JSExn e) {
-            if(f.op[pc] != FINALLY_DONE) e.addBacktrace(f.sourceName,f.line[pc]);
             while(stack.size() > 0) {
                 Object o = stack.pop();
                 if (o instanceof CatchMarker || o instanceof TryMarker) {
@@ -531,12 +552,6 @@ class Interpreter implements ByteCodes, Tokens {
                         pc = ((TryMarker)o).finallyLoc - 1;
                         continue OUTER;
                     }
-                } else if(o instanceof CallMarker) {
-                    CallMarker cm = (CallMarker) o;
-                    if(cm.f == null)
-                        e.addBacktrace("<java>",0); // This might not even be worth mentioning
-                    else
-                        e.addBacktrace(cm.f.sourceName,cm.f.line[cm.pc-1]);
                 }
             }
             throw e;
@@ -555,7 +570,22 @@ class Interpreter implements ByteCodes, Tokens {
         public CallMarker(Interpreter cx) { pc = cx.pc + 1; scope = cx.scope; f = cx.f; }
     }
     
-    public static class CatchMarker { public CatchMarker() { } }
+    public static class TrapMarker extends CallMarker {
+        Trap t;
+        JS trapee;
+        Object key;
+        Object val;
+        boolean cascadeHappened;
+        public TrapMarker(Interpreter cx, Trap t, JS trapee, Object key, Object val) {
+            super(cx);
+            this.t = t;
+            this.trapee = trapee;
+            this.key = key;
+            this.val = val;
+        }
+    }
+    
+    public static class CatchMarker { }
     private static CatchMarker catchMarker = new CatchMarker();
     
     public static class LoopMarker {
@@ -650,13 +680,13 @@ class Interpreter implements ByteCodes, Tokens {
             return sb.toString();
         }
         case "indexOf": {
-            String search = alength >= 1 ? arg0.toString() : "null";
+            String search = alength >= 1 ? JS.toString(arg0) : "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";
+            String search = alength >= 1 ? JS.toString(arg0) : "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));            
@@ -687,14 +717,14 @@ class Interpreter implements ByteCodes, Tokens {
     static Object getFromPrimitive(Object o, Object key) throws JSExn {
         boolean returnJS = false;
         if (o instanceof Boolean) {
-            throw new JSExn("cannot call methods on Booleans");
+            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();
+            String s = JS.toString(o);
             
             // this is sort of ugly, but this list should never change
             // These should provide a complete (enough) implementation of the ECMA-262 String object
@@ -709,7 +739,7 @@ class Interpreter implements ByteCodes, Tokens {
             case "lastIndexOf": returnJS = true; break; 
             case "match": returnJS = true; break; 
             case "replace": returnJS = true; break; 
-            case "seatch": returnJS = true; break; 
+            case "search": returnJS = true; break; 
             case "slice": returnJS = true; break; 
             case "split": returnJS = true; break; 
             case "toLowerCase": returnJS = true; break; 
@@ -720,7 +750,7 @@ class Interpreter implements ByteCodes, Tokens {
         }
         if (returnJS) {
             final Object target = o;
-            final String method = key.toString();
+            final String method = JS.toString(o);
             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");