2004/01/08 05:02:19
[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 MAKE_GRAMMAR: {
360                 final Grammar r = (Grammar)arg;
361                 final JSScope final_scope = scope;
362                 Grammar r2 = new Grammar() {
363                         public int match(String s, int start, Hash v, JSScope scope) throws JSExn {
364                             return r.match(s, start, v, final_scope);
365                         }
366                         public int matchAndWrite(String s, int start, Hash v, JSScope scope, String key) throws JSExn {
367                             return r.matchAndWrite(s, start, v, final_scope, key);
368                         }
369                         public Object call(Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn {
370                             Hash v = new Hash();
371                             r.matchAndWrite((String)a0, 0, v, final_scope, "foo");
372                             return v.get("foo");
373                         }
374                     };
375                 Object obj = stack.pop();
376                 if (obj != null && obj instanceof Grammar) r2 = new Grammar.Alternative((Grammar)obj, r2);
377                 stack.push(r2);
378                 break;
379             }
380
381             case ASSIGN_SUB: case ASSIGN_ADD: {
382                 Object val = stack.pop();
383                 Object key = stack.pop();
384                 Object obj = stack.peek();
385                 if (val instanceof JSFunction && obj instanceof JS) {
386                     // A trap addition/removal
387                     JS js = obj instanceof JSScope ? ((JSScope)obj).top() : (JS) obj;
388                     if(op == ASSIGN_ADD) js.addTrap(key, (JSFunction)val);
389                     else js.delTrap(key, (JSFunction)val);
390                     pc += ((Integer)arg).intValue() - 1;
391                 } else {
392                     // The following setup is VERY important. The generated bytecode depends on the stack
393                     // being setup like this (top to bottom) KEY, OBJ, VAL, KEY, OBJ
394                     stack.push(key);
395                     stack.push(val);
396                     stack.push(obj);
397                     stack.push(key);
398                 }
399                 break;
400             }
401
402             case ADD: {
403                 int count = ((Number)arg).intValue();
404                 if(count < 2) throw new Error("this should never happen");
405                 if(count == 2) {
406                     // common case
407                     Object right = stack.pop();
408                     Object left = stack.pop();
409                     if(left instanceof String || right instanceof String)
410                         stack.push(JS.toString(left).concat(JS.toString(right)));
411                     else stack.push(JS.N(JS.toDouble(left) + JS.toDouble(right)));
412                 } else {
413                     Object[] args = new Object[count];
414                     while(--count >= 0) args[count] = stack.pop();
415                     if(args[0] instanceof String) {
416                         StringBuffer sb = new StringBuffer(64);
417                         for(int i=0;i<args.length;i++) sb.append(JS.toString(args[i]));
418                         stack.push(sb.toString());
419                     } else {
420                         int numStrings = 0;
421                         for(int i=0;i<args.length;i++) if(args[i] instanceof String) numStrings++;
422                         if(numStrings == 0) {
423                             double d = 0.0;
424                             for(int i=0;i<args.length;i++) d += JS.toDouble(args[i]);
425                             stack.push(JS.N(d));
426                         } else {
427                             int i=0;
428                             StringBuffer sb = new StringBuffer(64);
429                             if(!(args[0] instanceof String || args[1] instanceof String)) {
430                                 double d=0.0;
431                                 do {
432                                     d += JS.toDouble(args[i++]);
433                                 } while(!(args[i] instanceof String));
434                                 sb.append(JS.toString(JS.N(d)));
435                             }
436                             while(i < args.length) sb.append(JS.toString(args[i++]));
437                             stack.push(sb.toString());
438                         }
439                     }
440                 }
441                 break;
442             }
443
444             default: {
445                 Object right = stack.pop();
446                 Object left = stack.pop();
447                 switch(op) {
448                         
449                 case BITOR: stack.push(JS.N(JS.toLong(left) | JS.toLong(right))); break;
450                 case BITXOR: stack.push(JS.N(JS.toLong(left) ^ JS.toLong(right))); break;
451                 case BITAND: stack.push(JS.N(JS.toLong(left) & JS.toLong(right))); break;
452
453                 case SUB: stack.push(JS.N(JS.toDouble(left) - JS.toDouble(right))); break;
454                 case MUL: stack.push(JS.N(JS.toDouble(left) * JS.toDouble(right))); break;
455                 case DIV: stack.push(JS.N(JS.toDouble(left) / JS.toDouble(right))); break;
456                 case MOD: stack.push(JS.N(JS.toDouble(left) % JS.toDouble(right))); break;
457                         
458                 case LSH: stack.push(JS.N(JS.toLong(left) << JS.toLong(right))); break;
459                 case RSH: stack.push(JS.N(JS.toLong(left) >> JS.toLong(right))); break;
460                 case URSH: stack.push(JS.N(JS.toLong(left) >>> JS.toLong(right))); break;
461                         
462                 case LT: case LE: case GT: case GE: {
463                     if (left == null) left = JS.N(0);
464                     if (right == null) right = JS.N(0);
465                     int result = 0;
466                     if (left instanceof String || right instanceof String) {
467                         result = left.toString().compareTo(right.toString());
468                     } else {
469                         result = (int)java.lang.Math.ceil(JS.toDouble(left) - JS.toDouble(right));
470                     }
471                     stack.push(JS.B((op == LT && result < 0) || (op == LE && result <= 0) ||
472                                (op == GT && result > 0) || (op == GE && result >= 0)));
473                     break;
474                 }
475                     
476                 case EQ:
477                 case NE: {
478                     Object l = left;
479                     Object r = right;
480                     boolean ret;
481                     if (l == null) { Object tmp = r; r = l; l = tmp; }
482                     if (l == null && r == null) ret = true;
483                     else if (r == null) ret = false; // l != null, so its false
484                     else if (l instanceof Boolean) ret = JS.B(JS.toBoolean(r)).equals(l);
485                     else if (l instanceof Number) ret = JS.toNumber(r).doubleValue() == JS.toNumber(l).doubleValue();
486                     else if (l instanceof String) ret = r != null && l.equals(r.toString());
487                     else ret = l.equals(r);
488                     stack.push(JS.B(op == EQ ? ret : !ret)); break;
489                 }
490
491                 default: throw new Error("unknown opcode " + op);
492                 } }
493             }
494
495         } catch(JSExn e) {
496             if(f.op[pc] != FINALLY_DONE) e.addBacktrace(f.sourceName,f.line[pc]);
497             while(stack.size() > 0) {
498                 Object o = stack.pop();
499                 if (o instanceof CatchMarker || o instanceof TryMarker) {
500                     boolean inCatch = o instanceof CatchMarker;
501                     if(inCatch) {
502                         o = stack.pop();
503                         if(((TryMarker)o).finallyLoc < 0) continue; // no finally block, keep going
504                     }
505                     if(!inCatch && ((TryMarker)o).catchLoc >= 0) {
506                         // run the catch block, this will implicitly run the finally block, if it exists
507                         stack.push(o);
508                         stack.push(catchMarker);
509                         stack.push(e.getObject());
510                         f = ((TryMarker)o).f;
511                         scope = ((TryMarker)o).scope;
512                         pc = ((TryMarker)o).catchLoc - 1;
513                         continue OUTER;
514                     } else {
515                         stack.push(new FinallyData(e));
516                         f = ((TryMarker)o).f;
517                         scope = ((TryMarker)o).scope;
518                         pc = ((TryMarker)o).finallyLoc - 1;
519                         continue OUTER;
520                     }
521                 } else if(o instanceof CallMarker) {
522                     CallMarker cm = (CallMarker) o;
523                     if(cm.f == null)
524                         e.addBacktrace("<java>",0); // This might not even be worth mentioning
525                     else
526                         e.addBacktrace(cm.f.sourceName,cm.f.line[cm.pc-1]);
527                 }
528             }
529             throw e;
530         } // end try/catch
531         } // end for
532     }
533
534
535
536     // Markers //////////////////////////////////////////////////////////////////////
537
538     public static class CallMarker {
539         int pc;
540         JSScope scope;
541         JSFunction f;
542         public CallMarker(Interpreter cx) { pc = cx.pc + 1; scope = cx.scope; f = cx.f; }
543     }
544     
545     public static class CatchMarker { public CatchMarker() { } }
546     private static CatchMarker catchMarker = new CatchMarker();
547     
548     public static class LoopMarker {
549         public int location;
550         public String label;
551         public JSScope scope;
552         public LoopMarker(int location, String label, JSScope scope) {
553             this.location = location;
554             this.label = label;
555             this.scope = scope;
556         }
557     }
558     public static class TryMarker {
559         public int catchLoc;
560         public int finallyLoc;
561         public JSScope scope;
562         public JSFunction f;
563         public TryMarker(int catchLoc, int finallyLoc, Interpreter cx) {
564             this.catchLoc = catchLoc;
565             this.finallyLoc = finallyLoc;
566             this.scope = cx.scope;
567             this.f = cx.f;
568         }
569     }
570     public static class FinallyData {
571         public int op;
572         public Object arg;
573         public JSExn exn;
574         public FinallyData(int op) { this(op,null); }
575         public FinallyData(int op, Object arg) { this.op = op; this.arg = arg; }
576         public FinallyData(JSExn exn) { this.exn = exn; } // Just throw this exn
577     }
578
579
580     // Operations on Primitives //////////////////////////////////////////////////////////////////////
581
582     static Object callMethodOnPrimitive(Object o, Object method, Object arg0, Object arg1, Object arg2, Object[] rest, int alength) throws JSExn {
583         if (method == null || !(method instanceof String) || "".equals(method))
584             throw new JSExn("attempt to call a non-existant method on a primitive");
585
586         if (o instanceof Number) {
587             //#switch(method)
588             case "toFixed": throw new JSExn("toFixed() not implemented");
589             case "toExponential": throw new JSExn("toExponential() not implemented");
590             case "toPrecision": throw new JSExn("toPrecision() not implemented");
591             case "toString": {
592                 int radix = alength >= 1 ? JS.toInt(arg0) : 10;
593                 return Long.toString(((Number)o).longValue(),radix);
594             }
595             //#end
596         } else if (o instanceof Boolean) {
597             // No methods for Booleans
598             throw new JSExn("attempt to call a method on a Boolean");
599         }
600
601         String s = JS.toString(o);
602         int slength = s.length();
603         //#switch(method)
604         case "substring": {
605             int a = alength >= 1 ? JS.toInt(arg0) : 0;
606             int b = alength >= 2 ? JS.toInt(arg1) : slength;
607             if (a > slength) a = slength;
608             if (b > slength) b = slength;
609             if (a < 0) a = 0;
610             if (b < 0) b = 0;
611             if (a > b) { int tmp = a; a = b; b = tmp; }
612             return s.substring(a,b);
613         }
614         case "substr": {
615             int start = alength >= 1 ? JS.toInt(arg0) : 0;
616             int len = alength >= 2 ? JS.toInt(arg1) : Integer.MAX_VALUE;
617             if (start < 0) start = slength + start;
618             if (start < 0) start = 0;
619             if (len < 0) len = 0;
620             if (len > slength - start) len = slength - start;
621             if (len <= 0) return "";
622             return s.substring(start,start+len);
623         }
624         case "charAt": {
625             int p = alength >= 1 ? JS.toInt(arg0) : 0;
626             if (p < 0 || p >= slength) return "";
627             return s.substring(p,p+1);
628         }
629         case "charCodeAt": {
630             int p = alength >= 1 ? JS.toInt(arg0) : 0;
631             if (p < 0 || p >= slength) return JS.N(Double.NaN);
632             return JS.N(s.charAt(p));
633         }
634         case "concat": {
635             StringBuffer sb = new StringBuffer(slength*2).append(s);
636             for(int i=0;i<alength;i++) sb.append(i==0?arg0:i==1?arg1:i==2?arg2:rest[i-3]);
637             return sb.toString();
638         }
639         case "indexOf": {
640             String search = alength >= 1 ? arg0.toString() : "null";
641             int start = alength >= 2 ? JS.toInt(arg1) : 0;
642             // Java's indexOf handles an out of bounds start index, it'll return -1
643             return JS.N(s.indexOf(search,start));
644         }
645         case "lastIndexOf": {
646             String search = alength >= 1 ? arg0.toString() : "null";
647             int start = alength >= 2 ? JS.toInt(arg1) : 0;
648             // Java's indexOf handles an out of bounds start index, it'll return -1
649             return JS.N(s.lastIndexOf(search,start));            
650         }
651         case "match": return JSRegexp.stringMatch(s,arg0);
652         case "replace": return JSRegexp.stringReplace(s,arg0,arg1);
653         case "search": return JSRegexp.stringSearch(s,arg0);
654         case "split": return JSRegexp.stringSplit(s,arg0,arg1,alength);
655         case "toLowerCase": return s.toLowerCase();
656         case "toUpperCase": return s.toUpperCase();
657         case "toString": return s;
658         case "slice": {
659             int a = alength >= 1 ? JS.toInt(arg0) : 0;
660             int b = alength >= 2 ? JS.toInt(arg1) : slength;
661             if (a < 0) a = slength + a;
662             if (b < 0) b = slength + b;
663             if (a < 0) a = 0;
664             if (b < 0) b = 0;
665             if (a > slength) a = slength;
666             if (b > slength) b = slength;
667             if (a > b) return "";
668             return s.substring(a,b);
669         }
670         //#end
671         throw new JSExn("Attempted to call non-existent method: " + method);
672     }
673     
674     static Object getFromPrimitive(Object o, Object key) throws JSExn {
675         boolean returnJS = false;
676         if (o instanceof Boolean) {
677             throw new JSExn("cannot call methods on Booleans");
678         } else if (o instanceof Number) {
679             if (key.equals("toPrecision") || key.equals("toExponential") || key.equals("toFixed"))
680                 returnJS = true;
681         }
682         if (!returnJS) {
683             // the string stuff applies to everything
684             String s = o.toString();
685             
686             // this is sort of ugly, but this list should never change
687             // These should provide a complete (enough) implementation of the ECMA-262 String object
688
689             //#switch(key)
690             case "length": return JS.N(s.length());
691             case "substring": returnJS = true; break; 
692             case "charAt": returnJS = true; break; 
693             case "charCodeAt": returnJS = true; break; 
694             case "concat": returnJS = true; break; 
695             case "indexOf": returnJS = true; break; 
696             case "lastIndexOf": returnJS = true; break; 
697             case "match": returnJS = true; break; 
698             case "replace": returnJS = true; break; 
699             case "seatch": returnJS = true; break; 
700             case "slice": returnJS = true; break; 
701             case "split": returnJS = true; break; 
702             case "toLowerCase": returnJS = true; break; 
703             case "toUpperCase": returnJS = true; break; 
704             case "toString": returnJS = true; break; 
705             case "substr": returnJS = true; break;  
706            //#end
707         }
708         if (returnJS) {
709             final Object target = o;
710             final String method = key.toString();
711             return new JS() {
712                     public Object call(Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn {
713                         if (nargs > 2) throw new JSExn("cannot call that method with that many arguments");
714                         return callMethodOnPrimitive(target, method, a0, a1, a2, rest, nargs);
715                     }
716             };
717         }
718         return null;
719     }
720
721     private static class Stub extends JS {
722         private Object method;
723         JS obj;
724         public Stub(JS obj, Object method) { this.obj = obj; this.method = method; }
725         public Object call(Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn {
726             return ((JS)obj).callMethod(method, a0, a1, a2, rest, nargs);
727         }
728     }
729 }