2003/06/05 00:12:17
[org.ibex.core.git] / src / org / xwt / js / ForthBlock.java
1 // Copyright 2002 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 block of Forth bytecode */
8 class ForthBlock implements OpCodes, Tokens {
9
10     int line;
11     String sourceName;
12     int[] op = new int[10];
13     Object[] arg = new Object[10];
14     int size = 0;
15
16     public ForthBlock(int line, String sourceName) { this.line = line; this.sourceName = sourceName; }
17     public ForthBlock(int line, String sourceName, int op_, Object arg_) { this(line, sourceName); add(op_, arg_); }
18
19     public int size() { return size; }
20     public void set(int pos, int op_, Object arg_) { op[pos] = op_; arg[pos] = arg_; }
21     public ForthBlock add(int op_) { return add(op_, null); }
22     public ForthBlock add(int op_, Object arg_) {
23         if (size == op.length - 1) {
24             int[] op2 = new int[op.length * 2]; System.arraycopy(op, 0, op2, 0, op.length); op = op2;
25             Object[] arg2 = new Object[op.length * 2]; System.arraycopy(arg, 0, arg2, 0, arg.length); arg = arg2;
26         }
27         op[size] = op_;
28         arg[size] = arg_;
29         size++;
30         return this;
31     }
32         
33     public Object eval(final JS.Scope s) throws ControlTransferException, JS.Exn { return eval(s, new Stack()); }
34     public Object eval(final JS.Scope s, Stack t) throws ControlTransferException {
35         for(int i=0; i<size; i++)
36             switch(op[i]) {
37             case LABEL: break; // FIXME
38             case LITERAL: t.push(arg[i]); break;
39             case OBJECT: t.push(new JS.Obj()); break;
40             case ARRAY: t.push(new JS.Array(JS.toNumber(arg[i]).intValue())); break;
41             case DECLARE: s.declare((String)t.pop()); break;
42             case OpCodes.THIS: t.push(s); break;   // FIXME: transparents
43             case JT: if (JS.toBoolean(t.pop())) i += JS.toNumber(arg[i]).intValue() - 1; break;
44             case JF: if (!JS.toBoolean(t.pop())) i += JS.toNumber(arg[i]).intValue() - 1; break;
45             case JMP: i += JS.toNumber(arg[i]).intValue() - 1; break;
46             case POP: t.pop(); break;
47             case SWAP: t.swap(); break;
48             case DUP: t.push(t.peek()); break;
49             case NOP: break;
50             case EXPR: t.push(((ForthBlock)arg[i]).eval(s)); break;
51             case SCOPE: t.push(((ForthBlock)arg[i]).eval(new JS.Scope(s), t)); break;
52
53             case ASSERT: if (!JS.toBoolean(t.pop())) throw new EvaluatorException(line, sourceName, "assertion failed"); break;
54             case RETURN: throw new ReturnException(t.pop());
55             case THROW: throw new JS.Exn(t.pop());
56
57             case TRY: break;
58             case INSTANCEOF: break;
59             case TYPEOF: break;
60             case PUSHKEYS: {
61                 Object o = t.peek();
62                 Object[] keys = ((JS)o).keys();
63                 JS.Array a = new JS.Array();
64                 a.setSize(keys.length);
65                 for(int j=0; j<keys.length; j++) a.setElementAt(keys[j], j);
66                 t.push(a);
67                 break;
68             }
69
70             case Lexer.BITNOT: t.push(new Long(~JS.toLong(t.pop()))); break;
71             case Lexer.BANG: t.push(new Boolean(!JS.toBoolean(t.pop()))); break;
72                     
73             case Lexer.BREAK: {
74                 // FIXME: make sure this can only appear in proper places
75                 return Boolean.FALSE;
76             }
77             case Lexer.CONTINUE: {
78                 // FIXME: make sure this can only appear in proper places
79                 return Boolean.TRUE;
80             }
81             case LOOP: {
82                 ForthBlock loop = (ForthBlock)arg[i];
83                 Stack t2 = new Stack();
84                 t2.push(Boolean.TRUE);
85                 while (true) {
86                     Boolean result;
87                     try {
88                         result = (Boolean)loop.eval(new JS.Scope(s), t2);
89                     } catch (ContinueException c) {
90                         result = Boolean.TRUE;
91                     } catch (BreakException b) {
92                         result = Boolean.FALSE;
93                     }
94                     if (result == Boolean.FALSE) break;
95                     t2 = new Stack();
96                     t2.push(Boolean.FALSE);
97                 }
98                 break;
99             }
100
101             case PUT: {
102                 Object val = t.pop();
103                 Object key = t.pop();
104                 ((JS)t.peek()).put(key, val);
105                 t.push(val);
106                 break;
107             }
108
109             case GET: {
110                 Object v = t.pop();
111                 Object o = t.pop();
112                 t.push(doGet(o, v));
113                 break;
114             }
115
116             case GET_PRESERVE: {
117                 Object v = t.pop();
118                 Object o = t.peek();
119                 t.push(v);
120                 t.push(doGet(o, v));
121                 break;
122             }
123                     
124             case CALL: {
125                 JS.Array arguments = new JS.Array();
126                 int numArgs = JS.toNumber(arg[i]).intValue();
127                 arguments.setSize(numArgs);
128                 for(int j=numArgs - 1; j >= 0; j--) arguments.setElementAt(t.pop(), j);
129                 JS.Function f = (JS.Function)t.pop();
130                 if (f == null) throw new JS.Exn(new EvaluatorException(line, sourceName, "attempted to call null"));
131                 t.push(f.call(arguments));
132                 break;
133             }
134
135             case OpCodes.FUNCTION: {
136                 final ForthBlock myBytes = (ForthBlock)arg[i];
137                 t.push(new JS.Function() {
138                         public String toString() { return sourceName + ":" + line; }
139                         public String getSourceName() throws JS.Exn { return sourceName; }
140                         public Object _call(final JS.Array args) throws JS.Exn {
141                             Function save = JS.getCurrentFunction();
142                             JS.currentFunction.put(java.lang.Thread.currentThread(), this);
143                             JS.Scope scope = new JS.Scope(s) {
144                                     // FIXME
145                                     public String getSourceName() { return sourceName; }
146                                     public Object get(Object key) throws JS.Exn {
147                                         if (key.equals("trapee")) return org.xwt.Trap.currentTrapee();
148                                         else if (key.equals("cascade")) return org.xwt.Trap.cascadeFunction;
149                                         return super.get(key);
150                                     }
151                                 };
152                             Stack t0 = new Stack();
153                             t0.push(args);
154                             try {
155                                 return myBytes.eval(scope, t0);
156                             } catch (ReturnException r) {
157                                 return r.retval;
158                             } catch (ControlTransferException c) {
159                                 throw new EvaluatorException(line, sourceName, "error, ControlTransferException tried to leave a function: " + c);
160                             } finally {
161                                 if (save == null) JS.currentFunction.remove(java.lang.Thread.currentThread());
162                                 else JS.currentFunction.put(java.lang.Thread.currentThread(), save);
163                             }
164                         }
165                     });
166                 break;
167             }
168
169             case Lexer.INC: case Lexer.DEC: {
170                 boolean isPrefix = JS.toBoolean(arg[i]);
171                 Object key = t.pop();
172                 JS obj = (JS)t.pop();
173                 Number num = JS.toNumber(obj.get(key));
174                 Number val = new Double(op[i] == Lexer.INC ? num.doubleValue() + 1.0 : num.doubleValue() - 1.0);
175                 obj.put(key, val);
176                 t.push(isPrefix ? val : num);
177                 break;
178             }
179
180             default: {
181                 Object right = t.pop();
182                 Object left = t.pop();
183                 switch(op[i]) {
184
185                 case Lexer.BITOR: t.push(new Long(JS.toLong(left) | JS.toLong(right))); break;
186                 case Lexer.BITXOR: t.push(new Long(JS.toLong(left) ^ JS.toLong(right))); break;
187                 case Lexer.BITAND: t.push(new Long(JS.toLong(left) & JS.toLong(right))); break;
188
189                 case Lexer.ADD: {
190                     Object l = left;
191                     Object r = right;
192                     if (l instanceof String || r instanceof String) {
193                         if (l == null) l = "null";
194                         if (r == null) r = "null";
195                         if (l instanceof Number && ((Number)l).doubleValue() == ((Number)l).longValue())
196                             l = new Long(((Number)l).longValue());
197                         if (r instanceof Number && ((Number)r).doubleValue() == ((Number)r).longValue())
198                             r = new Long(((Number)r).longValue());
199                         t.push(l.toString() + r.toString()); break;
200                     }
201                     t.push(new Double(JS.toDouble(l) + JS.toDouble(r))); break;
202                 }
203                         
204                 case Lexer.SUB: t.push(new Double(JS.toDouble(left) - JS.toDouble(right))); break;
205                 case Lexer.MUL: t.push(new Double(JS.toDouble(left) * JS.toDouble(right))); break;
206                 case Lexer.DIV: t.push(new Double(JS.toDouble(left) / JS.toDouble(right))); break;
207                 case Lexer.MOD: t.push(new Double(JS.toDouble(left) % JS.toDouble(right))); break;
208                         
209                 case Lexer.LSH: t.push(new Long(JS.toLong(left) << JS.toLong(right))); break;
210                 case Lexer.RSH: t.push(new Long(JS.toLong(left) >> JS.toLong(right))); break;
211                 case Lexer.URSH: t.push(new Long(JS.toLong(left) >>> JS.toLong(right))); break;
212                         
213                     // FIXME: these need to work on strings
214                 case Lexer.LT: t.push(JS.toDouble(left) < JS.toDouble(right) ? Boolean.TRUE : Boolean.FALSE); break;
215                 case Lexer.LE: t.push(JS.toDouble(left) <= JS.toDouble(right) ? Boolean.TRUE : Boolean.FALSE); break;
216                 case Lexer.GT: t.push(JS.toDouble(left) > JS.toDouble(right) ? Boolean.TRUE : Boolean.FALSE); break;
217                 case Lexer.GE: t.push(JS.toDouble(left) >= JS.toDouble(right) ? Boolean.TRUE : Boolean.FALSE); break;
218                     
219                 case Lexer.EQ:
220                 case Lexer.NE: {
221                     // FIXME: should use Javascript coercion-equality rules
222                     Object l = left;
223                     Object r = right;
224                     boolean ret;
225                     if (l == null) { Object tmp = r; r = l; l = tmp; }
226                     if (l == null && r == null) ret = true;
227                     else if (l instanceof Boolean) ret = new Boolean(JS.toBoolean(r)).equals(l);
228                     else if (l instanceof Number) ret = JS.toNumber(r).doubleValue() == JS.toNumber(l).doubleValue();
229                     else if (l instanceof String) ret = r != null && l.equals(r.toString());
230                     else ret = l.equals(r);
231                     t.push(new Boolean(op[i] == Lexer.EQ ? ret : !ret)); break;
232                 }
233
234                 default: throw new Error("unknown opcode " + op[i]);
235                 } }
236             }
237         if (t.size() != 1) {
238             throw new EvaluatorException(line, sourceName, "eval() terminated with " + t.size() + " elements on the stack; one expected");
239         }
240         return t.pop();
241     }
242
243     public Object doGet(final Object o, final Object v) {
244         if (o == null) throw new EvaluatorException(line, sourceName, "tried to get property \"" + v + "\" from the null value");
245         if (o instanceof String) {
246             if (v.equals("length")) return new Integer(((String)o).length());
247             else if (v.equals("substring")) return new JS.Function() {
248                     public Object _call(JS.Array args) {
249                         if (args.length() == 1) return ((String)o).substring(JS.toNumber(args.elementAt(0)).intValue());
250                         else if (args.length() == 2) return ((String)o).substring(JS.toNumber(args.elementAt(0)).intValue(),
251                                                                                   JS.toNumber(args.elementAt(1)).intValue());
252                         else throw new Error("String.substring() can only take one or two arguments");
253                     }
254                 };
255             else if (v.equals("toLowerCase")) return new JS.Function() {
256                     public Object _call(JS.Array args) {
257                         return ((String)o).toLowerCase();
258                     } };
259             else if (v.equals("toUpperCase")) return new JS.Function() {
260                     public Object _call(JS.Array args) {
261                         return ((String)o).toString().toUpperCase();
262                     } };
263             else if (v.equals("charAt")) return new JS.Function() {
264                     public Object _call(JS.Array args) {
265                         return ((String)o).charAt(JS.toNumber(args.elementAt(0)).intValue()) + "";
266                     } };
267             else if (v.equals("lastIndexOf")) return new JS.Function() {
268                     public Object _call(JS.Array args) {
269                         if (args.length() != 1) return null;
270                         return new Integer(((String)o).lastIndexOf(args.elementAt(0).toString()));
271                     } };
272             else if (v.equals("indexOf")) return new JS.Function() {
273                     public Object _call(JS.Array args) {
274                         if (args.length() != 1) return null;
275                         return new Integer(((String)o).indexOf(args.elementAt(0).toString()));
276                     } };
277             throw new Error("Not Implemented: propery " + v + " on String objects");
278         } else if (o instanceof Boolean) {
279             throw new Error("Not Implemented: properties on Boolean objects");
280         } else if (o instanceof Number) {
281             Log.log(this, "Not Implemented: properties on Number objects");
282             return null;
283             //throw new Error("Not Implemented: properties on Number objects");
284         } else if (o instanceof JS) {
285             return ((JS)o).get(v);
286         }
287         return null;
288     }
289
290     static class Stack {
291         public Object[] os = new Object[256];
292         private int size = 0;
293         public void push(Object o) { os[size++] = o; }
294         public Object pop() { return os[--size]; }
295         public Object peek() { return os[size - 1]; }
296         public void swap() { Object temp = os[size - 1]; os[size - 1] = os[size - 2]; os[size - 2] = temp; }
297         public int size() { return size; }
298     }
299
300     static class EvaluatorException extends RuntimeException {
301         public EvaluatorException(int line, String sourceName, String s) { super(sourceName + ":" + line + " " + s); }
302     }
303
304     static abstract class ControlTransferException extends Exception { }
305     static class BreakException extends ControlTransferException {
306         public String label;
307         BreakException(String label) { this.label = label; }
308     }
309     static class ContinueException extends ControlTransferException {
310         public String label;
311         ContinueException(String label) { this.label = label; }
312     }
313     static class ReturnException extends ControlTransferException {
314         public Object retval;
315         ReturnException(Object retval) { this.retval = retval; }
316     }
317     
318 }