X-Git-Url: http://git.megacz.com/?p=org.ibex.core.git;a=blobdiff_plain;f=src%2Forg%2Fibex%2Fjs%2FInterpreter.java;fp=src%2Forg%2Fibex%2Fjs%2FInterpreter.java;h=2d88042fdf50e5aa6094ea4f2164af040876a9db;hp=d16c582eebef74ed92c0d0078a44ca6a0c4aa0c5;hb=ce791e4058158295bce9cf7b6698c2b565d571d7;hpb=fffcafc33aa4066bdf85da7a32e1a1cdb9db2d6f diff --git a/src/org/ibex/js/Interpreter.java b/src/org/ibex/js/Interpreter.java index d16c582..2d88042 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,24 @@ 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 this.f = f; this.pausecount = pauseable ? 0 : -1; this.scope = new JSScope(f.parentScope); - stack.push(args); + try { + stack.push(new CallMarker()); // the "root function returned" marker -- f==null + stack.push(args); + } 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 +60,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 +77,32 @@ class Interpreter implements ByteCodes, Tokens { arg = fd.arg; } switch(op) { - case LITERAL: 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.toNumber(arg).intValue())); break; - case DECLARE: scope.declare((String)(arg==null ? stack.peek() : arg)); if(arg != null) stack.push(arg); 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); + int depth = (arg == null ? 1 : JS.toInt((JS)arg)); + JS 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; } + 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: { + JS o = stack.pop(); + if (JS.checkAssertions && !JS.toBoolean(o)) + 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 +111,32 @@ 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(); + JS o = stack.peek(); + Enumeration e = o.keys(); JSArray a = new JSArray(); - while(e.hasMoreElements()) a.addElement(e.nextElement()); + // FEATURE: Take advantage of the Enumeration, don't create a JSArray + while(e.hasMoreElements()) a.addElement((JS)e.nextElement()); stack.push(a); 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(); + 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 +148,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,7 +168,7 @@ class Interpreter implements ByteCodes, Tokens { } case RETURN: { - Object retval = stack.pop(); + JS retval = stack.pop(); while(stack.size() > 0) { Object o = stack.pop(); if (o instanceof TryMarker) { @@ -198,6 +205,7 @@ class Interpreter implements ByteCodes, Tokens { f = (JSFunction)((CallMarker)o).f; stack.push(retval); if (pausecount > initialPauseCount) { pc++; return null; } // we were paused + if(f == null) return retval; continue OUTER; } } @@ -205,22 +213,15 @@ 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"); - 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; TrapMarker tm = null; - if(target instanceof JSScope && key.equals("cascade")) { + if(target instanceof JSScope && key.jsequals(CASCADE)) { Object o=null; int i; for(i=stack.size()-1;i>=0;i--) if((o = stack.elementAt(i)) instanceof CallMarker) break; @@ -244,7 +245,7 @@ class Interpreter implements ByteCodes, Tokens { stack.push(val); if(t != null) { - stack.push(new TrapMarker(this,t,(JS)target,key,val)); + stack.push(new TrapMarker(this,t,target,key,val)); JSArray args = new JSArray(); args.addElement(val); stack.push(args); @@ -253,7 +254,7 @@ class Interpreter implements ByteCodes, Tokens { pc = -1; break; } else { - ((JS)target).put(key,val); + target.put(key,val); if (pausecount > initialPauseCount) { pc++; return null; } // we were paused break; } @@ -261,29 +262,23 @@ class Interpreter implements ByteCodes, Tokens { case GET: case GET_PRESERVE: { - Object target, key; + JS target, key; if (op == GET) { - key = arg == null ? stack.pop() : arg; + key = arg == null ? stack.pop() : (JS)arg; target = stack.pop(); } else { key = stack.pop(); target = stack.peek(); stack.push(key); } - Object ret = null; + 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 (target instanceof String || target instanceof Number || target instanceof Boolean) { - ret = getFromPrimitive(target,key); - stack.push(ret); - 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; + if(target instanceof JSScope && key.jsequals(CASCADE)) { + JS 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"); @@ -311,8 +306,8 @@ class Interpreter implements ByteCodes, Tokens { pc = -1; break; } else { - ret = ((JS)target).get(key); - if (ret == JS.METHOD) ret = new Stub((JS)target, key); + 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; @@ -320,10 +315,10 @@ class Interpreter implements ByteCodes, Tokens { } 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 method = null; + JS ret = null; + JS object = stack.pop(); if (op == CALLMETHOD) { if (object == JS.METHOD) { @@ -338,35 +333,28 @@ class Interpreter implements ByteCodes, Tokens { stack.pop(); } } - Object[] rest = numArgs > 3 ? new Object[numArgs - 3] : null; + JS[] rest = numArgs > 3 ? new JS[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(); + JS a2 = numArgs <= 2 ? null : stack.pop(); + JS a1 = numArgs <= 1 ? null : stack.pop(); + JS 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) { + 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); 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; @@ -375,7 +363,7 @@ class Interpreter implements ByteCodes, Tokens { case THROW: throw new JSExn(stack.pop(), stack, f, pc, scope); - /* FIXME + /* FIXME GRAMMAR case MAKE_GRAMMAR: { final Grammar r = (Grammar)arg; final JSScope final_scope = scope; @@ -399,11 +387,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) { @@ -412,17 +400,16 @@ 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; } + // FIXME: This was for the old trap syntax, remove it, shouldn't be needed anymore case ASSIGN_SUB: case ASSIGN_ADD: { - Object val = stack.pop(); - Object key = stack.pop(); - Object obj = stack.peek(); + JS val = stack.pop(); + JS key = stack.pop(); + JS 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); @@ -433,25 +420,33 @@ class Interpreter implements ByteCodes, Tokens { } 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; } @@ -529,7 +511,7 @@ class Interpreter implements ByteCodes, Tokens { } catch(JSExn e) { while(stack.size() > 0) { - Object o = stack.pop(); + JS o = stack.pop(); if (o instanceof CatchMarker || o instanceof TryMarker) { boolean inCatch = o instanceof CatchMarker; if(inCatch) { @@ -563,20 +545,28 @@ class Interpreter implements ByteCodes, Tokens { // Markers ////////////////////////////////////////////////////////////////////// - public static class CallMarker { - int pc; - JSScope scope; - JSFunction 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.pc + 1; scope = cx.scope; f = cx.f; } + public CallMarker() { pc = -1; scope = null; f = null; } } - public static class TrapMarker extends CallMarker { + static class TrapMarker extends CallMarker { Trap t; - JS trapee; - Object key; - Object val; + final JS trapee; + final JS key; + final JS val; boolean cascadeHappened; - public TrapMarker(Interpreter cx, Trap t, JS trapee, Object key, Object val) { + public TrapMarker(Interpreter cx, Trap t, JS trapee, JS key, JS val) { super(cx); this.t = t; this.trapee = trapee; @@ -585,24 +575,24 @@ class Interpreter implements ByteCodes, Tokens { } } - public static class CatchMarker { } + 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; @@ -610,19 +600,20 @@ 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 { + // FIXME: Move these into JSString and JSNumber + /*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"); @@ -759,14 +750,41 @@ class Interpreter implements ByteCodes, Tokens { }; } return null; - } + }*/ private static class Stub extends JS { - private Object method; + 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; + public final void push(JS o) throws JSExn { + if(sp == stack.length) grow(); + stack[sp++] = o; + } + public final JS peek() { + if(sp == 0) throw new RuntimeException("Stack underflow"); + return stack[sp-1]; + } + public final JS pop() { + if(sp == 0) throw new RuntimeException("Stack underflow"); + return stack[--sp]; + } + private 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); + } + // FIXME: Eliminate all uses of SWAP n>1 so we don't need this + public int size() { return sp; } + public void setElementAt(JS o, int i) { stack[i] = o; } + public JS elementAt(int i) { return stack[i]; } + } }