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