From cba642c3dd58d20b4ecebe4ea96232cdd983b17e Mon Sep 17 00:00:00 2001 From: megacz Date: Fri, 30 Jan 2004 07:41:38 +0000 Subject: [PATCH] 2003/11/17 02:59:34 darcs-hash:20040130074138-2ba56-8c56c55e3c6c3c302289b4b33b7539199505c413.gz --- src/org/xwt/js/Interpreter.java | 488 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 488 insertions(+) create mode 100644 src/org/xwt/js/Interpreter.java diff --git a/src/org/xwt/js/Interpreter.java b/src/org/xwt/js/Interpreter.java new file mode 100644 index 0000000..a9ea8a3 --- /dev/null +++ b/src/org/xwt/js/Interpreter.java @@ -0,0 +1,488 @@ +// Copyright 2003 Adam Megacz, see the COPYING file for licensing [GPL] +package org.xwt.js; + +import org.xwt.util.*; +import java.util.*; +import java.io.*; + +class Interpreter implements ByteCodes, Tokens { + + static Object eval(final JSContext cx) throws JS.Exn { + final int initialPauseCount = cx.pausecount; + OUTER: for(;; cx.pc++) { + try { + if (cx.f == null || cx.pc >= cx.f.size) return cx.stack.pop(); + int op = cx.f.op[cx.pc]; + Object arg = cx.f.arg[cx.pc]; + if(op == FINALLY_DONE) { + FinallyData fd = (FinallyData) cx.stack.pop(); + if(fd == null) continue OUTER; // NOP + op = fd.op; + arg = fd.arg; + } + switch(op) { + case LITERAL: cx.stack.push(arg); break; + case OBJECT: cx.stack.push(new JS()); break; + case ARRAY: cx.stack.push(new JSArray(JS.toNumber(arg).intValue())); break; + case DECLARE: cx.scope.declare((String)(arg==null ? cx.stack.peek() : arg)); if(arg != null) cx.stack.push(arg); break; + case TOPSCOPE: cx.stack.push(cx.scope); break; + case JT: if (JS.toBoolean(cx.stack.pop())) cx.pc += JS.toNumber(arg).intValue() - 1; break; + case JF: if (!JS.toBoolean(cx.stack.pop())) cx.pc += JS.toNumber(arg).intValue() - 1; break; + case JMP: cx.pc += JS.toNumber(arg).intValue() - 1; break; + case POP: cx.stack.pop(); break; + case SWAP: { + int depth = (arg == null ? 1 : JS.toInt(arg)); + Object save = cx.stack.elementAt(cx.stack.size() - 1); + for(int i=cx.stack.size() - 1; i > cx.stack.size() - 1 - depth; i--) + cx.stack.setElementAt(cx.stack.elementAt(i-1), i); + cx.stack.setElementAt(save, cx.stack.size() - depth - 1); + break; } + case DUP: cx.stack.push(cx.stack.peek()); break; + case NEWSCOPE: cx.scope = new JSScope(cx.scope); break; + case OLDSCOPE: cx.scope = cx.scope.getParentJSScope(); break; + case ASSERT: if (!JS.toBoolean(cx.stack.pop())) throw je("assertion failed"); break; + case BITNOT: cx.stack.push(new Long(~JS.toLong(cx.stack.pop()))); break; + case BANG: cx.stack.push(new Boolean(!JS.toBoolean(cx.stack.pop()))); break; + case NEWFUNCTION: cx.stack.push(((JSFunction)arg).cloneWithNewParentJSScope(cx.scope)); break; + case LABEL: break; + + case TYPEOF: { + Object o = cx.stack.pop(); + if (o == null) cx.stack.push(null); + else if (o instanceof JS) cx.stack.push(((JS)o).typeName()); + else if (o instanceof String) cx.stack.push("string"); + else if (o instanceof Number) cx.stack.push("number"); + else if (o instanceof Boolean) cx.stack.push("boolean"); + else cx.stack.push("unknown"); + break; + } + + case PUSHKEYS: { + Object o = cx.stack.peek(); + Enumeration e = ((JS)o).keys(); + JSArray a = new JSArray(); + while(e.hasMoreElements()) a.addElement(e.nextElement()); + cx.stack.push(a); + break; + } + + case LOOP: + cx.stack.push(new LoopMarker(cx.pc, cx.pc > 0 && cx.f.op[cx.pc - 1] == LABEL ? + (String)cx.f.arg[cx.pc - 1] : (String)null, cx.scope)); + cx.stack.push(Boolean.TRUE); + break; + + case BREAK: + case CONTINUE: + while(cx.stack.size() > 0) { + Object o = cx.stack.pop(); + if (o instanceof CallMarker) ee("break or continue not within a loop"); + if (o instanceof TryMarker) { + if(((TryMarker)o).finallyLoc < 0) continue; // no finally block, keep going + cx.stack.push(new FinallyData(op, arg)); + cx.scope = ((TryMarker)o).scope; + cx.pc = ((TryMarker)o).finallyLoc - 1; + continue OUTER; + } + if (o instanceof LoopMarker) { + if (arg == null || arg.equals(((LoopMarker)o).label)) { + int loopInstructionLocation = ((LoopMarker)o).location; + int endOfLoop = ((Integer)cx.f.arg[loopInstructionLocation]).intValue() + loopInstructionLocation; + cx.scope = ((LoopMarker)o).scope; + if (op == CONTINUE) { cx.stack.push(o); cx.stack.push(Boolean.FALSE); } + cx.pc = op == BREAK ? endOfLoop - 1 : loopInstructionLocation; + continue OUTER; + } + } + } + throw new Error("CONTINUE/BREAK invoked but couldn't find LoopMarker at " + + cx.getSourceName() + ":" + cx.getLine()); + + case TRY: { + int[] jmps = (int[]) arg; + // jmps[0] is how far away the catch block is, jmps[1] is how far away the finally block is + // each can be < 0 if the specified block does not exist + cx.stack.push(new TryMarker(jmps[0] < 0 ? -1 : cx.pc + jmps[0], jmps[1] < 0 ? -1 : cx.pc + jmps[1], cx.scope)); + break; + } + + case RETURN: { + Object retval = cx.stack.pop(); + while(cx.stack.size() > 0) { + Object o = cx.stack.pop(); + if (o instanceof TryMarker) { + if(((TryMarker)o).finallyLoc < 0) continue; + cx.stack.push(retval); + cx.stack.push(new FinallyData(RETURN)); + cx.scope = ((TryMarker)o).scope; + cx.pc = ((TryMarker)o).finallyLoc - 1; + continue OUTER; + } else if (o instanceof CallMarker) { + if (cx.scope instanceof JSTrap.JSTrapScope) { + JSTrap.JSTrapScope ts = (JSTrap.JSTrapScope)cx.scope; + if (!ts.cascadeHappened) { + ts.cascadeHappened = true; + JSTrap t = ts.t.next; + while (t != null && t.f.numFormalArgs == 0) t = t.next; + if (t == null) { + ((JS)ts.t.trapee).put(t.name, ts.val); + if (cx.pausecount > initialPauseCount) return null; // we were paused + } else { + cx.stack.push(o); + JSArray args = new JSArray(); + args.addElement(ts.val); + cx.stack.push(args); + cx.f = t.f; + cx.scope = new JSTrap.JSTrapScope(cx.f.parentJSScope, t, ts.val); + cx.pc = -1; + break; + } + } + } + cx.scope = ((CallMarker)o).scope; + cx.pc = ((CallMarker)o).pc; + cx.f = (JSFunction)((CallMarker)o).f; + cx.stack.push(retval); + continue OUTER; + } + } + throw new Error("error: RETURN invoked but couldn't find a CallMarker!"); + } + + case PUT: { + Object val = cx.stack.pop(); + Object key = cx.stack.pop(); + Object target = cx.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"); + JSTrap t = null; + if (target instanceof JSTrap.JSTrappable) { + t = ((JSTrap.JSTrappable)target).getTrap(val); + while (t != null && t.f.numFormalArgs == 0) t = t.next; + } else if (target instanceof JSTrap.JSTrapScope && key.equals("cascade")) { + JSTrap.JSTrapScope ts = (JSTrap.JSTrapScope)target; + t = ts.t.next; + ts.cascadeHappened = true; + while (t != null && t.f.numFormalArgs == 0) t = t.next; + if (t == null) target = ts.t.trapee; + } + if (t != null) { + cx.stack.push(new CallMarker(cx)); + JSArray args = new JSArray(); + args.addElement(val); + cx.stack.push(args); + cx.f = t.f; + cx.scope = new JSTrap.JSTrapScope(cx.f.parentJSScope, t, val); + cx.pc = -1; + break; + } + ((JS)target).put(key, val); + if (cx.pausecount > initialPauseCount) return null; // we were paused + cx.stack.push(val); + break; + } + + case GET: + case GET_PRESERVE: { + Object o, v; + if (op == GET) { + v = arg == null ? cx.stack.pop() : arg; + o = cx.stack.pop(); + } else { + v = cx.stack.pop(); + o = cx.stack.peek(); + cx.stack.push(v); + } + 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 value @" + cx.pc + "\n" + cx.f.dump()); + if (o instanceof String || o instanceof Number || o instanceof Boolean) { + ret = Internal.getFromPrimitive(o,v); + cx.stack.push(ret); + break; + } else if (o instanceof JS) { + JSTrap t = null; + if (o instanceof JSTrap.JSTrappable) { + t = ((JSTrap.JSTrappable)o).getTrap(v); + while (t != null && t.f.numFormalArgs != 0) t = t.next; + } else if (o instanceof JSTrap.JSTrapScope && v.equals("cascade")) { + t = ((JSTrap.JSTrapScope)o).t.next; + while (t != null && t.f.numFormalArgs != 0) t = t.next; + if (t == null) o = ((JSTrap.JSTrapScope)o).t.trapee; + } + if (t != null) { + cx.stack.push(new CallMarker(cx)); + JSArray args = new JSArray(); + cx.stack.push(args); + cx.f = t.f; + cx.scope = new JSTrap.JSTrapScope(cx.f.parentJSScope, t, null); + ((JSTrap.JSTrapScope)cx.scope).cascadeHappened = true; + cx.pc = -1; + break; + } + ret = ((JS)o).get(v); + if (ret == JSCallable.METHOD) ret = new Internal.JSCallableStub((JS)o, v); + if (cx.pausecount > initialPauseCount) return null; // we were paused + cx.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 = cx.stack.pop(); + + if (op == CALL) { + object = cx.stack.pop(); + } else if (object == JSCallable.METHOD) { + method = cx.stack.pop(); + object = cx.stack.pop(); + } + + if (object instanceof String || object instanceof Number || object instanceof Boolean) { + JSArray arguments = new JSArray(); + for(int j=numArgs - 1; j >= 0; j--) arguments.setElementAt(cx.stack.pop(), j); + ret = Internal.callMethodOnPrimitive(object, method, arguments); + + } else if (object instanceof JSFunction) { + // FEATURE: use something similar to call0/call1/call2 here + JSArray arguments = new JSArray(); + for(int j=numArgs - 1; j >= 0; j--) arguments.setElementAt(cx.stack.pop(), j); + cx.stack.push(new CallMarker(cx)); + cx.stack.push(arguments); + cx.f = (JSFunction)object; + cx.scope = new JSScope(cx.f.parentJSScope); + cx.pc = -1; + break; + + } else if (object instanceof JSCallable) { + JSCallable c = (JSCallable)object; + Object[] rest = numArgs > 3 ? new Object[numArgs - 3] : null; + for(int i=numArgs - 1; i>2; i--) rest[i-3] = cx.stack.pop(); + Object a2 = numArgs <= 2 ? null : cx.stack.pop(); + Object a1 = numArgs <= 1 ? null : cx.stack.pop(); + Object a0 = numArgs <= 0 ? null : cx.stack.pop(); + ret = c.callMethod(method, a0, a1, a2, rest, numArgs); + + } else { + throw new JS.Exn("can't call a " + object.getClass().getName() + " @" + cx.pc + "\n" + cx.f.dump()); + + } + if (cx.pausecount > initialPauseCount) return null; + cx.stack.push(ret); + break; + } + + case THROW: { + Object o = cx.stack.pop(); + if(o instanceof JS.Exn) throw (JS.Exn)o; + throw new JS.Exn(o); + } + + case ASSIGN_SUB: case ASSIGN_ADD: { + Object val = cx.stack.pop(); + Object old = cx.stack.pop(); + Object key = cx.stack.pop(); + Object obj = cx.stack.peek(); + if (val instanceof JSFunction && obj instanceof JSScope) { + JSScope parent = (JSScope)obj; + while(parent.getParentJSScope() != null) parent = parent.getParentJSScope(); + if (parent instanceof JSTrap.JSTrappable) { + JSTrap.JSTrappable b = (JSTrap.JSTrappable)parent; + if (op == ASSIGN_ADD) JSTrap.addTrap(b, key, (JSFunction)val); + else JSTrap.delTrap(b, key, (JSFunction)val); + // skip over the "normal" implementation of +=/-= + cx.pc += ((Integer)arg).intValue() - 1; + break; + } + } + // use the "normal" implementation + cx.stack.push(key); + cx.stack.push(old); + cx.stack.push(val); + break; + } + + case ADD: { + int count = ((Number)arg).intValue(); + if(count < 2) throw new Error("this should never happen"); + if(count == 2) { + // common case + Object right = cx.stack.pop(); + Object left = cx.stack.pop(); + if(left instanceof String || right instanceof String) + cx.stack.push(JS.toString(left).concat(JS.toString(right))); + else cx.stack.push(new Double(JS.toDouble(left) + JS.toDouble(right))); + } else { + Object[] args = new Object[count]; + while(--count >= 0) args[count] = cx.stack.pop(); + if(args[0] instanceof String) { + StringBuffer sb = new StringBuffer(64); + for(int i=0;i> JS.toLong(right))); break; + case URSH: cx.stack.push(new Long(JS.toLong(left) >>> JS.toLong(right))); break; + + case LT: case LE: case GT: case GE: { + if (left == null) left = new Integer(0); + if (right == null) right = new Integer(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)); + } + cx.stack.push(new Boolean((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 = new Boolean(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); + cx.stack.push(new Boolean(op == EQ ? ret : !ret)); break; + } + + default: throw new Error("unknown opcode " + op); + } } + } + + } catch(JS.Exn e) { + while(cx.stack.size() > 0) { + Object o = cx.stack.pop(); + if (o instanceof CatchMarker || o instanceof TryMarker) { + boolean inCatch = o instanceof CatchMarker; + if(inCatch) { + o = cx.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 + cx.stack.push(o); + cx.stack.push(catchMarker); + cx.stack.push(e.getObject()); + cx.scope = ((TryMarker)o).scope; + cx.pc = ((TryMarker)o).catchLoc - 1; + continue OUTER; + } else { + cx.stack.push(e); + cx.stack.push(new FinallyData(THROW)); + cx.scope = ((TryMarker)o).scope; + cx.pc = ((TryMarker)o).finallyLoc - 1; + continue OUTER; + } + } + // no handler found within this func + if(o instanceof CallMarker) throw e; + } + throw e; + } // end try/catch + } // end for + } + + // Exception Stuff //////////////////////////////////////////////////////////////// + + static class EvaluatorException extends RuntimeException { public EvaluatorException(String s) { super(s); } } + static EvaluatorException ee(String s) { + throw new EvaluatorException(JSContext.getSourceName() + ":" + JSContext.getLine() + " " + s); + } + static JS.Exn je(String s) { + throw new JS.Exn(JSContext.getSourceName() + ":" + JSContext.getLine() + " " + s); + } + + + // Markers ////////////////////////////////////////////////////////////////////// + + public static class CallMarker { + int pc; + JSScope scope; + JSFunction f; + public CallMarker(JSContext cx) { pc = cx.pc + 1; scope = cx.scope; f = cx.f; } + } + + public static class CatchMarker { public CatchMarker() { } } + private static CatchMarker catchMarker = new CatchMarker(); + + public static class LoopMarker { + public int location; + public String label; + 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 TryMarker(int catchLoc, int finallyLoc, JSScope scope) { + this.catchLoc = catchLoc; + this.finallyLoc = finallyLoc; + this.scope = scope; + } + } + public static class FinallyData { + public int op; + public Object arg; + public FinallyData(int op, Object arg) { this.op = op; this.arg = arg; } + public FinallyData(int op) { this(op,null); } + } +} -- 1.7.10.4