X-Git-Url: http://git.megacz.com/?a=blobdiff_plain;f=src%2Forg%2Fibex%2Fjs%2FInterpreter.java;h=73de955e3ecb05427fc8da62ded68a918d3b4f01;hb=4c96b0f7e40e4689434c46e4300cc9bef9d0fab9;hp=5ad701407037059165cc3044fe7ca3a9244486f6;hpb=64b8c4b435a4457e342fd03fc4a725d5ea16da36;p=org.ibex.core.git diff --git a/src/org/ibex/js/Interpreter.java b/src/org/ibex/js/Interpreter.java index 5ad7014..73de955 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 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); + 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; @@ -231,7 +232,7 @@ class Interpreter implements ByteCodes, Tokens { key = tm.key; tm.cascadeHappened = true; t = tm.t; - if(t.readTrap()) throw new JSExn("can't put to cascade in a read trap"); + if(t.readTrap()) throw new JSExn("can't do a write cascade in a read trap"); t = t.next; while(t != null && t.readTrap()) t = t.next; } @@ -244,16 +245,16 @@ 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); f = t.f; - scope = new JSScope(f.parentScope); + scope = new TrapScope(f.parentScope,target,f,key); 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"); @@ -292,7 +287,7 @@ class Interpreter implements ByteCodes, Tokens { 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"); + if(t.writeTrap()) throw new JSExn("can't do a read cascade in a write trap"); t = t.next; while(t != null && t.writeTrap()) t = t.next; if(t != null) tm.cascadeHappened = true; @@ -307,66 +302,59 @@ class Interpreter implements ByteCodes, Tokens { stack.push(new TrapMarker(this,t,(JS)target,key,null)); stack.push(new JSArray()); f = t.f; - scope = new JSScope(f.parentScope); + scope = new TrapScope(f.parentScope,target,f,key); pc = -1; break; } else { - ret = ((JS)target).get(key); - if (ret == JS.METHOD) ret = new Stub((JS)target, key); - stack.push(ret); + 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; } } 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) { 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; + 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,46 +400,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; } @@ -528,55 +496,72 @@ 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.size() > 0) { + 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; + } // 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 +570,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,163 +595,67 @@ class Interpreter implements ByteCodes, Tokens { this.f = cx.f; } } - public static class FinallyData { - public int op; - public Object arg; - public JSExn exn; + static class FinallyData extends Marker { + final public int op; + final public Object arg; + final public JSExn exn; public FinallyData(int op) { this(op,null); } - public FinallyData(int op, Object arg) { this.op = op; this.arg = arg; } - public FinallyData(JSExn exn) { this.exn = exn; } // Just throw this exn + public FinallyData(int op, Object arg) { this.op = op; this.arg = arg; this.exn = null; } + public FinallyData(JSExn exn) { this.exn = exn; this.op = -1; this.arg = null; } // Just throw this exn } - - // Operations on Primitives ////////////////////////////////////////////////////////////////////// - - static Object callMethodOnPrimitive(Object o, Object method, Object arg0, Object arg1, Object arg2, Object[] rest, int alength) throws JSExn { - if (method == null || !(method instanceof String) || "".equals(method)) - throw new JSExn("attempt to call a non-existant method on a primitive"); - - if (o instanceof Number) { - //#switch(method) - case "toFixed": throw new JSExn("toFixed() not implemented"); - case "toExponential": throw new JSExn("toExponential() not implemented"); - case "toPrecision": throw new JSExn("toPrecision() not implemented"); - case "toString": { - int radix = alength >= 1 ? JS.toInt(arg0) : 10; - return Long.toString(((Number)o).longValue(),radix); + static class TrapScope extends JSScope { + JS trapee; + JS callee; + JS trapname; + public TrapScope(JSScope parent, JS trapee, JS callee, JS trapname) { + super(parent); this.trapee = trapee; this.callee = callee; this.trapname = trapname; + } + public JS get(JS key) throws JSExn { + if(JS.isString(key)) { + //#switch(JS.toString(key)) + case "trapee": return trapee; + case "callee": return callee; + case "trapname": return trapname; + //#end } - //#end - } else if (o instanceof Boolean) { - // No methods for Booleans - throw new JSExn("attempt to call a method on a Boolean"); + return super.get(key); } + } - 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)); - } - 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); + static class Stub extends JS { + private JS method; + JS obj; + 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); } - //#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; + 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; } - 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 + public final JS peek() { + if(sp == 0) throw new RuntimeException("Stack underflow"); + return stack[sp-1]; } - 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 final JS pop() { + if(sp == 0) throw new RuntimeException("Stack underflow"); + return stack[--sp]; } - return null; - } - - private static class Stub extends JS { - private Object 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 { - return ((JS)obj).callMethod(method, a0, a1, a2, rest, nargs); + 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]; } } }