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