X-Git-Url: http://git.megacz.com/?p=org.ibex.core.git;a=blobdiff_plain;f=src%2Forg%2Fibex%2Fjs%2FInterpreter.java;h=83ff5397123ed62fd3c182014ade2706ffbfcb80;hp=5fac7cbcb68a8ceebf68b77544589acdafb70ab5;hb=a19b897271a8ab6b25aba63e4b30223c2477c28d;hpb=b3fc9de644c7049e93b16a7200343841253bfadc diff --git a/src/org/ibex/js/Interpreter.java b/src/org/ibex/js/Interpreter.java index 5fac7cb..83ff539 100644 --- a/src/org/ibex/js/Interpreter.java +++ b/src/org/ibex/js/Interpreter.java @@ -6,8 +6,8 @@ import java.util.*; /** Encapsulates a single JS interpreter (ie call stack) */ class Interpreter implements ByteCodes, Tokens { - private static final int MAX_STACK_SIZE = 512; - + private static final JS CASCADE = JSString.intern("cascade"); + // Thread-Interpreter Mapping ///////////////////////////////////////////////////////////////////////// static Interpreter current() { return (Interpreter)threadToInterpreter.get(Thread.currentThread()); } @@ -19,19 +19,33 @@ 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) { - 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 = new JSScope(f.parentScope); - stack.push(args); + 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"); Thread t = Thread.currentThread(); Interpreter old = (Interpreter)threadToInterpreter.get(t); threadToInterpreter.put(t, this); @@ -55,15 +69,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) { @@ -74,28 +86,20 @@ 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 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 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 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 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; @@ -104,32 +108,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 @@ -141,9 +141,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; } @@ -161,8 +161,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; @@ -172,33 +172,38 @@ 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 read 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; } } @@ -206,174 +211,149 @@ class Interpreter implements ByteCodes, Tokens { } 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"); - + 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 = null; - Trap.TrapScope ts = null; - if (target instanceof JSScope && key.equals("cascade")) { - 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; - } - } - if(ts != null) { - 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; } + 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(ts == null) { - 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(tm == null) { // not cascading + t = target instanceof JSScope ? t = ((JSScope)target).top().getTrap(key) : target.getTrap(key); + if(t != null) t = t.writeTrap(); } - if (t != null) { - stack.push(new CallMarker(this)); - if(stack.size() > MAX_STACK_SIZE) throw new JSExn("stack overflow"); - JSArray args = new JSArray(); - args.addElement(val); - stack.push(args); - f = t.f; - scope = new Trap.TrapScope(f.parentScope, t, val); - pc = -1; - break; + 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); - break; + + if(t != null) { + setupTrap(t,val,new TrapMarker(this,t,val, tm != null && tm.pauseOnPut)); + 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; + } } 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)); - if(stack.size() > MAX_STACK_SIZE) throw new JSExn("stack overflow"); - 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; + 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"); + + 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(); } - ret = ((JS)o).get(v); - if (ret == JS.METHOD) ret = new Stub((JS)o, v); + } + 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); + if(t != null) t = t.readTrap(); + } + + 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; } - throw je("tried to get property " + v + " from a " + o.getClass().getName()); } 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 MAX_STACK_SIZE) throw new JSExn("stack overflow"); - stack.push(arguments); + stack.push(new JSArgs(a0,a1,a2,rest,numArgs,object)); f = (JSFunction)object; scope = new JSScope(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; @@ -397,11 +377,11 @@ 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 = (JS) obj; + 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) { @@ -410,46 +390,39 @@ class Interpreter implements ByteCodes, Tokens { } 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; } - 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> 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 = JS.toString(left).compareTo(JS.toString(right)); - } 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: { - // FIXME: This is not correct, see ECMA-262 11.9.3 - 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(JS.toString(r)); - 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; } @@ -525,66 +486,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(t.isWriteTrap() ? new JSArgs(val,t.f) : new JSArgs(t.f)); + f = t.f; + scope = new TrapScope(t.f.parentScope,t); + 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"); } } - public static class CatchMarker { } + 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; + } + } + + 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; @@ -592,163 +594,109 @@ 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); - } - //#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= 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 ? 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)); + static class TrapScope extends JSScope { + private Trap t; + public TrapScope(JSScope parent, Trap t) { + super(parent); this.t = t; } - 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); + public JS get(JS key) throws JSExn { + //#jswitch(key) + case "trapee": return t.target; + case "callee": return t.f; + case "trapname": return t.key; + //#end + return super.get(key); } - //#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 = 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 - - //#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 = 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"); - 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; + } + } + //#jswitch(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); + } + } + } + } }