/** Encapsulates a single JS interpreter (ie call stack) */
class Interpreter implements ByteCodes, Tokens {
-
+ private static final int MAX_STACK_SIZE = 512;
// Thread-Interpreter Mapping /////////////////////////////////////////////////////////////////////////
}
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;
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;
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);
}
}
}
pc = ((CallMarker)o).pc - 1;
f = (JSFunction)((CallMarker)o).f;
stack.push(retval);
+ if (pausecount > initialPauseCount) { pc++; return null; } // we were paused
continue OUTER;
}
}
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: {
JSArray arguments = new JSArray();
for(int i=0; i<numArgs; i++) arguments.addElement(i==0?a0:i==1?a1:i==2?a2:rest[i-3]);
stack.push(new CallMarker(this));
+ if(stack.size() > MAX_STACK_SIZE) throw new JSExn("stack overflow");
stack.push(arguments);
f = (JSFunction)object;
scope = new JSScope(f.parentScope);
}
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;
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;
}
default: {
+ if(op == BITOR) throw new Error("pc: " + pc + " of " + f);
Object right = stack.pop();
Object left = stack.pop();
switch(op) {
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));
}
case EQ:
case NE: {
+ // FIXME: This is not correct, see ECMA-262 11.9.3
Object l = left;
Object r = right;
boolean ret;
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;
}
}
} 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) {
pc = ((TryMarker)o).finallyLoc - 1;
continue OUTER;
}
- } else if(o instanceof CallMarker) {
- CallMarker cm = (CallMarker) o;
- if(cm.f == null)
- e.addBacktrace("<java>",0); // This might not even be worth mentioning
- else
- e.addBacktrace(cm.f.sourceName,cm.f.line[cm.pc-1]);
}
}
throw e;
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 {
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));
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
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;
}
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");