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, JSArray 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 JSArray args = new JSArray();
192 args.addElement(tm.val);
195 scope = new JSScope(f.parentScope);
199 tm.trapee.put(tm.key,tm.val);
203 scope = ((CallMarker)o).scope;
204 pc = ((CallMarker)o).pc - 1;
205 f = (JSFunction)((CallMarker)o).f;
207 if (pausecount > initialPauseCount) { pc++; return null; } // we were paused
208 if(f == null) return retval;
212 throw new Error("error: RETURN invoked but couldn't find a CallMarker!");
216 JS val = stack.pop();
217 JS key = stack.pop();
218 JS target = stack.peek();
219 if (target == null) throw je("tried to put " + JS.debugToString(val) + " to the " + JS.debugToString(key) + " property on the null value");
220 if (key == null) throw je("tried to assign \"" + JS.debugToString(val) + "\" to the null key");
223 TrapMarker tm = null;
224 if(target instanceof JSScope && key.jsequals(CASCADE)) {
227 for(i=stack.size()-1;i>=0;i--) if((o = stack.elementAt(i)) instanceof CallMarker) break;
228 if(i==0) throw new Error("didn't find a call marker while doing cascade");
229 if(o instanceof TrapMarker) {
233 tm.cascadeHappened = true;
235 if(t.readTrap()) throw new JSExn("can't do a write cascade in a read trap");
237 while(t != null && t.readTrap()) t = t.next;
240 if(tm == null) { // didn't find a trap marker, try to find a trap
241 t = target instanceof JSScope ? t = ((JSScope)target).top().getTrap(key) : ((JS)target).getTrap(key);
242 while(t != null && t.readTrap()) t = t.next;
248 stack.push(new TrapMarker(this,t,target,key,val));
249 JSArray args = new JSArray();
250 args.addElement(val);
253 scope = new TrapScope(f.parentScope,target,f,key);
258 if (pausecount > initialPauseCount) { pc++; return null; } // we were paused
267 key = arg == null ? stack.pop() : (JS)arg;
268 target = stack.pop();
271 target = stack.peek();
275 if (key == null) throw je("tried to get the null key from " + target);
276 if (target == null) throw je("tried to get property \"" + key + "\" from the null object");
279 TrapMarker tm = null;
280 if(target instanceof JSScope && key.jsequals(CASCADE)) {
283 for(i=stack.size()-1;i>=0;i--) if((o = stack.elementAt(i)) instanceof CallMarker) break;
284 if(i==0) throw new Error("didn't find a call marker while doing cascade");
285 if(o instanceof TrapMarker) {
290 if(t.writeTrap()) throw new JSExn("can't do a read cascade in a write trap");
292 while(t != null && t.writeTrap()) t = t.next;
293 if(t != null) tm.cascadeHappened = true;
296 if(tm == null) { // didn't find a trap marker, try to find a trap
297 t = target instanceof JSScope ? t = ((JSScope)target).top().getTrap(key) : ((JS)target).getTrap(key);
298 while(t != null && t.writeTrap()) t = t.next;
302 stack.push(new TrapMarker(this,t,(JS)target,key,null));
303 stack.push(new JSArray());
305 scope = new TrapScope(f.parentScope,target,f,key);
309 ret = target.get(key);
310 if (pausecount > initialPauseCount) { pc++; return null; } // we were paused
311 if (ret == JS.METHOD) ret = new Stub(target, key);
317 case CALL: case CALLMETHOD: {
318 int numArgs = JS.toInt((JS)arg);
321 JS object = stack.pop();
323 if (op == CALLMETHOD) {
324 if (object == JS.METHOD) {
325 method = stack.pop();
326 object = stack.pop();
327 } else if (object == null) {
328 method = stack.pop();
329 object = stack.pop();
330 throw new JSExn("function '"+JS.debugToString(method)+"' not found in " + object.getClass().getName());
336 JS[] rest = numArgs > 3 ? new JS[numArgs - 3] : null;
337 for(int i=numArgs - 1; i>2; i--) rest[i-3] = stack.pop();
338 JS a2 = numArgs <= 2 ? null : stack.pop();
339 JS a1 = numArgs <= 1 ? null : stack.pop();
340 JS a0 = numArgs <= 0 ? null : stack.pop();
343 if (object instanceof JSFunction) {
344 // FIXME: use something similar to call0/call1/call2 here
345 JSArray arguments = new JSArray();
346 for(int i=0; i<numArgs; i++) arguments.addElement(i==0?a0:i==1?a1:i==2?a2:rest[i-3]);
347 stack.push(new CallMarker(this));
348 stack.push(arguments);
349 f = (JSFunction)object;
350 scope = new JSScope(f.parentScope);
355 ret = method == null ? c.call(a0, a1, a2, rest, numArgs) : c.callMethod(method, a0, a1, a2, rest, numArgs);
358 if (pausecount > initialPauseCount) { pc++; return null; }
364 throw new JSExn(stack.pop(), stack, f, pc, scope);
368 final Grammar r = (Grammar)arg;
369 final JSScope final_scope = scope;
370 Grammar r2 = new Grammar() {
371 public int match(String s, int start, Hash v, JSScope scope) throws JSExn {
372 return r.match(s, start, v, final_scope);
374 public int matchAndWrite(String s, int start, Hash v, JSScope scope, String key) throws JSExn {
375 return r.matchAndWrite(s, start, v, final_scope, key);
377 public Object call(Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn {
379 r.matchAndWrite((String)a0, 0, v, final_scope, "foo");
383 Object obj = stack.pop();
384 if (obj != null && obj instanceof Grammar) r2 = new Grammar.Alternative((Grammar)obj, r2);
389 case ADD_TRAP: case DEL_TRAP: {
390 JS val = stack.pop();
391 JS key = stack.pop();
392 JS js = stack.peek();
393 // A trap addition/removal
394 if(!(val instanceof JSFunction)) throw new JSExn("tried to add/remove a non-function trap");
395 if(js instanceof JSScope) {
396 JSScope s = (JSScope) js;
397 while(s.getParentScope() != null) {
398 if(s.has(key)) throw new JSExn("cannot trap a variable that isn't at the top level scope");
399 s = s.getParentScope();
403 if(op == ADD_TRAP) js.addTrap(key, (JSFunction)val);
404 else js.delTrap(key, (JSFunction)val);
409 int count = ((JSNumber)arg).toInt();
410 if(count < 2) throw new Error("this should never happen");
413 JS right = stack.pop();
414 JS left = stack.pop();
416 if(left instanceof JSString || right instanceof JSString)
417 ret = JS.S(JS.toString(left).concat(JS.toString(right)));
418 else if(left instanceof JSNumber.D || right instanceof JSNumber.D)
419 ret = JS.N(JS.toDouble(left) + JS.toDouble(right));
421 long l = JS.toLong(left) + JS.toLong(right);
422 if(l < Integer.MIN_VALUE || l > Integer.MAX_VALUE) ret = JS.N(l);
427 JS[] args = new JS[count];
428 while(--count >= 0) args[count] = stack.pop();
429 if(args[0] instanceof JSString) {
430 StringBuffer sb = new StringBuffer(64);
431 for(int i=0;i<args.length;i++) sb.append(JS.toString(args[i]));
432 stack.push(JS.S(sb.toString()));
435 for(int i=0;i<args.length;i++) if(args[i] instanceof JSString) numStrings++;
436 if(numStrings == 0) {
438 for(int i=0;i<args.length;i++) d += JS.toDouble(args[i]);
442 StringBuffer sb = new StringBuffer(64);
443 if(!(args[0] instanceof JSString || args[1] instanceof JSString)) {
446 d += JS.toDouble(args[i++]);
447 } while(!(args[i] instanceof JSString));
448 sb.append(JS.toString(JS.N(d)));
450 while(i < args.length) sb.append(JS.toString(args[i++]));
451 stack.push(JS.S(sb.toString()));
459 JS right = stack.pop();
460 JS left = stack.pop();
463 case BITOR: stack.push(JS.N(JS.toLong(left) | JS.toLong(right))); break;
464 case BITXOR: stack.push(JS.N(JS.toLong(left) ^ JS.toLong(right))); break;
465 case BITAND: stack.push(JS.N(JS.toLong(left) & JS.toLong(right))); break;
467 case SUB: stack.push(JS.N(JS.toDouble(left) - JS.toDouble(right))); break;
468 case MUL: stack.push(JS.N(JS.toDouble(left) * JS.toDouble(right))); break;
469 case DIV: stack.push(JS.N(JS.toDouble(left) / JS.toDouble(right))); break;
470 case MOD: stack.push(JS.N(JS.toDouble(left) % JS.toDouble(right))); break;
472 case LSH: stack.push(JS.N(JS.toLong(left) << JS.toLong(right))); break;
473 case RSH: stack.push(JS.N(JS.toLong(left) >> JS.toLong(right))); break;
474 case URSH: stack.push(JS.N(JS.toLong(left) >>> JS.toLong(right))); break;
476 //#repeat </<=/>/>= LT/LE/GT/GE
478 if(left instanceof JSString && right instanceof JSString)
479 stack.push(JS.B(JS.toString(left).compareTo(JS.toString(right)) < 0));
481 stack.push(JS.B(JS.toDouble(left) < JS.toDouble(right)));
488 if(left == null && right == null) ret = true;
489 else if(left == null || right == null) ret = false;
490 else ret = left.jsequals(right);
491 stack.push(JS.B(op == EQ ? ret : !ret)); break;
494 default: throw new Error("unknown opcode " + op);
500 pc--; // it'll get incremented on the next iteration
505 /** tries to find a handler withing the call chain for this exception
506 if a handler is found the interpreter is setup to call the exception handler
507 if a handler is not found the exception is thrown
509 void catchException(JSExn e) throws JSExn {
510 while(stack.size() > 0) {
512 if (o instanceof CatchMarker || o instanceof TryMarker) {
513 boolean inCatch = o instanceof CatchMarker;
516 if(((TryMarker)o).finallyLoc < 0) continue; // no finally block, keep going
518 if(!inCatch && ((TryMarker)o).catchLoc >= 0) {
519 // run the catch block, this will implicitly run the finally block, if it exists
521 stack.push(catchMarker);
522 stack.push(e.getObject());
523 f = ((TryMarker)o).f;
524 scope = ((TryMarker)o).scope;
525 pc = ((TryMarker)o).catchLoc;
528 stack.push(new FinallyData(e));
529 f = ((TryMarker)o).f;
530 scope = ((TryMarker)o).scope;
531 pc = ((TryMarker)o).finallyLoc;
541 // Markers //////////////////////////////////////////////////////////////////////
543 static class Marker extends JS {
544 public JS get(JS key) throws JSExn { throw new Error("this should not be accessible from a script"); }
545 public void put(JS key, JS val) throws JSExn { throw new Error("this should not be accessible from a script"); }
546 public String coerceToString() { throw new Error("this should not be accessible from a script"); }
547 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"); }
550 static class CallMarker extends Marker {
554 public CallMarker(Interpreter cx) { pc = cx.pc + 1; scope = cx.scope; f = cx.f; }
555 public CallMarker() { pc = -1; scope = null; f = null; }
558 static class TrapMarker extends CallMarker {
563 boolean cascadeHappened;
564 public TrapMarker(Interpreter cx, Trap t, JS trapee, JS key, JS val) {
567 this.trapee = trapee;
573 static class CatchMarker extends Marker { }
574 private static CatchMarker catchMarker = new CatchMarker();
576 static class LoopMarker extends Marker {
577 final public int location;
578 final public String label;
579 final public JSScope scope;
580 public LoopMarker(int location, String label, JSScope scope) {
581 this.location = location;
586 static class TryMarker extends Marker {
587 final public int catchLoc;
588 final public int finallyLoc;
589 final public JSScope scope;
590 final public JSFunction f;
591 public TryMarker(int catchLoc, int finallyLoc, Interpreter cx) {
592 this.catchLoc = catchLoc;
593 this.finallyLoc = finallyLoc;
594 this.scope = cx.scope;
598 static class FinallyData extends Marker {
600 final public Object arg;
601 final public JSExn exn;
602 public FinallyData(int op) { this(op,null); }
603 public FinallyData(int op, Object arg) { this.op = op; this.arg = arg; this.exn = null; }
604 public FinallyData(JSExn exn) { this.exn = exn; this.op = -1; this.arg = null; } // Just throw this exn
607 static class TrapScope extends JSScope {
611 public TrapScope(JSScope parent, JS trapee, JS callee, JS trapname) {
612 super(parent); this.trapee = trapee; this.callee = callee; this.trapname = trapname;
614 public JS get(JS key) throws JSExn {
615 if(JS.isString(key)) {
616 //#switch(JS.toString(key))
617 case "trapee": return trapee;
618 case "callee": return callee;
619 case "trapname": return trapname;
622 return super.get(key);
626 static class Stub extends JS {
629 public Stub(JS obj, JS method) { this.obj = obj; this.method = method; }
630 public JS call(JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn {
631 return ((JS)obj).callMethod(method, a0, a1, a2, rest, nargs);
636 private static final int MAX_STACK_SIZE = 512;
637 private JS[] stack = new JS[64];
639 public final void push(JS o) throws JSExn {
640 if(sp == stack.length) grow();
643 public final JS peek() {
644 if(sp == 0) throw new RuntimeException("Stack underflow");
647 public final JS pop() {
648 if(sp == 0) throw new RuntimeException("Stack underflow");
651 private void grow() throws JSExn {
652 if(stack.length >= MAX_STACK_SIZE) throw new JSExn("Stack overflow");
653 JS[] stack2 = new JS[stack.length * 2];
654 System.arraycopy(stack,0,stack2,0,stack.length);
656 // FIXME: Eliminate all uses of SWAP n>1 so we don't need this
657 public int size() { return sp; }
658 public void setElementAt(JS o, int i) { stack[i] = o; }
659 public JS elementAt(int i) { return stack[i]; }