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