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