1 // Copyright 2004 Adam Megacz, see the COPYING file for licensing [GPL]
4 import org.ibex.util.*;
7 /** Encapsulates a single JS interpreter (ie call stack) */
8 class Interpreter implements ByteCodes, Tokens {
9 private static final JS CASCADE = JSString.intern("cascade");
11 // Thread-Interpreter Mapping /////////////////////////////////////////////////////////////////////////
13 static Interpreter current() { return (Interpreter)threadToInterpreter.get(Thread.currentThread()); }
14 private static Hashtable threadToInterpreter = new Hashtable();
17 // Instance members and methods //////////////////////////////////////////////////////////////////////
19 int pausecount; ///< the number of times pause() has been invoked; -1 indicates unpauseable
20 JSFunction f = null; ///< the currently-executing JSFunction
21 JSScope scope; ///< the current top-level scope (LIFO stack via NEWSCOPE/OLDSCOPE)
22 final Stack stack = new Stack(); ///< the object stack
23 int pc = 0; ///< the program counter
25 Interpreter(JSFunction f, boolean pauseable, JSArgs args) {
27 this.pausecount = pauseable ? 0 : -1;
28 this.scope = new JSScope(f.parentScope);
30 stack.push(new CallMarker()); // the "root function returned" marker -- f==null
33 throw new Error("should never happen");
37 /** this is the only synchronization point we need in order to be threadsafe */
38 synchronized JS resume() throws JSExn {
39 if(f == null) throw new RuntimeException("function already finished");
40 Thread t = Thread.currentThread();
41 Interpreter old = (Interpreter)threadToInterpreter.get(t);
42 threadToInterpreter.put(t, this);
46 if (old == null) threadToInterpreter.remove(t);
47 else threadToInterpreter.put(t, old);
51 static int getLine() {
52 Interpreter c = Interpreter.current();
53 return c == null || c.f == null || c.pc < 0 || c.pc >= c.f.size ? -1 : c.f.line[c.pc];
56 static String getSourceName() {
57 Interpreter c = Interpreter.current();
58 return c == null || c.f == null ? null : c.f.sourceName;
61 private static JSExn je(String s) { return new JSExn(getSourceName() + ":" + getLine() + " " + s); }
63 private JS run() throws JSExn {
65 // if pausecount changes after a get/put/call, we know we've been paused
66 final int initialPauseCount = pausecount;
71 Object arg = f.arg[pc];
72 if(op == FINALLY_DONE) {
73 FinallyData fd = (FinallyData) stack.pop();
74 if(fd == null) continue OUTER; // NOP
75 if(fd.exn != null) throw fd.exn;
80 case LITERAL: stack.push((JS)arg); break;
81 case OBJECT: stack.push(new JS.O()); break;
82 case ARRAY: stack.push(new JSArray(JS.toInt((JS)arg))); break;
83 case DECLARE: scope.declare((JS)(arg==null ? stack.peek() : arg)); if(arg != null) stack.push((JS)arg); break;
84 case TOPSCOPE: stack.push(scope); break;
85 case JT: if (JS.toBoolean(stack.pop())) pc += JS.toInt((JS)arg) - 1; break;
86 case JF: if (!JS.toBoolean(stack.pop())) pc += JS.toInt((JS)arg) - 1; break;
87 case JMP: pc += JS.toInt((JS)arg) - 1; break;
88 case POP: stack.pop(); break;
90 int depth = (arg == null ? 1 : JS.toInt((JS)arg));
91 JS save = stack.elementAt(stack.size() - 1);
92 for(int i=stack.size() - 1; i > stack.size() - 1 - depth; i--)
93 stack.setElementAt(stack.elementAt(i-1), i);
94 stack.setElementAt(save, stack.size() - depth - 1);
97 case DUP: stack.push(stack.peek()); break;
98 case NEWSCOPE: scope = new JSScope(scope); break;
99 case OLDSCOPE: scope = scope.getParentScope(); break;
102 if (JS.checkAssertions && !JS.toBoolean(o))
103 throw je("ibex.assertion.failed");
106 case BITNOT: stack.push(JS.N(~JS.toLong(stack.pop()))); break;
107 case BANG: stack.push(JS.B(!JS.toBoolean(stack.pop()))); break;
108 case NEWFUNCTION: stack.push(((JSFunction)arg)._cloneWithNewParentScope(scope)); break;
112 Object o = stack.pop();
113 if (o == null) stack.push(null);
114 else if (o instanceof JSString) stack.push(JS.S("string"));
115 else if (o instanceof JSNumber.B) stack.push(JS.S("boolean"));
116 else if (o instanceof JSNumber) stack.push(JS.S("number"));
117 else stack.push(JS.S("object"));
123 Enumeration e = o.keys();
124 JSArray a = new JSArray();
125 // FEATURE: Take advantage of the Enumeration, don't create a JSArray
126 while(e.hasMoreElements()) a.addElement((JS)e.nextElement());
132 stack.push(new LoopMarker(pc, pc > 0 && f.op[pc - 1] == LABEL ? (String)f.arg[pc - 1] : (String)null, scope));
138 while(stack.size() > 0) {
140 if (o instanceof CallMarker) je("break or continue not within a loop");
141 if (o instanceof TryMarker) {
142 if(((TryMarker)o).finallyLoc < 0) continue; // no finally block, keep going
143 stack.push(new FinallyData(op, arg));
144 scope = ((TryMarker)o).scope;
145 pc = ((TryMarker)o).finallyLoc - 1;
148 if (o instanceof LoopMarker) {
149 if (arg == null || arg.equals(((LoopMarker)o).label)) {
150 int loopInstructionLocation = ((LoopMarker)o).location;
151 int endOfLoop = JS.toInt((JS)f.arg[loopInstructionLocation]) + loopInstructionLocation;
152 scope = ((LoopMarker)o).scope;
153 if (op == CONTINUE) { stack.push(o); stack.push(JS.F); }
154 pc = op == BREAK ? endOfLoop - 1 : loopInstructionLocation;
159 throw new Error("CONTINUE/BREAK invoked but couldn't find LoopMarker at " +
160 getSourceName() + ":" + getLine());
163 int[] jmps = (int[]) arg;
164 // jmps[0] is how far away the catch block is, jmps[1] is how far away the finally block is
165 // each can be < 0 if the specified block does not exist
166 stack.push(new TryMarker(jmps[0] < 0 ? -1 : pc + jmps[0], jmps[1] < 0 ? -1 : pc + jmps[1], this));
171 JS retval = stack.pop();
172 while(stack.size() > 0) {
173 Object o = stack.pop();
174 if (o instanceof TryMarker) {
175 if(((TryMarker)o).finallyLoc < 0) continue;
177 stack.push(new FinallyData(RETURN));
178 scope = ((TryMarker)o).scope;
179 pc = ((TryMarker)o).finallyLoc - 1;
181 } else if (o instanceof CallMarker) {
182 if (o instanceof TrapMarker) { // handles return component of a read trap
183 TrapMarker tm = (TrapMarker) o;
184 boolean cascade = tm.t.writeTrap() && !tm.cascadeHappened && !JS.toBoolean(retval);
187 while(t != null && t.readTrap()) t = t.next;
191 stack.push(new JSArgs(tm.val,t.f));
193 scope = new JSScope(f.parentScope);
197 tm.trapee.put(tm.key,tm.val);
201 scope = ((CallMarker)o).scope;
202 pc = ((CallMarker)o).pc - 1;
203 f = (JSFunction)((CallMarker)o).f;
205 if (pausecount > initialPauseCount) { pc++; return null; } // we were paused
206 if(f == null) return retval;
210 throw new Error("error: RETURN invoked but couldn't find a CallMarker!");
214 JS val = stack.pop();
215 JS key = stack.pop();
216 JS target = stack.peek();
217 if (target == null) throw je("tried to put " + JS.debugToString(val) + " to the " + JS.debugToString(key) + " property on the null value");
218 if (key == null) throw je("tried to assign \"" + JS.debugToString(val) + "\" to the null key");
221 TrapMarker tm = null;
222 if(target instanceof JSScope && key.jsequals(CASCADE)) {
225 for(i=stack.size()-1;i>=0;i--) if((o = stack.elementAt(i)) instanceof CallMarker) break;
226 if(i==0) throw new Error("didn't find a call marker while doing cascade");
227 if(o instanceof TrapMarker) {
231 tm.cascadeHappened = true;
233 if(t.readTrap()) throw new JSExn("can't do a write cascade in a read trap");
235 while(t != null && t.readTrap()) t = t.next;
238 if(tm == null) { // didn't find a trap marker, try to find a trap
239 t = target instanceof JSScope ? t = ((JSScope)target).top().getTrap(key) : ((JS)target).getTrap(key);
240 while(t != null && t.readTrap()) t = t.next;
246 stack.push(new TrapMarker(this,t,target,key,val));
247 stack.push(new JSArgs(t.f));
249 scope = new TrapScope(f.parentScope,target,f,key);
254 if (pausecount > initialPauseCount) { pc++; return null; } // we were paused
263 key = arg == null ? stack.pop() : (JS)arg;
264 target = stack.pop();
267 target = stack.peek();
271 if (key == null) throw je("tried to get the null key from " + target);
272 if (target == null) throw je("tried to get property \"" + key + "\" from the null object");
275 TrapMarker tm = null;
276 if(target instanceof JSScope && key.jsequals(CASCADE)) {
279 for(i=stack.size()-1;i>=0;i--) if((o = stack.elementAt(i)) instanceof CallMarker) break;
280 if(i==0) throw new Error("didn't find a call marker while doing cascade");
281 if(o instanceof TrapMarker) {
286 if(t.writeTrap()) throw new JSExn("can't do a read cascade in a write trap");
288 while(t != null && t.writeTrap()) t = t.next;
289 if(t != null) tm.cascadeHappened = true;
292 if(tm == null) { // didn't find a trap marker, try to find a trap
293 t = target instanceof JSScope ? t = ((JSScope)target).top().getTrap(key) : ((JS)target).getTrap(key);
294 while(t != null && t.writeTrap()) t = t.next;
298 stack.push(new TrapMarker(this,t,(JS)target,key,null));
299 stack.push(new JSArgs(t.f));
301 scope = new TrapScope(f.parentScope,target,f,key);
305 ret = target.get(key);
306 if (pausecount > initialPauseCount) { pc++; return null; } // we were paused
307 if (ret == JS.METHOD) ret = new Stub(target, key);
313 case CALL: case CALLMETHOD: {
314 int numArgs = JS.toInt((JS)arg);
316 JS[] rest = numArgs > 3 ? new JS[numArgs - 3] : null;
317 for(int i=numArgs - 1; i>2; i--) rest[i-3] = stack.pop();
318 JS a2 = numArgs <= 2 ? null : stack.pop();
319 JS a1 = numArgs <= 1 ? null : stack.pop();
320 JS a0 = numArgs <= 0 ? null : stack.pop();
324 JS object = stack.pop();
326 if (op == CALLMETHOD) {
327 if (object == JS.METHOD) {
328 method = stack.pop();
329 object = stack.pop();
330 } else if (object == null) {
331 method = stack.pop();
332 object = stack.pop();
333 throw new JSExn("function '"+JS.debugToString(method)+"' not found in " + object.getClass().getName());
340 if (object instanceof JSFunction) {
341 stack.push(new CallMarker(this));
342 stack.push(new JSArgs(a0,a1,a2,rest,numArgs,object));
343 f = (JSFunction)object;
344 scope = new JSScope(f.parentScope);
349 ret = method == null ? c.call(a0, a1, a2, rest, numArgs) : c.callMethod(method, a0, a1, a2, rest, numArgs);
352 if (pausecount > initialPauseCount) { pc++; return null; }
358 throw new JSExn(stack.pop(), stack, f, pc, scope);
362 final Grammar r = (Grammar)arg;
363 final JSScope final_scope = scope;
364 Grammar r2 = new Grammar() {
365 public int match(String s, int start, Hash v, JSScope scope) throws JSExn {
366 return r.match(s, start, v, final_scope);
368 public int matchAndWrite(String s, int start, Hash v, JSScope scope, String key) throws JSExn {
369 return r.matchAndWrite(s, start, v, final_scope, key);
371 public Object call(Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn {
373 r.matchAndWrite((String)a0, 0, v, final_scope, "foo");
377 Object obj = stack.pop();
378 if (obj != null && obj instanceof Grammar) r2 = new Grammar.Alternative((Grammar)obj, r2);
383 case ADD_TRAP: case DEL_TRAP: {
384 JS val = stack.pop();
385 JS key = stack.pop();
386 JS js = stack.peek();
387 // A trap addition/removal
388 if(!(val instanceof JSFunction)) throw new JSExn("tried to add/remove a non-function trap");
389 if(js instanceof JSScope) {
390 JSScope s = (JSScope) js;
391 while(s.getParentScope() != null) {
392 if(s.has(key)) throw new JSExn("cannot trap a variable that isn't at the top level scope");
393 s = s.getParentScope();
397 if(op == ADD_TRAP) js.addTrap(key, (JSFunction)val);
398 else js.delTrap(key, (JSFunction)val);
403 int count = ((JSNumber)arg).toInt();
404 if(count < 2) throw new Error("this should never happen");
407 JS right = stack.pop();
408 JS left = stack.pop();
410 if(left instanceof JSString || right instanceof JSString)
411 ret = JS.S(JS.toString(left).concat(JS.toString(right)));
412 else if(left instanceof JSNumber.D || right instanceof JSNumber.D)
413 ret = JS.N(JS.toDouble(left) + JS.toDouble(right));
415 long l = JS.toLong(left) + JS.toLong(right);
416 if(l < Integer.MIN_VALUE || l > Integer.MAX_VALUE) ret = JS.N(l);
421 JS[] args = new JS[count];
422 while(--count >= 0) args[count] = stack.pop();
423 if(args[0] instanceof JSString) {
424 StringBuffer sb = new StringBuffer(64);
425 for(int i=0;i<args.length;i++) sb.append(JS.toString(args[i]));
426 stack.push(JS.S(sb.toString()));
429 for(int i=0;i<args.length;i++) if(args[i] instanceof JSString) numStrings++;
430 if(numStrings == 0) {
432 for(int i=0;i<args.length;i++) d += JS.toDouble(args[i]);
436 StringBuffer sb = new StringBuffer(64);
437 if(!(args[0] instanceof JSString || args[1] instanceof JSString)) {
440 d += JS.toDouble(args[i++]);
441 } while(!(args[i] instanceof JSString));
442 sb.append(JS.toString(JS.N(d)));
444 while(i < args.length) sb.append(JS.toString(args[i++]));
445 stack.push(JS.S(sb.toString()));
453 JS right = stack.pop();
454 JS left = stack.pop();
457 case BITOR: stack.push(JS.N(JS.toLong(left) | JS.toLong(right))); break;
458 case BITXOR: stack.push(JS.N(JS.toLong(left) ^ JS.toLong(right))); break;
459 case BITAND: stack.push(JS.N(JS.toLong(left) & JS.toLong(right))); break;
461 case SUB: stack.push(JS.N(JS.toDouble(left) - JS.toDouble(right))); break;
462 case MUL: stack.push(JS.N(JS.toDouble(left) * JS.toDouble(right))); break;
463 case DIV: stack.push(JS.N(JS.toDouble(left) / JS.toDouble(right))); break;
464 case MOD: stack.push(JS.N(JS.toDouble(left) % JS.toDouble(right))); break;
466 case LSH: stack.push(JS.N(JS.toLong(left) << JS.toLong(right))); break;
467 case RSH: stack.push(JS.N(JS.toLong(left) >> JS.toLong(right))); break;
468 case URSH: stack.push(JS.N(JS.toLong(left) >>> JS.toLong(right))); break;
470 //#repeat </<=/>/>= LT/LE/GT/GE
472 if(left instanceof JSString && right instanceof JSString)
473 stack.push(JS.B(JS.toString(left).compareTo(JS.toString(right)) < 0));
475 stack.push(JS.B(JS.toDouble(left) < JS.toDouble(right)));
482 if(left == null && right == null) ret = true;
483 else if(left == null || right == null) ret = false;
484 else ret = left.jsequals(right);
485 stack.push(JS.B(op == EQ ? ret : !ret)); break;
488 default: throw new Error("unknown opcode " + op);
494 pc--; // it'll get incremented on the next iteration
499 /** tries to find a handler withing the call chain for this exception
500 if a handler is found the interpreter is setup to call the exception handler
501 if a handler is not found the exception is thrown
503 void catchException(JSExn e) throws JSExn {
504 while(stack.size() > 0) {
506 if (o instanceof CatchMarker || o instanceof TryMarker) {
507 boolean inCatch = o instanceof CatchMarker;
510 if(((TryMarker)o).finallyLoc < 0) continue; // no finally block, keep going
512 if(!inCatch && ((TryMarker)o).catchLoc >= 0) {
513 // run the catch block, this will implicitly run the finally block, if it exists
515 stack.push(catchMarker);
516 stack.push(e.getObject());
517 f = ((TryMarker)o).f;
518 scope = ((TryMarker)o).scope;
519 pc = ((TryMarker)o).catchLoc;
522 stack.push(new FinallyData(e));
523 f = ((TryMarker)o).f;
524 scope = ((TryMarker)o).scope;
525 pc = ((TryMarker)o).finallyLoc;
535 // Markers //////////////////////////////////////////////////////////////////////
537 static class Marker extends JS {
538 public JS get(JS key) throws JSExn { throw new Error("this should not be accessible from a script"); }
539 public void put(JS key, JS val) throws JSExn { throw new Error("this should not be accessible from a script"); }
540 public String coerceToString() { throw new Error("this should not be accessible from a script"); }
541 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"); }
544 static class CallMarker extends Marker {
548 public CallMarker(Interpreter cx) { pc = cx.pc + 1; scope = cx.scope; f = cx.f; }
549 public CallMarker() { pc = -1; scope = null; f = null; }
552 static class TrapMarker extends CallMarker {
557 boolean cascadeHappened;
558 public TrapMarker(Interpreter cx, Trap t, JS trapee, JS key, JS val) {
561 this.trapee = trapee;
567 static class CatchMarker extends Marker { }
568 private static CatchMarker catchMarker = new CatchMarker();
570 static class LoopMarker extends Marker {
571 final public int location;
572 final public String label;
573 final public JSScope scope;
574 public LoopMarker(int location, String label, JSScope scope) {
575 this.location = location;
580 static class TryMarker extends Marker {
581 final public int catchLoc;
582 final public int finallyLoc;
583 final public JSScope scope;
584 final public JSFunction f;
585 public TryMarker(int catchLoc, int finallyLoc, Interpreter cx) {
586 this.catchLoc = catchLoc;
587 this.finallyLoc = finallyLoc;
588 this.scope = cx.scope;
592 static class FinallyData extends Marker {
594 final public Object arg;
595 final public JSExn exn;
596 public FinallyData(int op) { this(op,null); }
597 public FinallyData(int op, Object arg) { this.op = op; this.arg = arg; this.exn = null; }
598 public FinallyData(JSExn exn) { this.exn = exn; this.op = -1; this.arg = null; } // Just throw this exn
601 static class TrapScope extends JSScope {
605 public TrapScope(JSScope parent, JS trapee, JS callee, JS trapname) {
606 super(parent); this.trapee = trapee; this.callee = callee; this.trapname = trapname;
608 public JS get(JS key) throws JSExn {
609 if(JS.isString(key)) {
610 //#switch(JS.toString(key))
611 case "trapee": return trapee;
612 case "callee": return callee;
613 case "trapname": return trapname;
616 return super.get(key);
620 static class JSArgs extends JS {
624 private final JS[] rest;
625 private final int nargs;
626 private final JS callee;
628 public JSArgs(JS callee) { this(null,null,null,null,0,callee); }
629 public JSArgs(JS a0, JS callee) { this(a0,null,null,null,1,callee); }
630 public JSArgs(JS a0, JS a1, JS a2, JS[] rest, int nargs, JS callee) {
631 this.a0 = a0; this.a1 = a1; this.a2 = a2;
632 this.rest = rest; this.nargs = nargs;
633 this.callee = callee;
636 public JS get(JS key) throws JSExn {
638 int n = JS.toInt(key);
643 default: return n>= 0 && n < nargs ? rest[n-3] : null;
646 //#switch(JS.toString(key))
647 case "callee": return callee;
648 case "length": return JS.N(nargs);
650 return super.get(key);
654 static class Stub extends JS {
657 public Stub(JS obj, JS method) { this.obj = obj; this.method = method; }
658 public JS call(JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn {
659 return ((JS)obj).callMethod(method, a0, a1, a2, rest, nargs);
664 private static final int MAX_STACK_SIZE = 512;
665 private JS[] stack = new JS[64];
667 public final void push(JS o) throws JSExn {
668 if(sp == stack.length) grow();
671 public final JS peek() {
672 if(sp == 0) throw new RuntimeException("Stack underflow");
675 public final JS pop() {
676 if(sp == 0) throw new RuntimeException("Stack underflow");
679 private void grow() throws JSExn {
680 if(stack.length >= MAX_STACK_SIZE) throw new JSExn("Stack overflow");
681 JS[] stack2 = new JS[stack.length * 2];
682 System.arraycopy(stack,0,stack2,0,stack.length);
684 public int size() { return sp; }
685 public JS elementAt(int i) { return stack[i]; }
687 // FIXME: Eliminate all uses of SWAP n>1 so we don't need this
688 public void setElementAt(JS o, int i) { stack[i] = o; }