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 (ret == JS.METHOD) ret = new Stub(target, key);
312 if (pausecount > initialPauseCount) { pc++; return null; } // we were paused
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);
408 // FIXME: This was for the old trap syntax, remove it, shouldn't be needed anymore
409 case ASSIGN_SUB: case ASSIGN_ADD: {
410 JS val = stack.pop();
411 JS key = stack.pop();
412 JS obj = stack.peek();
413 // The following setup is VERY important. The generated bytecode depends on the stack
414 // being setup like this (top to bottom) KEY, OBJ, VAL, KEY, OBJ
423 int count = ((JSNumber)arg).toInt();
424 if(count < 2) throw new Error("this should never happen");
427 JS right = stack.pop();
428 JS left = stack.pop();
430 if(left instanceof JSString || right instanceof JSString)
431 ret = JS.S(JS.toString(left).concat(JS.toString(right)));
432 else if(left instanceof JSNumber.D || right instanceof JSNumber.D)
433 ret = JS.N(JS.toDouble(left) + JS.toDouble(right));
435 long l = JS.toLong(left) + JS.toLong(right);
436 if(l < Integer.MIN_VALUE || l > Integer.MAX_VALUE) ret = JS.N(l);
441 JS[] args = new JS[count];
442 while(--count >= 0) args[count] = stack.pop();
443 if(args[0] instanceof JSString) {
444 StringBuffer sb = new StringBuffer(64);
445 for(int i=0;i<args.length;i++) sb.append(JS.toString(args[i]));
446 stack.push(JS.S(sb.toString()));
449 for(int i=0;i<args.length;i++) if(args[i] instanceof JSString) numStrings++;
450 if(numStrings == 0) {
452 for(int i=0;i<args.length;i++) d += JS.toDouble(args[i]);
456 StringBuffer sb = new StringBuffer(64);
457 if(!(args[0] instanceof JSString || args[1] instanceof JSString)) {
460 d += JS.toDouble(args[i++]);
461 } while(!(args[i] instanceof JSString));
462 sb.append(JS.toString(JS.N(d)));
464 while(i < args.length) sb.append(JS.toString(args[i++]));
465 stack.push(JS.S(sb.toString()));
473 JS right = stack.pop();
474 JS left = stack.pop();
477 case BITOR: stack.push(JS.N(JS.toLong(left) | JS.toLong(right))); break;
478 case BITXOR: stack.push(JS.N(JS.toLong(left) ^ JS.toLong(right))); break;
479 case BITAND: stack.push(JS.N(JS.toLong(left) & JS.toLong(right))); break;
481 case SUB: stack.push(JS.N(JS.toDouble(left) - JS.toDouble(right))); break;
482 case MUL: stack.push(JS.N(JS.toDouble(left) * JS.toDouble(right))); break;
483 case DIV: stack.push(JS.N(JS.toDouble(left) / JS.toDouble(right))); break;
484 case MOD: stack.push(JS.N(JS.toDouble(left) % JS.toDouble(right))); break;
486 case LSH: stack.push(JS.N(JS.toLong(left) << JS.toLong(right))); break;
487 case RSH: stack.push(JS.N(JS.toLong(left) >> JS.toLong(right))); break;
488 case URSH: stack.push(JS.N(JS.toLong(left) >>> JS.toLong(right))); break;
490 //#repeat </<=/>/>= LT/LE/GT/GE
492 if(left instanceof JSString && right instanceof JSString)
493 stack.push(JS.B(JS.toString(left).compareTo(JS.toString(right)) < 0));
495 stack.push(JS.B(JS.toDouble(left) < JS.toDouble(right)));
502 if(left == null && right == null) ret = true;
503 else if(left == null || right == null) ret = false;
504 else ret = left.jsequals(right);
505 stack.push(JS.B(op == EQ ? ret : !ret)); break;
508 default: throw new Error("unknown opcode " + op);
513 while(stack.size() > 0) {
515 if (o instanceof CatchMarker || o instanceof TryMarker) {
516 boolean inCatch = o instanceof CatchMarker;
519 if(((TryMarker)o).finallyLoc < 0) continue; // no finally block, keep going
521 if(!inCatch && ((TryMarker)o).catchLoc >= 0) {
522 // run the catch block, this will implicitly run the finally block, if it exists
524 stack.push(catchMarker);
525 stack.push(e.getObject());
526 f = ((TryMarker)o).f;
527 scope = ((TryMarker)o).scope;
528 pc = ((TryMarker)o).catchLoc - 1;
531 stack.push(new FinallyData(e));
532 f = ((TryMarker)o).f;
533 scope = ((TryMarker)o).scope;
534 pc = ((TryMarker)o).finallyLoc - 1;
546 // Markers //////////////////////////////////////////////////////////////////////
548 static class Marker extends JS {
549 public JS get(JS key) throws JSExn { throw new Error("this should not be accessible from a script"); }
550 public void put(JS key, JS val) throws JSExn { throw new Error("this should not be accessible from a script"); }
551 public String coerceToString() { throw new Error("this should not be accessible from a script"); }
552 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"); }
555 static class CallMarker extends Marker {
559 public CallMarker(Interpreter cx) { pc = cx.pc + 1; scope = cx.scope; f = cx.f; }
560 public CallMarker() { pc = -1; scope = null; f = null; }
563 static class TrapMarker extends CallMarker {
568 boolean cascadeHappened;
569 public TrapMarker(Interpreter cx, Trap t, JS trapee, JS key, JS val) {
572 this.trapee = trapee;
578 static class CatchMarker extends Marker { }
579 private static CatchMarker catchMarker = new CatchMarker();
581 static class LoopMarker extends Marker {
582 final public int location;
583 final public String label;
584 final public JSScope scope;
585 public LoopMarker(int location, String label, JSScope scope) {
586 this.location = location;
591 static class TryMarker extends Marker {
592 final public int catchLoc;
593 final public int finallyLoc;
594 final public JSScope scope;
595 final public JSFunction f;
596 public TryMarker(int catchLoc, int finallyLoc, Interpreter cx) {
597 this.catchLoc = catchLoc;
598 this.finallyLoc = finallyLoc;
599 this.scope = cx.scope;
603 static class FinallyData extends Marker {
605 final public Object arg;
606 final public JSExn exn;
607 public FinallyData(int op) { this(op,null); }
608 public FinallyData(int op, Object arg) { this.op = op; this.arg = arg; this.exn = null; }
609 public FinallyData(JSExn exn) { this.exn = exn; this.op = -1; this.arg = null; } // Just throw this exn
612 static class TrapScope extends JSScope {
616 public TrapScope(JSScope parent, JS trapee, JS callee, JS trapname) {
617 super(parent); this.trapee = trapee; this.callee = callee; this.trapname = trapname;
619 public JS get(JS key) throws JSExn {
620 if(JS.isString(key)) {
621 //#switch(JS.toString(key))
622 case "trapee": return trapee;
623 case "callee": return callee;
624 case "trapname": return trapname;
627 return super.get(key);
631 // Operations on Primitives //////////////////////////////////////////////////////////////////////
633 // FIXME: Move these into JSString and JSNumber
634 /*static Object callMethodOnPrimitive(Object o, Object method, Object arg0, Object arg1, Object arg2, Object[] rest, int alength) throws JSExn {
635 if (method == null || !(method instanceof String) || "".equals(method))
636 throw new JSExn("attempt to call a non-existant method on a primitive");
638 if (o instanceof Number) {
640 case "toFixed": throw new JSExn("toFixed() not implemented");
641 case "toExponential": throw new JSExn("toExponential() not implemented");
642 case "toPrecision": throw new JSExn("toPrecision() not implemented");
644 int radix = alength >= 1 ? JS.toInt(arg0) : 10;
645 return Long.toString(((Number)o).longValue(),radix);
648 } else if (o instanceof Boolean) {
649 // No methods for Booleans
650 throw new JSExn("attempt to call a method on a Boolean");
653 String s = JS.toString(o);
654 int slength = s.length();
657 int a = alength >= 1 ? JS.toInt(arg0) : 0;
658 int b = alength >= 2 ? JS.toInt(arg1) : slength;
659 if (a > slength) a = slength;
660 if (b > slength) b = slength;
663 if (a > b) { int tmp = a; a = b; b = tmp; }
664 return s.substring(a,b);
667 int start = alength >= 1 ? JS.toInt(arg0) : 0;
668 int len = alength >= 2 ? JS.toInt(arg1) : Integer.MAX_VALUE;
669 if (start < 0) start = slength + start;
670 if (start < 0) start = 0;
671 if (len < 0) len = 0;
672 if (len > slength - start) len = slength - start;
673 if (len <= 0) return "";
674 return s.substring(start,start+len);
677 int p = alength >= 1 ? JS.toInt(arg0) : 0;
678 if (p < 0 || p >= slength) return "";
679 return s.substring(p,p+1);
682 int p = alength >= 1 ? JS.toInt(arg0) : 0;
683 if (p < 0 || p >= slength) return JS.N(Double.NaN);
684 return JS.N(s.charAt(p));
687 StringBuffer sb = new StringBuffer(slength*2).append(s);
688 for(int i=0;i<alength;i++) sb.append(i==0?arg0:i==1?arg1:i==2?arg2:rest[i-3]);
689 return sb.toString();
692 String search = alength >= 1 ? JS.toString(arg0) : "null";
693 int start = alength >= 2 ? JS.toInt(arg1) : 0;
694 // Java's indexOf handles an out of bounds start index, it'll return -1
695 return JS.N(s.indexOf(search,start));
697 case "lastIndexOf": {
698 String search = alength >= 1 ? JS.toString(arg0) : "null";
699 int start = alength >= 2 ? JS.toInt(arg1) : 0;
700 // Java's indexOf handles an out of bounds start index, it'll return -1
701 return JS.N(s.lastIndexOf(search,start));
703 case "match": return JSRegexp.stringMatch(s,arg0);
704 case "replace": return JSRegexp.stringReplace(s,arg0,arg1);
705 case "search": return JSRegexp.stringSearch(s,arg0);
706 case "split": return JSRegexp.stringSplit(s,arg0,arg1,alength);
707 case "toLowerCase": return s.toLowerCase();
708 case "toUpperCase": return s.toUpperCase();
709 case "toString": return s;
711 int a = alength >= 1 ? JS.toInt(arg0) : 0;
712 int b = alength >= 2 ? JS.toInt(arg1) : slength;
713 if (a < 0) a = slength + a;
714 if (b < 0) b = slength + b;
717 if (a > slength) a = slength;
718 if (b > slength) b = slength;
719 if (a > b) return "";
720 return s.substring(a,b);
723 throw new JSExn("Attempted to call non-existent method: " + method);
726 static Object getFromPrimitive(Object o, Object key) throws JSExn {
727 boolean returnJS = false;
728 if (o instanceof Boolean) {
729 throw new JSExn("Booleans do not have properties");
730 } else if (o instanceof Number) {
731 if (key.equals("toPrecision") || key.equals("toExponential") || key.equals("toFixed"))
735 // the string stuff applies to everything
736 String s = JS.toString(o);
738 // this is sort of ugly, but this list should never change
739 // These should provide a complete (enough) implementation of the ECMA-262 String object
742 case "length": return JS.N(s.length());
743 case "substring": returnJS = true; break;
744 case "charAt": returnJS = true; break;
745 case "charCodeAt": returnJS = true; break;
746 case "concat": returnJS = true; break;
747 case "indexOf": returnJS = true; break;
748 case "lastIndexOf": returnJS = true; break;
749 case "match": returnJS = true; break;
750 case "replace": returnJS = true; break;
751 case "search": returnJS = true; break;
752 case "slice": returnJS = true; break;
753 case "split": returnJS = true; break;
754 case "toLowerCase": returnJS = true; break;
755 case "toUpperCase": returnJS = true; break;
756 case "toString": returnJS = true; break;
757 case "substr": returnJS = true; break;
761 final Object target = o;
762 final String method = JS.toString(o);
764 public Object call(Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn {
765 if (nargs > 2) throw new JSExn("cannot call that method with that many arguments");
766 return callMethodOnPrimitive(target, method, a0, a1, a2, rest, nargs);
773 private static class Stub extends JS {
776 public Stub(JS obj, JS method) { this.obj = obj; this.method = method; }
777 public JS call(JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn {
778 return ((JS)obj).callMethod(method, a0, a1, a2, rest, nargs);
783 private static final int MAX_STACK_SIZE = 512;
784 private JS[] stack = new JS[64];
786 public final void push(JS o) throws JSExn {
787 if(sp == stack.length) grow();
790 public final JS peek() {
791 if(sp == 0) throw new RuntimeException("Stack underflow");
794 public final JS pop() {
795 if(sp == 0) throw new RuntimeException("Stack underflow");
798 private void grow() throws JSExn {
799 if(stack.length >= MAX_STACK_SIZE) throw new JSExn("Stack overflow");
800 JS[] stack2 = new JS[stack.length * 2];
801 System.arraycopy(stack,0,stack2,0,stack.length);
803 // FIXME: Eliminate all uses of SWAP n>1 so we don't need this
804 public int size() { return sp; }
805 public void setElementAt(JS o, int i) { stack[i] = o; }
806 public JS elementAt(int i) { return stack[i]; }