2004/01/07 20:37:32
[org.ibex.core.git] / src / org / xwt / js / Interpreter.java
1 // Copyright 2003 Adam Megacz, see the COPYING file for licensing [GPL]
2 package org.xwt.js;
3
4 import org.xwt.util.*;
5 import java.util.*;
6 import java.io.*;
7
8 /** Encapsulates a single JS interpreter (ie call stack) */
9 class Interpreter implements ByteCodes, Tokens {
10
11
12     // Thread-Interpreter Mapping /////////////////////////////////////////////////////////////////////////
13
14     static Interpreter current() { return (Interpreter)threadToInterpreter.get(Thread.currentThread()); }
15     private static Hashtable threadToInterpreter = new Hashtable();
16
17     
18     // Instance members and methods //////////////////////////////////////////////////////////////////////
19     
20     int pausecount;               ///< the number of times pause() has been invoked; -1 indicates unpauseable
21     JSFunction f = null;          ///< the currently-executing JSFunction
22     JSScope scope;                ///< the current top-level scope (LIFO stack via NEWSCOPE/OLDSCOPE)
23     Vec stack = new Vec();        ///< the object stack
24     int pc = 0;                   ///< the program counter
25
26     Interpreter(JSFunction f, boolean pauseable, JSArray args) {
27         stack.push(new Interpreter.CallMarker(this));    // the "root function returned" marker -- f==null
28         this.f = f;
29         this.pausecount = pauseable ? 0 : -1;
30         this.scope = new JSScope(f.parentScope);
31         stack.push(args);
32     }
33     
34     /** this is the only synchronization point we need in order to be threadsafe */
35     synchronized Object resume() throws JSExn {
36         Thread t = Thread.currentThread();
37         Interpreter old = (Interpreter)threadToInterpreter.get(t);
38         threadToInterpreter.put(t, this);
39         try {
40             return run();
41         } finally {
42             if (old == null) threadToInterpreter.remove(t);
43             else threadToInterpreter.put(t, old);
44         }
45     }
46
47     private static JSExn je(String s) { return new JSExn(JS.getSourceName() + ":" + JS.getLine() + " " + s); }
48
49     // FIXME: double check the trap logic
50     private Object run() throws JSExn {
51
52         // if pausecount changes after a get/put/call, we know we've been paused
53         final int initialPauseCount = pausecount;
54
55         OUTER: for(;; pc++) {
56         try {
57             if (f == null) return stack.pop();
58             int op = f.op[pc];
59             Object arg = f.arg[pc];
60             if(op == FINALLY_DONE) {
61                 FinallyData fd = (FinallyData) stack.pop();
62                 if(fd == null) continue OUTER; // NOP
63                 if(fd.exn != null) throw fd.exn;
64                 op = fd.op;
65                 arg = fd.arg;
66             }
67             switch(op) {
68             case LITERAL: stack.push(arg); break;
69             case OBJECT: stack.push(new JS()); break;
70             case ARRAY: stack.push(new JSArray(JS.toNumber(arg).intValue())); break;
71             case DECLARE: scope.declare((String)(arg==null ? stack.peek() : arg)); if(arg != null) stack.push(arg); break;
72             case TOPSCOPE: stack.push(scope); break;
73             case JT: if (JS.toBoolean(stack.pop())) pc += JS.toNumber(arg).intValue() - 1; break;
74             case JF: if (!JS.toBoolean(stack.pop())) pc += JS.toNumber(arg).intValue() - 1; break;
75             case JMP: pc += JS.toNumber(arg).intValue() - 1; break;
76             case POP: stack.pop(); break;
77             case SWAP: {
78                 int depth = (arg == null ? 1 : JS.toInt(arg));
79                 Object save = stack.elementAt(stack.size() - 1);
80                 for(int i=stack.size() - 1; i > stack.size() - 1 - depth; i--)
81                     stack.setElementAt(stack.elementAt(i-1), i);
82                 stack.setElementAt(save, stack.size() - depth - 1);
83                 break; }
84             case DUP: stack.push(stack.peek()); break;
85             case NEWSCOPE: scope = new JSScope(scope); break;
86             case OLDSCOPE: scope = scope.getParentScope(); break;
87             case ASSERT: if (!JS.toBoolean(stack.pop())) throw je("xwt.assertion.failed" /*FEATURE: line number*/); break;
88             case BITNOT: stack.push(JS.N(~JS.toLong(stack.pop()))); break;
89             case BANG: stack.push(JS.B(!JS.toBoolean(stack.pop()))); break;
90             case NEWFUNCTION: stack.push(((JSFunction)arg).cloneWithNewParentScope(scope)); break;
91             case LABEL: break;
92
93             case TYPEOF: {
94                 Object o = stack.pop();
95                 if (o == null) stack.push(null);
96                 else if (o instanceof JS) stack.push(((JS)o).typeName());
97                 else if (o instanceof String) stack.push("string");
98                 else if (o instanceof Number) stack.push("number");
99                 else if (o instanceof Boolean) stack.push("boolean");
100                 else stack.push("unknown");
101                 break;
102             }
103
104             case PUSHKEYS: {
105                 Object o = stack.peek();
106                 Enumeration e = ((JS)o).keys();
107                 JSArray a = new JSArray();
108                 while(e.hasMoreElements()) a.addElement(e.nextElement());
109                 stack.push(a);
110                 break;
111             }
112
113             case LOOP:
114                 stack.push(new LoopMarker(pc, pc > 0 && f.op[pc - 1] == LABEL ? (String)f.arg[pc - 1] : (String)null, scope));
115                 stack.push(Boolean.TRUE);
116                 break;
117
118             case BREAK:
119             case CONTINUE:
120                 while(stack.size() > 0) {
121                     Object o = stack.pop();
122                     if (o instanceof CallMarker) je("break or continue not within a loop");
123                     if (o instanceof TryMarker) {
124                         if(((TryMarker)o).finallyLoc < 0) continue; // no finally block, keep going
125                         stack.push(new FinallyData(op, arg));
126                         scope = ((TryMarker)o).scope;
127                         pc = ((TryMarker)o).finallyLoc - 1;
128                         continue OUTER;
129                     }
130                     if (o instanceof LoopMarker) {
131                         if (arg == null || arg.equals(((LoopMarker)o).label)) {
132                             int loopInstructionLocation = ((LoopMarker)o).location;
133                             int endOfLoop = ((Integer)f.arg[loopInstructionLocation]).intValue() + loopInstructionLocation;
134                             scope = ((LoopMarker)o).scope;
135                             if (op == CONTINUE) { stack.push(o); stack.push(Boolean.FALSE); }
136                             pc = op == BREAK ? endOfLoop - 1 : loopInstructionLocation;
137                             continue OUTER;
138                         }
139                     }
140                 }
141                 throw new Error("CONTINUE/BREAK invoked but couldn't find LoopMarker at " +
142                                 JS.getSourceName() + ":" + JS.getLine());
143
144             case TRY: {
145                 int[] jmps = (int[]) arg;
146                 // jmps[0] is how far away the catch block is, jmps[1] is how far away the finally block is
147                 // each can be < 0 if the specified block does not exist
148                 stack.push(new TryMarker(jmps[0] < 0 ? -1 : pc + jmps[0], jmps[1] < 0 ? -1 : pc + jmps[1], this));
149                 break;
150             }
151
152             case RETURN: {
153                 Object retval = stack.pop();
154                 while(stack.size() > 0) {
155                     Object o = stack.pop();
156                     if (o instanceof TryMarker) {
157                         if(((TryMarker)o).finallyLoc < 0) continue;
158                         stack.push(retval); 
159                         stack.push(new FinallyData(RETURN));
160                         scope = ((TryMarker)o).scope;
161                         pc = ((TryMarker)o).finallyLoc - 1;
162                         continue OUTER;
163                     } else if (o instanceof CallMarker) {
164                         if (scope instanceof Trap.TrapScope) { // handles return component of a read trap
165                             Trap.TrapScope ts = (Trap.TrapScope)scope;
166                             if (retval != null) ts.cascadeHappened = true;
167                             if (!ts.cascadeHappened) {
168                                 ts.cascadeHappened = true;
169                                 Trap t = ts.t.next;
170                                 while (t != null && t.f.numFormalArgs == 0) t = t.next;
171                                 if (t == null) {
172                                     ((JS)ts.t.trapee).put(ts.t.name, ts.val);
173                                     if (pausecount > initialPauseCount) { pc++; return null; }   // we were paused
174                                 } else {
175                                     stack.push(o);
176                                     JSArray args = new JSArray();
177                                     args.addElement(ts.val);
178                                     stack.push(args);
179                                     f = t.f;
180                                     scope = new Trap.TrapScope(f.parentScope, t, ts.val);
181                                     pc = -1;
182                                     continue OUTER;
183                                 }
184                             }
185                         }
186                         scope = ((CallMarker)o).scope;
187                         pc = ((CallMarker)o).pc - 1;
188                         f = (JSFunction)((CallMarker)o).f;
189                         stack.push(retval);
190                         continue OUTER;
191                     }
192                 }
193                 throw new Error("error: RETURN invoked but couldn't find a CallMarker!");
194             }
195
196             case PUT: {
197                 Object val = stack.pop();
198                 Object key = stack.pop();
199                 Object target = stack.peek();
200                 if (target == null)
201                     throw je("tried to put a value to the " + key + " property on the null value");
202                 if (!(target instanceof JS))
203                     throw je("tried to put a value to the " + key + " property on a " + target.getClass().getName());
204                 if (key == null)
205                     throw je("tried to assign \"" + (val==null?"(null)":val.toString()) + "\" to the null key");
206
207                 Trap t = null;
208                 if (target instanceof Trap.TrapScope && key.equals("cascade")) {
209                     Trap.TrapScope ts = (Trap.TrapScope)target;
210                     t = ts.t.next;
211                     ts.cascadeHappened = true;
212                     while (t != null && t.f.numFormalArgs == 0) t = t.next;
213                     if (t == null) { target = ts.t.trapee; key = ts.t.name; }
214
215                 } else if (target instanceof Trap.TrapScope && key.equals(((Trap.TrapScope)target).t.name)) {
216                     throw je("tried to put to " + key + " inside a trap it owns; use cascade instead"); 
217
218                 } else if (target instanceof JS) {
219                     if (target instanceof JSScope) {
220                         JSScope p = (JSScope)target; // search the scope-path for the trap
221                         t = p.getTrap(key);
222                         while (t == null && p.getParentScope() != null) { p = p.getParentScope(); t = p.getTrap(key); }
223                     } else {
224                         t = ((JS)target).getTrap(key);
225                     }
226
227                     while (t != null && t.f.numFormalArgs == 0) t = t.next; // find the first write trap
228                     if (t != null) {
229                         stack.push(new CallMarker(this));
230                         JSArray args = new JSArray();
231                         args.addElement(val);
232                         stack.push(args);
233                     }
234                 }
235                 if (t != null) {
236                     f = t.f;
237                     scope = new Trap.TrapScope(f.parentScope, t, val);
238                     pc = -1;
239                     break;
240                 }
241                 ((JS)target).put(key, val);
242                 if (pausecount > initialPauseCount) { pc++; return null; }   // we were paused
243                 stack.push(val);
244                 break;
245             }
246
247             case GET:
248             case GET_PRESERVE: {
249                 Object o, v;
250                 if (op == GET) {
251                     v = arg == null ? stack.pop() : arg;
252                     o = stack.pop();
253                 } else {
254                     v = stack.pop();
255                     o = stack.peek();
256                     stack.push(v);
257                 }
258                 Object ret = null;
259                 if (v == null) throw je("tried to get the null key from " + o);
260                 if (o == null) throw je("tried to get property \"" + v + "\" from the null object");
261                 if (o instanceof String || o instanceof Number || o instanceof Boolean) {
262                     ret = getFromPrimitive(o,v);
263                     stack.push(ret);
264                     break;
265                 } else if (o instanceof JS) {
266                     Trap t = null;
267                     if (o instanceof Trap.TrapScope && v.equals("cascade")) {
268                         t = ((Trap.TrapScope)o).t.next;
269                         while (t != null && t.f.numFormalArgs != 0) t = t.next;
270                         if (t == null) { v = ((Trap.TrapScope)o).t.name; o = ((Trap.TrapScope)o).t.trapee; }
271
272                     } else if (o instanceof JS) {
273                         if (o instanceof JSScope) {
274                             JSScope p = (JSScope)o; // search the scope-path for the trap
275                             t = p.getTrap(v);
276                             while (t == null && p.getParentScope() != null) { p = p.getParentScope(); t = p.getTrap(v); }
277                         } else {
278                             t = ((JS)o).getTrap(v);
279                         }
280
281                         while (t != null && t.f.numFormalArgs != 0) t = t.next; // get first read trap
282                         if (t != null) {
283                             stack.push(new CallMarker(this));
284                             JSArray args = new JSArray();
285                             stack.push(args);
286                         }
287                     }
288                     if (t != null) {
289                         f = t.f;
290                         scope = new Trap.TrapScope(f.parentScope, t, null);
291                         ((Trap.TrapScope)scope).cascadeHappened = true;
292                         pc = -1;
293                         break;
294                     }
295                     ret = ((JS)o).get(v);
296                     if (ret == JS.METHOD) ret = new Stub((JS)o, v);
297                     if (pausecount > initialPauseCount) { pc++; return null; }   // we were paused
298                     stack.push(ret);
299                     break;
300                 }
301                 throw je("tried to get property " + v + " from a " + o.getClass().getName());
302             }
303             
304             case CALL: case CALLMETHOD: {
305                 int numArgs = JS.toInt(arg);
306                 Object method = null;
307                 Object ret = null;
308                 Object object = stack.pop();
309
310                 if (op == CALLMETHOD) {
311                     if (object == JS.METHOD) {
312                         method = stack.pop();
313                         object = stack.pop();
314                     } else if (object == null) {
315                         Object name = stack.pop();
316                         stack.pop();
317                         throw new JSExn("function '"+name+"' not found");
318                     } else {
319                         stack.pop();
320                         stack.pop();
321                     }
322                 }
323                 Object[] rest = numArgs > 3 ? new Object[numArgs - 3] : null;
324                 for(int i=numArgs - 1; i>2; i--) rest[i-3] = stack.pop();
325                 Object a2 = numArgs <= 2 ? null : stack.pop();
326                 Object a1 = numArgs <= 1 ? null : stack.pop();
327                 Object a0 = numArgs <= 0 ? null : stack.pop();
328
329                 if (object instanceof String || object instanceof Number || object instanceof Boolean) {
330                     ret = callMethodOnPrimitive(object, method, a0, a1, a2, null, numArgs);
331
332                 } else if (object instanceof JSFunction) {
333                     // FIXME: use something similar to call0/call1/call2 here
334                     JSArray arguments = new JSArray();
335                     for(int i=0; i<numArgs; i++) arguments.addElement(i==0?a0:i==1?a1:i==2?a2:rest[i-3]);
336                     stack.push(new CallMarker(this));
337                     stack.push(arguments);
338                     f = (JSFunction)object;
339                     scope = new JSScope(f.parentScope);
340                     pc = -1;
341                     break;
342
343                 } else if (object instanceof JS) {
344                     JS c = (JS)object;
345                     ret = method == null ? c.call(a0, a1, a2, rest, numArgs) : c.callMethod(method, a0, a1, a2, rest, numArgs);
346
347                 } else {
348                     throw new JSExn("can't call a " + object + " @" + pc + "\n" + f.dump());
349
350                 }
351                 if (pausecount > initialPauseCount) { pc++; return null; }
352                 stack.push(ret);
353                 break;
354             }
355
356             case THROW:
357                 throw new JSExn(stack.pop());
358
359             case ASSIGN_SUB: case ASSIGN_ADD: {
360                 Object val = stack.pop();
361                 Object key = stack.pop();
362                 Object obj = stack.peek();
363                 if (val instanceof JSFunction && obj instanceof JS) {
364                     // A trap addition/removal
365                     JS js = obj instanceof JSScope ? ((JSScope)obj).top() : (JS) obj;
366                     if(op == ASSIGN_ADD) js.addTrap(key, (JSFunction)val);
367                     else js.delTrap(key, (JSFunction)val);
368                     pc += ((Integer)arg).intValue() - 1;
369                 } else {
370                     // The following setup is VERY important. The generated bytecode depends on the stack
371                     // being setup like this (top to bottom) KEY, OBJ, VAL, KEY, OBJ
372                     stack.push(key);
373                     stack.push(val);
374                     stack.push(obj);
375                     stack.push(key);
376                 }
377                 break;
378             }
379
380             case ADD: {
381                 int count = ((Number)arg).intValue();
382                 if(count < 2) throw new Error("this should never happen");
383                 if(count == 2) {
384                     // common case
385                     Object right = stack.pop();
386                     Object left = stack.pop();
387                     if(left instanceof String || right instanceof String)
388                         stack.push(JS.toString(left).concat(JS.toString(right)));
389                     else stack.push(JS.N(JS.toDouble(left) + JS.toDouble(right)));
390                 } else {
391                     Object[] args = new Object[count];
392                     while(--count >= 0) args[count] = stack.pop();
393                     if(args[0] instanceof String) {
394                         StringBuffer sb = new StringBuffer(64);
395                         for(int i=0;i<args.length;i++) sb.append(JS.toString(args[i]));
396                         stack.push(sb.toString());
397                     } else {
398                         int numStrings = 0;
399                         for(int i=0;i<args.length;i++) if(args[i] instanceof String) numStrings++;
400                         if(numStrings == 0) {
401                             double d = 0.0;
402                             for(int i=0;i<args.length;i++) d += JS.toDouble(args[i]);
403                             stack.push(JS.N(d));
404                         } else {
405                             int i=0;
406                             StringBuffer sb = new StringBuffer(64);
407                             if(!(args[0] instanceof String || args[1] instanceof String)) {
408                                 double d=0.0;
409                                 do {
410                                     d += JS.toDouble(args[i++]);
411                                 } while(!(args[i] instanceof String));
412                                 sb.append(JS.toString(JS.N(d)));
413                             }
414                             while(i < args.length) sb.append(JS.toString(args[i++]));
415                             stack.push(sb.toString());
416                         }
417                     }
418                 }
419                 break;
420             }
421
422             default: {
423                 Object right = stack.pop();
424                 Object left = stack.pop();
425                 switch(op) {
426                         
427                 case BITOR: stack.push(JS.N(JS.toLong(left) | JS.toLong(right))); break;
428                 case BITXOR: stack.push(JS.N(JS.toLong(left) ^ JS.toLong(right))); break;
429                 case BITAND: stack.push(JS.N(JS.toLong(left) & JS.toLong(right))); break;
430
431                 case SUB: stack.push(JS.N(JS.toDouble(left) - JS.toDouble(right))); break;
432                 case MUL: stack.push(JS.N(JS.toDouble(left) * JS.toDouble(right))); break;
433                 case DIV: stack.push(JS.N(JS.toDouble(left) / JS.toDouble(right))); break;
434                 case MOD: stack.push(JS.N(JS.toDouble(left) % JS.toDouble(right))); break;
435                         
436                 case LSH: stack.push(JS.N(JS.toLong(left) << JS.toLong(right))); break;
437                 case RSH: stack.push(JS.N(JS.toLong(left) >> JS.toLong(right))); break;
438                 case URSH: stack.push(JS.N(JS.toLong(left) >>> JS.toLong(right))); break;
439                         
440                 case LT: case LE: case GT: case GE: {
441                     if (left == null) left = JS.N(0);
442                     if (right == null) right = JS.N(0);
443                     int result = 0;
444                     if (left instanceof String || right instanceof String) {
445                         result = left.toString().compareTo(right.toString());
446                     } else {
447                         result = (int)java.lang.Math.ceil(JS.toDouble(left) - JS.toDouble(right));
448                     }
449                     stack.push(JS.B((op == LT && result < 0) || (op == LE && result <= 0) ||
450                                (op == GT && result > 0) || (op == GE && result >= 0)));
451                     break;
452                 }
453                     
454                 case EQ:
455                 case NE: {
456                     Object l = left;
457                     Object r = right;
458                     boolean ret;
459                     if (l == null) { Object tmp = r; r = l; l = tmp; }
460                     if (l == null && r == null) ret = true;
461                     else if (r == null) ret = false; // l != null, so its false
462                     else if (l instanceof Boolean) ret = JS.B(JS.toBoolean(r)).equals(l);
463                     else if (l instanceof Number) ret = JS.toNumber(r).doubleValue() == JS.toNumber(l).doubleValue();
464                     else if (l instanceof String) ret = r != null && l.equals(r.toString());
465                     else ret = l.equals(r);
466                     stack.push(JS.B(op == EQ ? ret : !ret)); break;
467                 }
468
469                 default: throw new Error("unknown opcode " + op);
470                 } }
471             }
472
473         } catch(JSExn e) {
474             if(f.op[pc] != FINALLY_DONE) e.addBacktrace(f.sourceName,f.line[pc]);
475             while(stack.size() > 0) {
476                 Object o = stack.pop();
477                 if (o instanceof CatchMarker || o instanceof TryMarker) {
478                     boolean inCatch = o instanceof CatchMarker;
479                     if(inCatch) {
480                         o = stack.pop();
481                         if(((TryMarker)o).finallyLoc < 0) continue; // no finally block, keep going
482                     }
483                     if(!inCatch && ((TryMarker)o).catchLoc >= 0) {
484                         // run the catch block, this will implicitly run the finally block, if it exists
485                         stack.push(o);
486                         stack.push(catchMarker);
487                         stack.push(e.getObject());
488                         f = ((TryMarker)o).f;
489                         scope = ((TryMarker)o).scope;
490                         pc = ((TryMarker)o).catchLoc - 1;
491                         continue OUTER;
492                     } else {
493                         stack.push(new FinallyData(e));
494                         f = ((TryMarker)o).f;
495                         scope = ((TryMarker)o).scope;
496                         pc = ((TryMarker)o).finallyLoc - 1;
497                         continue OUTER;
498                     }
499                 } else if(o instanceof CallMarker) {
500                     CallMarker cm = (CallMarker) o;
501                     if(cm.f == null)
502                         e.addBacktrace("<java>",0); // This might not even be worth mentioning
503                     else
504                         e.addBacktrace(cm.f.sourceName,cm.f.line[cm.pc-1]);
505                 }
506             }
507             throw e;
508         } // end try/catch
509         } // end for
510     }
511
512
513
514     // Markers //////////////////////////////////////////////////////////////////////
515
516     public static class CallMarker {
517         int pc;
518         JSScope scope;
519         JSFunction f;
520         public CallMarker(Interpreter cx) { pc = cx.pc + 1; scope = cx.scope; f = cx.f; }
521     }
522     
523     public static class CatchMarker { public CatchMarker() { } }
524     private static CatchMarker catchMarker = new CatchMarker();
525     
526     public static class LoopMarker {
527         public int location;
528         public String label;
529         public JSScope scope;
530         public LoopMarker(int location, String label, JSScope scope) {
531             this.location = location;
532             this.label = label;
533             this.scope = scope;
534         }
535     }
536     public static class TryMarker {
537         public int catchLoc;
538         public int finallyLoc;
539         public JSScope scope;
540         public JSFunction f;
541         public TryMarker(int catchLoc, int finallyLoc, Interpreter cx) {
542             this.catchLoc = catchLoc;
543             this.finallyLoc = finallyLoc;
544             this.scope = cx.scope;
545             this.f = cx.f;
546         }
547     }
548     public static class FinallyData {
549         public int op;
550         public Object arg;
551         public JSExn exn;
552         public FinallyData(int op) { this(op,null); }
553         public FinallyData(int op, Object arg) { this.op = op; this.arg = arg; }
554         public FinallyData(JSExn exn) { this.exn = exn; } // Just throw this exn
555     }
556
557
558     // Operations on Primitives //////////////////////////////////////////////////////////////////////
559
560     static Object callMethodOnPrimitive(Object o, Object method, Object arg0, Object arg1, Object arg2, Object[] rest, int alength) throws JSExn {
561         if (method == null || !(method instanceof String) || "".equals(method))
562             throw new JSExn("attempt to call a non-existant method on a primitive");
563
564         if (o instanceof Number) {
565             //#switch(method)
566             case "toFixed": throw new JSExn("toFixed() not implemented");
567             case "toExponential": throw new JSExn("toExponential() not implemented");
568             case "toPrecision": throw new JSExn("toPrecision() not implemented");
569             case "toString": {
570                 int radix = alength >= 1 ? JS.toInt(arg0) : 10;
571                 return Long.toString(((Number)o).longValue(),radix);
572             }
573             //#end
574         } else if (o instanceof Boolean) {
575             // No methods for Booleans
576             throw new JSExn("attempt to call a method on a Boolean");
577         }
578
579         String s = JS.toString(o);
580         int slength = s.length();
581         //#switch(method)
582         case "substring": {
583             int a = alength >= 1 ? JS.toInt(arg0) : 0;
584             int b = alength >= 2 ? JS.toInt(arg1) : slength;
585             if (a > slength) a = slength;
586             if (b > slength) b = slength;
587             if (a < 0) a = 0;
588             if (b < 0) b = 0;
589             if (a > b) { int tmp = a; a = b; b = tmp; }
590             return s.substring(a,b);
591         }
592         case "substr": {
593             int start = alength >= 1 ? JS.toInt(arg0) : 0;
594             int len = alength >= 2 ? JS.toInt(arg1) : Integer.MAX_VALUE;
595             if (start < 0) start = slength + start;
596             if (start < 0) start = 0;
597             if (len < 0) len = 0;
598             if (len > slength - start) len = slength - start;
599             if (len <= 0) return "";
600             return s.substring(start,start+len);
601         }
602         case "charAt": {
603             int p = alength >= 1 ? JS.toInt(arg0) : 0;
604             if (p < 0 || p >= slength) return "";
605             return s.substring(p,p+1);
606         }
607         case "charCodeAt": {
608             int p = alength >= 1 ? JS.toInt(arg0) : 0;
609             if (p < 0 || p >= slength) return JS.N(Double.NaN);
610             return JS.N(s.charAt(p));
611         }
612         case "concat": {
613             StringBuffer sb = new StringBuffer(slength*2).append(s);
614             for(int i=0;i<alength;i++) sb.append(i==0?arg0:i==1?arg1:i==2?arg2:rest[i-3]);
615             return sb.toString();
616         }
617         case "indexOf": {
618             String search = alength >= 1 ? arg0.toString() : "null";
619             int start = alength >= 2 ? JS.toInt(arg1) : 0;
620             // Java's indexOf handles an out of bounds start index, it'll return -1
621             return JS.N(s.indexOf(search,start));
622         }
623         case "lastIndexOf": {
624             String search = alength >= 1 ? arg0.toString() : "null";
625             int start = alength >= 2 ? JS.toInt(arg1) : 0;
626             // Java's indexOf handles an out of bounds start index, it'll return -1
627             return JS.N(s.lastIndexOf(search,start));            
628         }
629         case "match": return JSRegexp.stringMatch(s,arg0);
630         case "replace": return JSRegexp.stringReplace(s,arg0,arg1);
631         case "search": return JSRegexp.stringSearch(s,arg0);
632         case "split": return JSRegexp.stringSplit(s,arg0,arg1,alength);
633         case "toLowerCase": return s.toLowerCase();
634         case "toUpperCase": return s.toUpperCase();
635         case "toString": return s;
636         case "slice": {
637             int a = alength >= 1 ? JS.toInt(arg0) : 0;
638             int b = alength >= 2 ? JS.toInt(arg1) : slength;
639             if (a < 0) a = slength + a;
640             if (b < 0) b = slength + b;
641             if (a < 0) a = 0;
642             if (b < 0) b = 0;
643             if (a > slength) a = slength;
644             if (b > slength) b = slength;
645             if (a > b) return "";
646             return s.substring(a,b);
647         }
648         //#end
649         throw new JSExn("Attempted to call non-existent method: " + method);
650     }
651     
652     static Object getFromPrimitive(Object o, Object key) throws JSExn {
653         boolean returnJS = false;
654         if (o instanceof Boolean) {
655             throw new JSExn("cannot call methods on Booleans");
656         } else if (o instanceof Number) {
657             if (key.equals("toPrecision") || key.equals("toExponential") || key.equals("toFixed"))
658                 returnJS = true;
659         }
660         if (!returnJS) {
661             // the string stuff applies to everything
662             String s = o.toString();
663             
664             // this is sort of ugly, but this list should never change
665             // These should provide a complete (enough) implementation of the ECMA-262 String object
666
667             //#switch(key)
668             case "length": return JS.N(s.length());
669             case "substring": returnJS = true; break; 
670             case "charAt": returnJS = true; break; 
671             case "charCodeAt": returnJS = true; break; 
672             case "concat": returnJS = true; break; 
673             case "indexOf": returnJS = true; break; 
674             case "lastIndexOf": returnJS = true; break; 
675             case "match": returnJS = true; break; 
676             case "replace": returnJS = true; break; 
677             case "seatch": returnJS = true; break; 
678             case "slice": returnJS = true; break; 
679             case "split": returnJS = true; break; 
680             case "toLowerCase": returnJS = true; break; 
681             case "toUpperCase": returnJS = true; break; 
682             case "toString": returnJS = true; break; 
683             case "substr": returnJS = true; break;  
684            //#end
685         }
686         if (returnJS) {
687             final Object target = o;
688             final String method = key.toString();
689             return new JS() {
690                     public Object call(Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn {
691                         if (nargs > 2) throw new JSExn("cannot call that method with that many arguments");
692                         return callMethodOnPrimitive(target, method, a0, a1, a2, rest, nargs);
693                     }
694             };
695         }
696         return null;
697     }
698
699     private static class Stub extends JS {
700         private Object method;
701         JS obj;
702         public Stub(JS obj, Object method) { this.obj = obj; this.method = method; }
703         public Object call(Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn {
704             return ((JS)obj).callMethod(method, a0, a1, a2, rest, nargs);
705         }
706     }
707 }