2003/06/16 08:47:28
[org.ibex.core.git] / src / org / xwt / js / CompiledFunctionImpl.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.io.*;
6
7 // FIXME: could use some cleaning up
8 /** a JavaScript function, compiled into bytecode */
9 class CompiledFunctionImpl extends JS.Callable implements ByteCodes, Tokens {
10
11     // Fields and Accessors ///////////////////////////////////////////////
12
13     /** the source code file that this block was drawn from */
14     private String sourceName;
15     public String getSourceName() throws JS.Exn { return sourceName; }
16     
17     /** the line numbers */
18     private int[] line = new int[10];
19
20     /** the instructions */
21     private int[] op = new int[10];
22
23     /** the arguments to the instructions */
24     private Object[] arg = new Object[10];
25
26     /** the number of instruction/argument pairs */
27     private int size = 0;
28     int size() { return size; }
29
30     /** the scope in which this function was declared; by default this function is called in a fresh subscope of the parentScope */
31     private JS.Scope parentScope;
32
33
34     // Constructors ////////////////////////////////////////////////////////
35
36     private CompiledFunctionImpl cloneWithNewParentScope(JS.Scope s) throws IOException {
37         CompiledFunctionImpl ret = new JS.CompiledFunction(sourceName, firstLine, null, s);
38         ret.paste(this);
39         return ret;
40     }
41
42     protected CompiledFunctionImpl(String sourceName, int firstLine, Reader sourceCode, JS.Scope parentScope) throws IOException {
43         this.sourceName = sourceName;
44         this.firstLine = firstLine;
45         this.parentScope = parentScope;
46         if (sourceCode == null) return;
47         Parser p = new Parser(sourceCode, sourceName, firstLine);
48         try {
49             while(true) {
50                 int s = size();
51                 p.parseStatement(this, null);
52                 if (s == size()) break;
53             }
54             add(-1, Tokens.RETURN);
55         } catch (Exception e) {
56             if (Log.on) Log.log(Parser.class, e);
57         }
58     }
59     
60     public Object call(JS.Array args) throws JS.Exn { return call(args, new FunctionScope(sourceName, parentScope)); }
61     public Object call(JS.Array args, JS.Scope scope) throws JS.Exn {
62         JS.Thread cx = JS.Thread.fromJavaThread(java.lang.Thread.currentThread());
63         CompiledFunction saved = cx.currentCompiledFunction;
64         try {
65             cx.currentCompiledFunction = (CompiledFunction)this;
66             int size = cx.stack.size();
67             cx.stack.push(new CallMarker());
68             cx.stack.push(args);
69             eval(scope);
70             Object ret = cx.stack.pop();
71             if (cx.stack.size() > size) Log.logJS(this, "warning, stack grew by " + (cx.stack.size() - size) + " elements during call");
72             return ret;
73         } finally {
74             cx.currentCompiledFunction = saved;
75         }
76     }
77
78
79     // Adding and Altering Bytecodes ///////////////////////////////////////////////////
80
81     int get(int pos) { return op[pos]; }
82     void set(int pos, int op_, Object arg_) { op[pos] = op_; arg[pos] = arg_; }
83     void set(int pos, Object arg_) { arg[pos] = arg_; }
84     void paste(CompiledFunctionImpl other) { for(int i=0; i<other.size; i++) add(other.line[i], other.op[i], other.arg[i]); }
85     CompiledFunctionImpl add(int line, int op_) { return add(line, op_, null); }
86     CompiledFunctionImpl add(int line, int op_, Object arg_) {
87         if (size == op.length - 1) {
88             int[] line2 = new int[op.length * 2]; System.arraycopy(this.line, 0, line2, 0, op.length); this.line = line2;
89             Object[] arg2 = new Object[op.length * 2]; System.arraycopy(arg, 0, arg2, 0, arg.length); arg = arg2;
90             int[] op2 = new int[op.length * 2]; System.arraycopy(op, 0, op2, 0, op.length); op = op2;
91         }
92         this.line[size] = line;
93         op[size] = op_;
94         arg[size] = arg_;
95         size++;
96         return this;
97     }
98
99
100     // Invoking the Bytecode ///////////////////////////////////////////////////////
101         
102     int pc = 0;
103     void eval(JS.Scope s) {
104         final JS.Thread cx = JS.Thread.fromJavaThread(java.lang.Thread.currentThread());
105         final Vec t = cx.stack;
106         OUTER: for(pc=0; pc<size; pc++) {
107             String label = null;
108             cx.line = line[op];
109             switch(op[pc]) {
110             case LITERAL: t.push(arg[pc]); break;
111             case OBJECT: t.push(new JS.Obj()); break;
112             case ARRAY: t.push(new JS.Array(JS.toNumber(arg[pc]).intValue())); break;
113             case DECLARE: s.declare((String)t.pop()); break;
114             case TOPSCOPE: t.push(s); break;
115             case JT: if (JS.toBoolean(t.pop())) pc += JS.toNumber(arg[pc]).intValue() - 1; break;
116             case JF: if (!JS.toBoolean(t.pop())) pc += JS.toNumber(arg[pc]).intValue() - 1; break;
117             case JMP: pc += JS.toNumber(arg[pc]).intValue() - 1; break;
118             case POP: t.pop(); break;
119             case SWAP: { Object o1 = t.pop(); Object o2 = t.pop(); t.push(o1); t.push(o2); break; }
120             case DUP: t.push(t.peek()); break;
121             case NEWSCOPE: /*s = new JS.Scope(s);*/ break;
122             case OLDSCOPE: /*s = s.getParentScope();*/ break;
123             case ASSERT: if (!JS.toBoolean(t.pop())) throw je("assertion failed"); break;
124             case BITNOT: t.push(new Long(~JS.toLong(t.pop()))); break;
125             case BANG: t.push(new Boolean(!JS.toBoolean(t.pop()))); break;
126
127             case TYPEOF: {
128                 Object o = t.pop();
129                 if (o == null) t.push(null);
130                 else if (o instanceof org.xwt.Box) t.push("Box");
131                 else if (o instanceof JS) t.push("Object");
132                 else if (o instanceof String) t.push("String");
133                 else if (o instanceof Number) t.push("Number");
134                 else if (o.getClass().getName().startsWith("org.xwt.js.")) t.push(o.getClass().getName().substring(11));
135                 else t.push("unknown");
136                 break;
137             }
138
139             case NEWFUNCTION: {
140                 try {
141                     t.push(((CompiledFunctionImpl)arg[pc]).cloneWithNewParentScope(s));
142                 } catch (IOException e) {
143                     throw new Error("this should never happen");
144                 }
145                 break;
146             }
147
148             case PUSHKEYS: {
149                 Object o = t.peek();
150                 Object[] keys = ((JS)o).keys();
151                 JS.Array a = new JS.Array();
152                 a.setSize(keys.length);
153                 for(int j=0; j<keys.length; j++) a.setElementAt(keys[j], j);
154                 t.push(a);
155                 break;
156             }
157
158             case LABEL: break;
159             case LOOP: {
160                 t.push(s);
161                 t.push(new LoopMarker(pc, pc > 0 && op[pc - 1] == LABEL ? (String)arg[pc - 1] : (String)null));
162                 t.push(Boolean.TRUE);
163                 break;
164             }
165
166             case BREAK:
167             case CONTINUE:
168                 while(t.size() > 0) {
169                     Object o = t.pop();
170                     if (o instanceof CallMarker) ee("break or continue not within a loop");
171                     if (o != null && o instanceof LoopMarker) {
172                         if (arg[pc] == null || arg[pc].equals(((LoopMarker)o).label)) {
173                             int loopInstructionLocation = ((LoopMarker)o).location;
174                             int endOfLoop = ((Integer)arg[loopInstructionLocation]).intValue() + loopInstructionLocation;
175                             s = (JS.Scope)t.pop();
176                             if (op[pc] == CONTINUE) { t.push(s); t.push(o); t.push(Boolean.FALSE); }
177                             pc = op[pc] == BREAK ? endOfLoop - 1 : loopInstructionLocation;
178                             continue OUTER;
179                         }
180                     }
181                 }
182                 throw new Error("CONTINUE/BREAK invoked but couldn't find a LoopMarker at " + sourceName + ":" + line);
183
184             case TRY: {
185                 t.push(new TryMarker(pc + ((Integer)arg[pc]).intValue(), s));
186                 break;
187             }
188
189             case RETURN: {
190                 Object retval = t.pop();
191                 while(t.size() > 0) {
192                     Object o = t.pop();
193                     if (o != null && o instanceof CallMarker) {
194                         t.push(retval);
195                         return;
196                     }
197                 }
198                 throw new Error("error: RETURN invoked but couldn't find a CallMarker!");
199             }
200
201             case PUT: {
202                 Object val = t.pop();
203                 Object key = t.pop();
204                 Object target = t.peek();
205                 if (target == null)
206                     throw je("tried to put a value to the " + key + " property on the null value");
207                 if (!(target instanceof JS))
208                     throw je("tried to put a value to the " + key + " property on a " + target.getClass().getName());
209                 ((JS)target).put(key, val);
210                 t.push(val);
211                 break;
212             }
213
214             case GET:
215             case GET_PRESERVE: {
216                 Object o, v;
217                 if (op[pc] == GET) {
218                     v = t.pop();
219                     o = t.pop();
220                 } else {
221                     v = t.pop();
222                     o = t.peek();
223                     t.push(v);
224                 }
225                 Object ret = null;
226                 if (o == null) throw je("tried to get property \"" + v + "\" from the null value");
227                 if (v == null) throw je("tried to get the null key from " + v);
228                 if (o instanceof String) {
229                     ret = getFromString((String)o, v);
230                 } else if (o instanceof Boolean) {
231                     throw je("Not Implemented: properties on Boolean objects");
232                 } else if (o instanceof Number) {
233                     Log.log(this, "Not Implemented: properties on Number objects");
234                 } else if (o instanceof JS) {
235                     ret = ((JS)o).get(v);
236                 }
237                 t.push(ret);
238                 break;
239             }
240                     
241             case CALL: {
242                 JS.Array arguments = new JS.Array();
243                 int numArgs = JS.toNumber(arg[pc]).intValue();
244                 arguments.setSize(numArgs);
245                 for(int j=numArgs - 1; j >= 0; j--) arguments.setElementAt(t.pop(), j);
246                 JS.Callable f = (JS.Callable)t.pop();
247                 if (f == null) throw je("attempted to call null");
248                 try {
249                     t.push(f.call(arguments));
250                     break;
251                 } catch (JS.Exn e) {
252                     t.push(e);
253                 }
254             }
255             // fall through if exception was thrown
256             case THROW: {
257                 Object exn = t.pop();
258                 while(t.size() > 0) {
259                     Object o = t.pop();
260                     if (o instanceof TryMarker) {
261                         t.push(exn);
262                         pc = ((TryMarker)o).location - 1;
263                         s = ((TryMarker)o).scope;
264                         continue OUTER;
265                     }
266                 }
267                 throw new JS.Exn(exn);
268             }
269
270             case INC: case DEC: {
271                 boolean isPrefix = JS.toBoolean(arg[pc]);
272                 Object key = t.pop();
273                 JS obj = (JS)t.pop();
274                 Number num = JS.toNumber(obj.get(key));
275                 Number val = new Double(op[pc] == INC ? num.doubleValue() + 1.0 : num.doubleValue() - 1.0);
276                 obj.put(key, val);
277                 t.push(isPrefix ? val : num);
278                 break;
279             }
280
281             default: {
282                 Object right = t.pop();
283                 Object left = t.pop();
284                 switch(op[pc]) {
285
286                 case ADD: {
287                     Object l = left;
288                     Object r = right;
289                     if (l instanceof String || r instanceof String) {
290                         if (l == null) l = "null";
291                         if (r == null) r = "null";
292                         if (l instanceof Number && ((Number)l).doubleValue() == ((Number)l).longValue())
293                             l = new Long(((Number)l).longValue());
294                         if (r instanceof Number && ((Number)r).doubleValue() == ((Number)r).longValue())
295                             r = new Long(((Number)r).longValue());
296                         t.push(l.toString() + r.toString()); break;
297                     }
298                     t.push(new Double(JS.toDouble(l) + JS.toDouble(r))); break;
299                 }
300                         
301                 case BITOR: t.push(new Long(JS.toLong(left) | JS.toLong(right))); break;
302                 case BITXOR: t.push(new Long(JS.toLong(left) ^ JS.toLong(right))); break;
303                 case BITAND: t.push(new Long(JS.toLong(left) & JS.toLong(right))); break;
304
305                 case SUB: t.push(new Double(JS.toDouble(left) - JS.toDouble(right))); break;
306                 case MUL: t.push(new Double(JS.toDouble(left) * JS.toDouble(right))); break;
307                 case DIV: t.push(new Double(JS.toDouble(left) / JS.toDouble(right))); break;
308                 case MOD: t.push(new Double(JS.toDouble(left) % JS.toDouble(right))); break;
309                         
310                 case LSH: t.push(new Long(JS.toLong(left) << JS.toLong(right))); break;
311                 case RSH: t.push(new Long(JS.toLong(left) >> JS.toLong(right))); break;
312                 case URSH: t.push(new Long(JS.toLong(left) >>> JS.toLong(right))); break;
313                         
314                 case LT: case LE: case GT: case GE: {
315                     if (left == null) left = new Integer(0);
316                     if (right == null) right = new Integer(0);
317                     int result = 0;
318                     if (left instanceof String || right instanceof String) {
319                         result = left.toString().compareTo(right.toString());
320                     } else {
321                         result = (int)java.lang.Math.ceil(JS.toDouble(left) - JS.toDouble(right));
322                     }
323                     t.push(new Boolean((op[pc] == LT && result < 0) || (op[pc] == LE && result <= 0) ||
324                                        (op[pc] == GT && result > 0) || (op[pc] == GE && result >= 0)));
325                     break;
326                 }
327                     
328                 case EQ:
329                 case NE: {
330                     Object l = left;
331                     Object r = right;
332                     boolean ret;
333                     if (l == null) { Object tmp = r; r = l; l = tmp; }
334                     if (l == null && r == null) ret = true;
335                     else if (l instanceof Boolean) ret = new Boolean(JS.toBoolean(r)).equals(l);
336                     else if (l instanceof Number) ret = JS.toNumber(r).doubleValue() == JS.toNumber(l).doubleValue();
337                     else if (l instanceof String) ret = r != null && l.equals(r.toString());
338                     else ret = l.equals(r);
339                     t.push(new Boolean(op[pc] == EQ ? ret : !ret)); break;
340                 }
341
342                 default: throw new Error("unknown opcode " + op[pc]);
343                 } }
344             }
345         }
346     }
347
348
349     // Helpers for Number, String, and Boolean ////////////////////////////////////////
350
351     private Object getFromString(final String o, final Object v) {
352         if (v.equals("length")) return new Integer(((String)o).length());
353         else if (v.equals("substring")) return new JS.Callable() {
354                 public Object call(JS.Array args) {
355                     if (args.length() == 1) return ((String)o).substring(JS.toNumber(args.elementAt(0)).intValue());
356                     else if (args.length() == 2) return ((String)o).substring(JS.toNumber(args.elementAt(0)).intValue(),
357                                                                               JS.toNumber(args.elementAt(1)).intValue());
358                     else throw je("String.substring() can only take one or two arguments");
359                 }
360             };
361         else if (v.equals("toLowerCase")) return new JS.Callable() {
362                 public Object call(JS.Array args) {
363                     return ((String)o).toLowerCase();
364                 } };
365         else if (v.equals("toUpperCase")) return new JS.Callable() {
366                 public Object call(JS.Array args) {
367                     return ((String)o).toString().toUpperCase();
368                 } };
369         else if (v.equals("charAt")) return new JS.Callable() {
370                 public Object call(JS.Array args) {
371                     return ((String)o).charAt(JS.toNumber(args.elementAt(0)).intValue()) + "";
372                 } };
373         else if (v.equals("lastIndexOf")) return new JS.Callable() {
374                 public Object call(JS.Array args) {
375                     if (args.length() != 1) return null;
376                     return new Integer(((String)o).lastIndexOf(args.elementAt(0).toString()));
377                 } };
378         else if (v.equals("indexOf")) return new JS.Callable() {
379                 public Object call(JS.Array args) {
380                     if (args.length() != 1) return null;
381                     return new Integer(((String)o).indexOf(args.elementAt(0).toString()));
382                 } };
383         throw je("Not Implemented: propery " + v + " on String objects");
384     }
385
386
387     // Exception Stuff ////////////////////////////////////////////////////////////////
388
389     static class EvaluatorException extends RuntimeException { public EvaluatorException(String s) { super(s); } }
390     EvaluatorException ee(String s) { throw new EvaluatorException(sourceName + ":" + line[pc] + " " + s); }
391     JS.Exn je(String s) { throw new JS.Exn(sourceName + ":" + line[pc] + " " + s); }
392
393
394     // FunctionScope /////////////////////////////////////////////////////////////////
395
396     private class FunctionScope extends JS.Scope {
397         String sourceName;
398         public FunctionScope(String sourceName, Scope parentScope) { super(parentScope); this.sourceName = sourceName; }
399         public String getSourceName() { return sourceName; }
400         public Object get(Object key) throws JS.Exn {
401             if (key.equals("trapee")) return org.xwt.Trap.currentTrapee();
402             else if (key.equals("cascade")) return org.xwt.Trap.cascadeFunction;
403             return super.get(key);
404         }
405     }
406
407
408     // Markers //////////////////////////////////////////////////////////////////////
409
410     public static class CallMarker { public CallMarker() { } }
411     public static class LoopMarker {
412         public int location;
413         public String label;
414         public LoopMarker(int location, String label) {
415             this.location = location;
416             this.label = label;
417         }
418     }
419     public static class TryMarker {
420         public int location;
421         public JS.Scope scope;
422         public TryMarker(int location, JS.Scope scope) {
423             this.location = location;
424             this.scope = scope;
425         }
426     }
427
428 }