X-Git-Url: http://git.megacz.com/?p=org.ibex.js.git;a=blobdiff_plain;f=src%2Forg%2Fibex%2Fjs%2FInterpreter.java;h=2b4e8887fb0f31ccef244f8cb3eaf549529ec4e9;hp=94ccab5d909c86037ac97fd773595166106d6c4d;hb=HEAD;hpb=667c62a82febcf946918c3a4fc09bb644b288b54 diff --git a/src/org/ibex/js/Interpreter.java b/src/org/ibex/js/Interpreter.java index 94ccab5..2b4e888 100644 --- a/src/org/ibex/js/Interpreter.java +++ b/src/org/ibex/js/Interpreter.java @@ -1,13 +1,14 @@ -// Copyright 2004 Adam Megacz, see the COPYING file for licensing [GPL] +// Copyright 2000-2005 the Contributors, as shown in the revision logs. +// Licensed under the Apache Public Source License 2.0 ("the License"). +// You may not use this file except in compliance with the License. + package org.ibex.js; import org.ibex.util.*; import java.util.*; /** Encapsulates a single JS interpreter (ie call stack) */ -class Interpreter implements ByteCodes, Tokens { - - +class Interpreter implements ByteCodes, Tokens, Pausable { // Thread-Interpreter Mapping ///////////////////////////////////////////////////////////////////////// static Interpreter current() { return (Interpreter)threadToInterpreter.get(Thread.currentThread()); } @@ -19,23 +20,44 @@ 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) { this(f, pauseable, args, true); } - Interpreter(JSFunction f, boolean pauseable, JSArray args, boolean wrap) { - stack.push(new Interpreter.CallMarker(this)); // the "root function returned" marker -- f==null + Interpreter(JSFunction f, boolean pauseable, JS[] args) { this.f = f; this.pausecount = pauseable ? 0 : -1; - this.scope = wrap ? new JSScope(f.parentScope) : f.parentScope; - stack.push(args); + this.scope = f.parentScope; + try { + stack.push(new CallMarker(null)); // the "root function returned" marker -- f==null + stack.push(new JSArgs(args, f)); // FIXME: temprorary bug fix + } catch(JSExn e) { + throw new Error("should never happen"); + } } + Interpreter(JS.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"); + } + } + + private boolean get = false; + // FIXME: split this stuff out into a Script instance control object + // so it's possible to make JS either single or multi threaded. /** this is the only synchronization point we need in order to be threadsafe */ - synchronized Object resume() throws JSExn { + public synchronized Object run(Object o) throws JSExn { + if (f == null) throw new AlreadyRunningException("function already finished"); + if (scope == null) throw new RuntimeException("scope is null"); + Thread t = Thread.currentThread(); Interpreter old = (Interpreter)threadToInterpreter.get(t); threadToInterpreter.put(t, this); + + if (get) stack.push(o); + try { return run(); } finally { @@ -43,6 +65,16 @@ class Interpreter implements ByteCodes, Tokens { else threadToInterpreter.put(t, old); } } + + public void pause() throws NotPausableException { + if (pausecount == -1 || f == null) throw new NotPausableException(); + pausecount++; + switch(f.op[pc]) { + case Tokens.RETURN: case ByteCodes.PUT: get = false; break; + case ByteCodes.GET: case ByteCodes.GET_PRESERVE: case ByteCodes.CALLMETHOD: case ByteCodes.CALL: get = true; break; + default: throw new Error("paused on unexpected bytecode: " + f.op[pc]); + } + } static int getLine() { Interpreter c = Interpreter.current(); @@ -56,15 +88,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) { @@ -75,61 +105,60 @@ 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 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 LITERAL: stack.push((JS)arg); break; + case OBJECT: stack.push(new JS.Obj()); break; + case ARRAY: stack.push(new JSArray(JSU.toInt((JS)arg))); break; + //case DECLARE: scope.declare((JS)(arg==null ? stack.peek() : arg)); if(arg != null) stack.push((JS)arg); break; + case JT: if (JSU.toBoolean((JS)stack.pop())) pc += JSU.toInt((JS)arg) - 1; break; + case JF: if (!JSU.toBoolean((JS)stack.pop())) pc += JSU.toInt((JS)arg) - 1; break; + case JMP: pc += JSU.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 BITNOT: stack.push(JS.N(~JS.toLong(stack.pop()))); break; - case BANG: stack.push(JS.B(!JS.toBoolean(stack.pop()))); break; + case NEWSCOPE: { + int n = JSU.toInt((JS)arg); + scope = new JSScope(scope,(n>>>16)&0xffff,(n>>>0)&0xffff); + break; + } + case OLDSCOPE: scope = scope.parent; break; + case GLOBALSCOPE: stack.push(scope.getGlobal()); break; + case SCOPEGET: stack.push(scope.get((JS)arg)); break; + case SCOPEPUT: { + // FIXME: HACK: share this around more and find the callee. + Object val = stack.peek(); + if (val != null && val instanceof JS[]) val = new JSArgs((JS[])val, null); + scope.put((JS)arg, (JS)val); break; + } + case ASSERT: if (!JSU.toBoolean((JS)stack.pop())) throw je("ibex.assertion.failed"); break; + case BITNOT: stack.push(JSU.N(~JSU.toLong((JS)stack.pop()))); break; + case BANG: stack.push(JSU.B(!JSU.toBoolean((JS)stack.pop()))); break; case NEWFUNCTION: stack.push(((JSFunction)arg)._cloneWithNewParentScope(scope)); break; case LABEL: break; 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(JSU.S("string")); + else if (o instanceof JSNumber.B) stack.push(JSU.S("boolean")); + else if (o instanceof JSNumber) stack.push(JSU.S("number")); + else stack.push(JSU.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 = (JS)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(new LoopMarker(pc, (String)(pc > 0 && f.op[pc - 1] == LABEL ? f.arg[pc - 1] : null), scope)); + stack.push(JSU.T); break; case BREAK: case CONTINUE: - while(stack.size() > 0) { + while(!stack.empty()) { Object o = stack.pop(); if (o instanceof CallMarker) je("break or continue not within a loop"); if (o instanceof TryMarker) { @@ -142,9 +171,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 = JSU.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(JSU.F); } pc = op == BREAK ? endOfLoop - 1 : loopInstructionLocation; continue OUTER; } @@ -162,8 +191,8 @@ class Interpreter implements ByteCodes, Tokens { } case RETURN: { - Object retval = stack.pop(); - while(stack.size() > 0) { + JS retval = (JS)stack.pop(); + while(!stack.empty()) { Object o = stack.pop(); if (o instanceof TryMarker) { if(((TryMarker)o).finallyLoc < 0) continue; @@ -173,291 +202,248 @@ 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 write trap + TrapMarker tm = (TrapMarker) o; + boolean cascade = tm.t.isWriteTrap() && !tm.cascadeHappened && !JSU.toBoolean(retval); + if(cascade) { + JS.Trap t = tm.t.nextWrite(); + if(t == null && tm.t.target() instanceof JS.Clone) { + t = ((JS.Clone)tm.t.target()).clonee.getTrap(tm.t.key()); + if(t != null && !t.isWriteTrap()) t = t.nextWrite(); + } + 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; } } throw new Error("error: RETURN invoked but couldn't find a CallMarker!"); } - - 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"); - - Trap t = null; - if (target instanceof JSScope && key.equals("cascade")) { - Trap.TrapScope ts = null; - 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; - } - } - } - 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); } + + case CASCADE: { + boolean write = JSU.toBoolean((JS)arg); + JS val = write ? (JS)stack.pop() : null; + CallMarker o = stack.findCall(); + if(!(o instanceof TrapMarker)) throw new JSExn("tried to CASCADE while not in a trap"); + TrapMarker tm = (TrapMarker) o; + JS key = tm.t.key(); + JS target = tm.t.target(); + if(tm.t.isWriteTrap() != write) + throw new JSExn("tried to do a "+(write?"write":"read") + " cascade in a " + (write?"read":"write") + " trap"); + JS.Trap t = write ? tm.t.nextWrite() : tm.t.nextRead(); + while (t == null && target instanceof JS.Clone) { + target = ((JS.Clone)target).clonee; + t = target.getTrap(key); + if(t != null) t = write ? t.write() : t.read(); + } + if(write) { + tm.cascadeHappened = true; + stack.push(val); + } + if(t != null) { + setupTrap(t,val,new TrapMarker(this,t,val,tm.pauseOnPut)); + pc--; // we increment later + } else { + if(write) { + if (tm.pauseOnPut) { pc++; return val; } + target.put(key,val); } else { - t = ((JS)target).getTrap(key); + JS ret = target.get(key); + if (ret != null && ret instanceof JS.Method) ret = new Stub(target, key); + stack.push(ret); } - while (t != null && t.f.numFormalArgs == 0) t = t.next; // find the first write trap + if (pausecount > initialPauseCount) { pc++; return null; } // we were paused } - if (t != null) { - stack.push(new CallMarker(this)); - JSArray args = new JSArray(); - args.addElement(val); - stack.push(args); - f = t.f; - scope = new Trap.TrapScope(f.parentScope, t, val); - pc = -1; - break; + break; + } + + case PUT: { + JS val = (JS)stack.pop(); + JS key = (JS)stack.pop(); + JS target = (JS)stack.peek(); + if (target == null) throw je("tried to put " + JSU.str(val) + " to the " + JSU.str(key) + " property on the null value"); + if (key == null) throw je("tried to assign \"" + JSU.str(val) + "\" to the null key"); + + JS.Trap t = target.getTrap(key); + if(t != null) t = t.write(); + + if(t == null && target instanceof JS.Clone) { + target = ((JS.Clone)target).clonee; + t = target.getTrap(key); + if(t != null) t = t.nextWrite(); } - ((JS)target).put(key, val); - if (pausecount > initialPauseCount) { pc++; return null; } // we were paused + stack.push(val); + + if(t != null) { + setupTrap(t,val,new TrapMarker(this,t,val)); + pc--; // we increment later + } else { + 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 ? (JS)stack.pop() : (JS)arg; + target = (JS)stack.pop(); } else { - v = stack.pop(); - o = stack.peek(); - stack.push(v); + key = (JS)stack.pop(); + target = (JS)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)); - 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; - } - ret = ((JS)o).get(v); - if (ret == JS.METHOD) ret = new Stub((JS)o, v); + JS ret = null; + if (key == null) throw je("tried to get the null key from " + JSU.str(target)); + if (target == null) throw je("tried to get property \"" + JSU.str(key) + "\" from the null object"); + + JS.Trap t = null; + try { t = target.getTrap(key); } catch (JSExn e) {} + if(t != null) t = t.read(); + + if(t == null && target instanceof JS.Clone) { + target = ((JS.Clone)target).clonee; + t = target.getTrap(key); + if(t != null) t = t.nextRead(); + } + + if(t != null) { + setupTrap(t,null,new TrapMarker(this,t,null)); + pc--; // we increment later + } else { + ret = target.get(key); if (pausecount > initialPauseCount) { pc++; return null; } // we were paused + + if (ret != null && ret instanceof JS.Method) ret = new Stub(target, key); stack.push(ret); - break; } - throw je("tried to get property " + v + " from a " + o.getClass().getName()); + break; } case CALL: case CALLMETHOD: { - int numArgs = JS.toInt(arg); - Object method = null; - Object ret = null; - Object object = stack.pop(); + JS[] jsargs; + if (arg instanceof JSNumber.I) { + // FIXME: we should be able to recycle JS[]'s here + jsargs = new JS[((JSNumber.I)arg).toInt()]; + for (int i=0; i < jsargs.length; i++) jsargs[i] = (JS)stack.pop(); + } else jsargs = (JS[])arg; + + JS method = null; + JS ret = null; + JS object = (JS)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"); + if (object == null) { + method = (JS)stack.pop(); + object = (JS)stack.pop(); + throw new JSExn("function '"+JSU.str(method)+"' not found in " + object.getClass().getName()); + } else if (object instanceof JS.Method) { + method = (JS)stack.pop(); + object = (JS)stack.pop(); } 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 initialPauseCount) { pc++; return null; } stack.push(ret); break; } case THROW: - throw new JSExn(stack.pop(), stack, f, pc, scope); - - /* FIXME - case MAKE_GRAMMAR: { - final Grammar r = (Grammar)arg; - final JSScope final_scope = scope; - Grammar r2 = new Grammar() { - public int match(String s, int start, Hash v, JSScope scope) throws JSExn { - return r.match(s, start, v, final_scope); - } - public int matchAndWrite(String s, int start, Hash v, JSScope scope, String key) throws JSExn { - return r.matchAndWrite(s, start, v, final_scope, key); - } - public Object call(Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn { - Hash v = new Hash(); - r.matchAndWrite((String)a0, 0, v, final_scope, "foo"); - return v.get("foo"); - } - }; - Object obj = stack.pop(); - if (obj != null && obj instanceof Grammar) r2 = new Grammar.Alternative((Grammar)obj, r2); - stack.push(r2); - break; - } - */ + throw new JSExn((JS)stack.pop(), this); + case ADD_TRAP: case DEL_TRAP: { - Object val = stack.pop(); - Object key = stack.pop(); - Object obj = stack.peek(); + JS val = (JS)stack.pop(); + JS key = (JS)stack.pop(); + JS js = (JS)stack.peek(); // A trap addition/removal - JS js = obj instanceof JSScope ? ((JSScope)obj).top() : (JS) obj; - 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); + if(!(val instanceof JSFunction)) throw new JSExn("tried to add/remove a non-function trap"); + if(op == ADD_TRAP) js.addTrap(key, val); + else js.delTrap(key, val); 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 = (JS)stack.pop(); + JS left = (JS)stack.pop(); + JS ret; + if(left instanceof JSString || right instanceof JSString) + ret = JSU.S(JSU.toString(left).concat(JSU.toString(right))); + else if(left instanceof JSNumber.D || right instanceof JSNumber.D) + ret = JSU.N(JSU.toDouble(left) + JSU.toDouble(right)); + else { + long l = JSU.toLong(left) + JSU.toLong(right); + if(l < Integer.MIN_VALUE || l > Integer.MAX_VALUE) ret = JSU.N(l); + ret = JSU.N((int)l); + } + stack.push(ret); } else { - Object[] args = new Object[count]; - while(--count >= 0) args[count] = stack.pop(); - if(args[0] instanceof String) { + JS[] args = new JS[count]; + while(--count >= 0) args[count] = (JS)stack.pop(); + 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 LSH: stack.push(JSU.N(JSU.toLong(left) << JSU.toLong(right))); break; + case RSH: stack.push(JSU.N(JSU.toLong(left) >> JSU.toLong(right))); break; + case URSH: stack.push(JSU.N(JSU.toLong(left) >>> JSU.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 = left.toString().compareTo(right.toString()); - } else { - result = (int)java.lang.Math.ceil(JS.toDouble(left) - JS.toDouble(right)); + int cmp = 0; + if(left instanceof JSString && right instanceof JSString) + cmp = JSU.toString(left).compareTo(JSU.toString(right)); + else + cmp = (int)(100 * (JSU.toDouble(left) - JSU.toDouble(right))); + switch(op) { + case LE: stack.push(JSU.B(cmp <= 0)); break; + case LT: stack.push(JSU.B(cmp < 0)); break; + case GE: stack.push(JSU.B(cmp >= 0)); break; + case GT: stack.push(JSU.B(cmp > 0)); break; + default: throw new RuntimeException("impossible"); } - stack.push(JS.B((op == LT && result < 0) || (op == LE && result <= 0) || - (op == GT && result > 0) || (op == GE && result >= 0))); break; } case EQ: case NE: { - 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(r.toString()); - else ret = l.equals(r); - stack.push(JS.B(op == EQ ? ret : !ret)); break; + if(left == null && right == null) ret = true; + else if(left == null || right == null) ret = false; + else ret = left.equals(right); + stack.push(JSU.B(op == EQ ? ret : !ret)); break; } default: throw new Error("unknown opcode " + op); @@ -516,66 +498,102 @@ 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()) { + Object o = stack.pop(); + if (o instanceof CatchMarker || o instanceof TryMarker) { + boolean inCatch = o instanceof CatchMarker; + if(inCatch) { + o = (JS)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(JS.Trap t, JS val, CallMarker cm) throws JSExn { + stack.push(cm); + stack.push(new TrapArgs(t, val)); + f = (JSFunction)t.function(); + scope = f.parentScope; + 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 {} + + 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; + } } - public static class CatchMarker { } - private static CatchMarker catchMarker = new CatchMarker(); + static class TrapMarker extends CallMarker { + JS.Trap t; + JS val; + boolean cascadeHappened; + final boolean pauseOnPut; + public TrapMarker(Interpreter cx, JS.Trap t, JS val) { this(cx,t,val,false); } + public TrapMarker(Interpreter cx, JS.Trap t, JS val, boolean pauseOnPut) { + super(cx); + this.t = t; + this.val = val; + this.pauseOnPut = pauseOnPut; + } + } - public static class LoopMarker { - public int location; - public String label; - public JSScope scope; + static class CatchMarker extends Marker { } + private static final CatchMarker catchMarker = new CatchMarker(); + + 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; @@ -583,163 +601,97 @@ 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 JSArgs extends JS.Immutable { + private final JS[] args; + private final JS callee; + + public JSArgs(JS[] args, JS callee) { this.args = args; this.callee = callee; } + + public JS get(JS key) throws JSExn { + if(JSU.isInt(key)) { + int i = JSU.toInt(key); + return i>=args.length ? null : args[i]; } + //#switch(JSU.toString(key)) + case "callee": return callee; + case "length": return JSU.N(args.length); //#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 ? arg0.toString() : "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"; - 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 TrapArgs extends JS.Immutable { + private Trap t; + private JS val; + public TrapArgs(Trap t, JS val) { this.t = t; this.val = val; } + public JS get(JS key) throws JSExn { + if(JSU.isInt(key) && JSU.toInt(key) == 0) return val; + //#switch(JSU.str(key)) + case "trapee": return t.target(); + case "callee": return t.function(); + case "trapname": return t.key(); + case "length": return t.isWriteTrap() ? JSU.ONE : JSU.ZERO; + //#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; + static class Stub extends JS.Immutable { + private JS method; + JS obj; + public Stub(JS obj, JS method) { this.obj = obj; this.method = method; } + public JS call(JS method, JS[] args) throws JSExn { + if (method==null) return obj.call(this.method, args); + return super.call(method, args); } - if (!returnJS) { - // the string stuff applies to everything - String s = o.toString(); - - // 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 final class Stack { + private static final int MAX_STACK_SIZE = 512; + private Object[] stack = new Object[8]; + private int sp = 0; + + boolean empty() { return sp == 0; } + void push(Object o) throws JSExn { if(sp == stack.length) grow(); stack[sp++] = o; } + Object peek() { if(sp == 0) throw new RuntimeException("stack underflow"); return stack[sp-1]; } + final Object pop() { if(sp == 0) throw new RuntimeException("stack underflow"); return stack[--sp]; } + void swap() throws JSExn { + if(sp < 2) throw new JSExn("stack overflow"); + Object tmp = stack[sp-2]; + stack[sp-2] = stack[sp-1]; + stack[sp-1] = tmp; } - if (returnJS) { - final Object target = o; - final String method = key.toString(); - 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); - } - }; + CallMarker findCall() { + for(int i=sp-1;i>=0;i--) if(stack[i] instanceof CallMarker) return (CallMarker) stack[i]; + return null; } - 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); + void grow() throws JSExn { + if(stack.length >= MAX_STACK_SIZE) throw new JSExn("stack overflow"); + Object[] stack2 = new Object[stack.length * 2]; + System.arraycopy(stack,0,stack2,0,stack.length); + stack = stack2; + } + + 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 " + JSU.str(((Interpreter.TrapMarker)cm).t.key()) + ")"; + e.addBacktrace(s); + } + } } } }