X-Git-Url: http://git.megacz.com/?p=org.ibex.core.git;a=blobdiff_plain;f=src%2Forg%2Fibex%2Fjs%2FInterpreter.java;h=d16c582eebef74ed92c0d0078a44ca6a0c4aa0c5;hp=1b679cafa95d839d7ca07b48fd60ff99349cbc63;hb=fffcafc33aa4066bdf85da7a32e1a1cdb9db2d6f;hpb=3591b88b94a6bb378af3d4abe6eb5233ce583104 diff --git a/src/org/ibex/js/Interpreter.java b/src/org/ibex/js/Interpreter.java index 1b679ca..d16c582 100644 --- a/src/org/ibex/js/Interpreter.java +++ b/src/org/ibex/js/Interpreter.java @@ -6,7 +6,7 @@ import java.util.*; /** Encapsulates a single JS interpreter (ie call stack) */ class Interpreter implements ByteCodes, Tokens { - + private static final int MAX_STACK_SIZE = 512; // Thread-Interpreter Mapping ///////////////////////////////////////////////////////////////////////// @@ -75,7 +75,7 @@ class Interpreter implements ByteCodes, Tokens { } switch(op) { case LITERAL: stack.push(arg); break; - case OBJECT: stack.push(new JS()); 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 TOPSCOPE: stack.push(scope); break; @@ -93,7 +93,9 @@ class Interpreter implements ByteCodes, Tokens { case DUP: stack.push(stack.peek()); break; case NEWSCOPE: scope = new JSScope(scope); break; case OLDSCOPE: scope = scope.getParentScope(); break; - case ASSERT: if (!JS.toBoolean(stack.pop())) throw je("ibex.assertion.failed" /*FEATURE: line number*/); 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 NEWFUNCTION: stack.push(((JSFunction)arg)._cloneWithNewParentScope(scope)); break; @@ -170,26 +172,24 @@ 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); + if (o instanceof TrapMarker) { // handles return component of a read trap + TrapMarker tm = (TrapMarker) o; + boolean cascade = tm.t.writeTrap() && !tm.cascadeHappened && !JS.toBoolean(retval); + if(cascade) { + Trap t = tm.t.next; + while(t != null && t.readTrap()) t = t.next; + if(t != null) { + tm.t = t; + stack.push(tm); JSArray args = new JSArray(); - args.addElement(ts.val); + args.addElement(tm.val); stack.push(args); f = t.f; - scope = new Trap.TrapScope(f.parentScope, t, ts.val); + scope = new JSScope(f.parentScope); pc = -1; continue OUTER; + } else { + tm.trapee.put(tm.key,tm.val); } } } @@ -197,6 +197,7 @@ class Interpreter implements ByteCodes, Tokens { pc = ((CallMarker)o).pc - 1; f = (JSFunction)((CallMarker)o).f; stack.push(retval); + if (pausecount > initialPauseCount) { pc++; return null; } // we were paused continue OUTER; } } @@ -213,102 +214,109 @@ class Interpreter implements ByteCodes, Tokens { 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"); + + 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; - if (target instanceof Trap.TrapScope && key.equals("cascade")) { - Trap.TrapScope ts = (Trap.TrapScope)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; 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); } - } else { - t = ((JS)target).getTrap(key); - } - - while (t != null && t.f.numFormalArgs == 0) t = t.next; // find the first write trap - if (t != null) { - stack.push(new CallMarker(this)); - JSArray args = new JSArray(); - args.addElement(val); - stack.push(args); + TrapMarker tm = null; + if(target instanceof JSScope && key.equals("cascade")) { + Object 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"); + if(o instanceof TrapMarker) { + tm = (TrapMarker) o; + target = tm.trapee; + key = tm.key; + tm.cascadeHappened = true; + t = tm.t; + if(t.readTrap()) throw new JSExn("can't put to cascade in a read trap"); + t = t.next; + while(t != null && t.readTrap()) t = t.next; } } - if (t != null) { + if(tm == null) { // didn't find a trap marker, try to find a trap + t = target instanceof JSScope ? t = ((JSScope)target).top().getTrap(key) : ((JS)target).getTrap(key); + while(t != null && t.readTrap()) t = t.next; + } + + stack.push(val); + + if(t != null) { + stack.push(new TrapMarker(this,t,(JS)target,key,val)); + JSArray args = new JSArray(); + args.addElement(val); + stack.push(args); f = t.f; - scope = new Trap.TrapScope(f.parentScope, t, val); + scope = new JSScope(f.parentScope); pc = -1; break; + } else { + ((JS)target).put(key,val); + if (pausecount > initialPauseCount) { pc++; return null; } // we were paused + break; } - ((JS)target).put(key, val); - if (pausecount > initialPauseCount) { pc++; return null; } // we were paused - stack.push(val); - break; } case GET: case GET_PRESERVE: { - Object o, v; + Object target, key; if (op == GET) { - v = arg == null ? stack.pop() : arg; - o = stack.pop(); + key = arg == null ? stack.pop() : arg; + target = stack.pop(); } else { - v = stack.pop(); - o = stack.peek(); - stack.push(v); + key = stack.pop(); + target = stack.peek(); + stack.push(key); } Object ret = null; - if (v == null) throw je("tried to get the null key from " + o); - if (o == null) throw je("tried to get property \"" + v + "\" from the null object"); - if (o instanceof String || o instanceof Number || o instanceof Boolean) { - ret = getFromPrimitive(o,v); + 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; - } 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); - } - } - if (t != null) { - f = t.f; - scope = new Trap.TrapScope(f.parentScope, t, null); - ((Trap.TrapScope)scope).cascadeHappened = true; - pc = -1; - 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; + 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"); + if(o instanceof TrapMarker) { + tm = (TrapMarker) o; + 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"); + t = t.next; + while(t != null && t.writeTrap()) t = t.next; + if(t != null) tm.cascadeHappened = true; } - ret = ((JS)o).get(v); - if (ret == JS.METHOD) ret = new Stub((JS)o, v); - if (pausecount > initialPauseCount) { pc++; return null; } // we were paused + } + if(tm == null) { // didn't find a trap marker, try to find a trap + t = target instanceof JSScope ? t = ((JSScope)target).top().getTrap(key) : ((JS)target).getTrap(key); + while(t != null && t.writeTrap()) t = t.next; + } + + if(t != null) { + stack.push(new TrapMarker(this,t,(JS)target,key,null)); + stack.push(new JSArray()); + f = t.f; + scope = new JSScope(f.parentScope); + pc = -1; + break; + } else { + ret = ((JS)target).get(key); + if (ret == JS.METHOD) ret = new Stub((JS)target, key); stack.push(ret); + if (pausecount > initialPauseCount) { pc++; return null; } // we were paused break; } - throw je("tried to get property " + v + " from a " + o.getClass().getName()); } case CALL: case CALLMETHOD: { @@ -344,6 +352,7 @@ class Interpreter implements ByteCodes, Tokens { 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); @@ -364,8 +373,9 @@ class Interpreter implements ByteCodes, Tokens { } case THROW: - throw new JSExn(stack.pop()); + throw new JSExn(stack.pop(), stack, f, pc, scope); + /* FIXME case MAKE_GRAMMAR: { final Grammar r = (Grammar)arg; final JSScope final_scope = scope; @@ -387,13 +397,23 @@ class Interpreter implements ByteCodes, Tokens { stack.push(r2); break; } - + */ case ADD_TRAP: case DEL_TRAP: { Object val = stack.pop(); Object key = stack.pop(); Object obj = stack.peek(); // A trap addition/removal - JS js = obj instanceof JSScope ? ((JSScope)obj).top() : (JS) obj; + JS js = (JS) obj; + if(js instanceof JSScope) { + JSScope s = (JSScope) js; + while(s.getParentScope() != null) { + if(s.has(key)) throw new JSExn("cannot trap a variable that isn't at the top level scope"); + s = s.getParentScope(); + } + 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; @@ -455,6 +475,7 @@ class Interpreter implements ByteCodes, Tokens { } default: { + if(op == BITOR) throw new Error("pc: " + pc + " of " + f); Object right = stack.pop(); Object left = stack.pop(); switch(op) { @@ -477,7 +498,7 @@ class Interpreter implements ByteCodes, Tokens { if (right == null) right = JS.N(0); int result = 0; if (left instanceof String || right instanceof String) { - result = left.toString().compareTo(right.toString()); + result = JS.toString(left).compareTo(JS.toString(right)); } else { result = (int)java.lang.Math.ceil(JS.toDouble(left) - JS.toDouble(right)); } @@ -488,6 +509,7 @@ class Interpreter implements ByteCodes, Tokens { case EQ: case NE: { + // FIXME: This is not correct, see ECMA-262 11.9.3 Object l = left; Object r = right; boolean ret; @@ -496,7 +518,7 @@ class Interpreter implements ByteCodes, Tokens { 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 if (l instanceof String) ret = r != null && l.equals(JS.toString(r)); else ret = l.equals(r); stack.push(JS.B(op == EQ ? ret : !ret)); break; } @@ -506,7 +528,6 @@ class Interpreter implements ByteCodes, Tokens { } } catch(JSExn e) { - if(f.op[pc] != FINALLY_DONE) e.addBacktrace(f.sourceName,f.line[pc]); while(stack.size() > 0) { Object o = stack.pop(); if (o instanceof CatchMarker || o instanceof TryMarker) { @@ -531,12 +552,6 @@ class Interpreter implements ByteCodes, Tokens { pc = ((TryMarker)o).finallyLoc - 1; continue OUTER; } - } else if(o instanceof CallMarker) { - CallMarker cm = (CallMarker) o; - if(cm.f == null) - e.addBacktrace("",0); // This might not even be worth mentioning - else - e.addBacktrace(cm.f.sourceName,cm.f.line[cm.pc-1]); } } throw e; @@ -555,7 +570,22 @@ class Interpreter implements ByteCodes, Tokens { public CallMarker(Interpreter cx) { pc = cx.pc + 1; scope = cx.scope; f = cx.f; } } - public static class CatchMarker { public CatchMarker() { } } + public static class TrapMarker extends CallMarker { + Trap t; + JS trapee; + Object key; + Object val; + boolean cascadeHappened; + public TrapMarker(Interpreter cx, Trap t, JS trapee, Object key, Object val) { + super(cx); + this.t = t; + this.trapee = trapee; + this.key = key; + this.val = val; + } + } + + public static class CatchMarker { } private static CatchMarker catchMarker = new CatchMarker(); public static class LoopMarker { @@ -650,13 +680,13 @@ class Interpreter implements ByteCodes, Tokens { return sb.toString(); } case "indexOf": { - String search = alength >= 1 ? arg0.toString() : "null"; + 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.indexOf(search,start)); } case "lastIndexOf": { - String search = alength >= 1 ? arg0.toString() : "null"; + 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)); @@ -687,14 +717,14 @@ class Interpreter implements ByteCodes, Tokens { static Object getFromPrimitive(Object o, Object key) throws JSExn { boolean returnJS = false; if (o instanceof Boolean) { - throw new JSExn("cannot call methods on Booleans"); + throw new JSExn("Booleans do not have properties"); } else if (o instanceof Number) { if (key.equals("toPrecision") || key.equals("toExponential") || key.equals("toFixed")) returnJS = true; } if (!returnJS) { // the string stuff applies to everything - String s = o.toString(); + 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 @@ -709,7 +739,7 @@ class Interpreter implements ByteCodes, Tokens { case "lastIndexOf": returnJS = true; break; case "match": returnJS = true; break; case "replace": returnJS = true; break; - case "seatch": returnJS = true; break; + case "search": returnJS = true; break; case "slice": returnJS = true; break; case "split": returnJS = true; break; case "toLowerCase": returnJS = true; break; @@ -720,7 +750,7 @@ class Interpreter implements ByteCodes, Tokens { } if (returnJS) { final Object target = o; - final String method = key.toString(); + 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");