2003/06/07 09:09:44
[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 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))); 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 target = (JS)t.peek();
105                 if (target == null) throw new JS.Exn("tried to put a value to the " + key + " property on the null value");
106                 target.put(key, val);
107                 t.push(val);
108                 break;
109             }
110
111             case GET: {
112                 Object v = t.pop();
113                 Object o = t.pop();
114                 t.push(doGet(o, v));
115                 break;
116             }
117
118             case GET_PRESERVE: {
119                 Object v = t.pop();
120                 Object o = t.peek();
121                 t.push(v);
122                 t.push(doGet(o, v));
123                 break;
124             }
125                     
126             case CALL: {
127                 JS.Array arguments = new JS.Array();
128                 int numArgs = JS.toNumber(arg[i]).intValue();
129                 arguments.setSize(numArgs);
130                 for(int j=numArgs - 1; j >= 0; j--) arguments.setElementAt(t.pop(), j);
131                 JS.Function f = (JS.Function)t.pop();
132                 if (f == null) throw new JS.Exn(new EvaluatorException(line, sourceName, "attempted to call null"));
133                 t.push(f.call(arguments));
134                 break;
135             }
136
137             case OpCodes.FUNCTION: {
138                 final ForthBlock myBytes = (ForthBlock)arg[i];
139                 t.push(new JS.Function() {
140                         public String toString() { return sourceName + ":" + line; }
141                         public String getSourceName() throws JS.Exn { return sourceName; }
142                         public int getLine() throws JS.Exn { return line; }
143                         public Object _call(final JS.Array args) throws JS.Exn {
144                             Function save = JS.getCurrentFunction();
145                             JS.currentFunction.put(java.lang.Thread.currentThread(), this);
146                             JS.Scope scope = new JS.Scope(s) {
147                                     // FIXME
148                                     public String getSourceName() { return sourceName; }
149                                     public Object get(Object key) throws JS.Exn {
150                                         if (key.equals("trapee")) return org.xwt.Trap.currentTrapee();
151                                         else if (key.equals("cascade")) return org.xwt.Trap.cascadeFunction;
152                                         return super.get(key);
153                                     }
154                                 };
155                             Stack t0 = new Stack();
156                             t0.push(args);
157                             try {
158                                 return myBytes.eval(scope, t0);
159                             } catch (ReturnException r) {
160                                 return r.retval;
161                             } catch (ControlTransferException c) {
162                                 throw new EvaluatorException(line, sourceName, "error, ControlTransferException tried to leave a function: " + c);
163                             } finally {
164                                 if (save == null) JS.currentFunction.remove(java.lang.Thread.currentThread());
165                                 else JS.currentFunction.put(java.lang.Thread.currentThread(), save);
166                             }
167                         }
168                     });
169                 break;
170             }
171
172             case Lexer.INC: case Lexer.DEC: {
173                 boolean isPrefix = JS.toBoolean(arg[i]);
174                 Object key = t.pop();
175                 JS obj = (JS)t.pop();
176                 Number num = JS.toNumber(obj.get(key));
177                 Number val = new Double(op[i] == Lexer.INC ? num.doubleValue() + 1.0 : num.doubleValue() - 1.0);
178                 obj.put(key, val);
179                 t.push(isPrefix ? val : num);
180                 break;
181             }
182
183             default: {
184                 Object right = t.pop();
185                 Object left = t.pop();
186                 switch(op[i]) {
187
188                 case Lexer.BITOR: t.push(new Long(JS.toLong(left) | JS.toLong(right))); break;
189                 case Lexer.BITXOR: t.push(new Long(JS.toLong(left) ^ JS.toLong(right))); break;
190                 case Lexer.BITAND: t.push(new Long(JS.toLong(left) & JS.toLong(right))); break;
191
192                 case Lexer.ADD: {
193                     Object l = left;
194                     Object r = right;
195                     if (l instanceof String || r instanceof String) {
196                         if (l == null) l = "null";
197                         if (r == null) r = "null";
198                         if (l instanceof Number && ((Number)l).doubleValue() == ((Number)l).longValue())
199                             l = new Long(((Number)l).longValue());
200                         if (r instanceof Number && ((Number)r).doubleValue() == ((Number)r).longValue())
201                             r = new Long(((Number)r).longValue());
202                         t.push(l.toString() + r.toString()); break;
203                     }
204                     t.push(new Double(JS.toDouble(l) + JS.toDouble(r))); break;
205                 }
206                         
207                 case Lexer.SUB: t.push(new Double(JS.toDouble(left) - JS.toDouble(right))); break;
208                 case Lexer.MUL: t.push(new Double(JS.toDouble(left) * JS.toDouble(right))); break;
209                 case Lexer.DIV: t.push(new Double(JS.toDouble(left) / JS.toDouble(right))); break;
210                 case Lexer.MOD: t.push(new Double(JS.toDouble(left) % JS.toDouble(right))); break;
211                         
212                 case Lexer.LSH: t.push(new Long(JS.toLong(left) << JS.toLong(right))); break;
213                 case Lexer.RSH: t.push(new Long(JS.toLong(left) >> JS.toLong(right))); break;
214                 case Lexer.URSH: t.push(new Long(JS.toLong(left) >>> JS.toLong(right))); break;
215                         
216                     // FIXME: these need to work on strings
217                 case Lexer.LT: t.push(JS.toDouble(left) < JS.toDouble(right) ? Boolean.TRUE : Boolean.FALSE); break;
218                 case Lexer.LE: t.push(JS.toDouble(left) <= JS.toDouble(right) ? Boolean.TRUE : Boolean.FALSE); break;
219                 case Lexer.GT: t.push(JS.toDouble(left) > JS.toDouble(right) ? Boolean.TRUE : Boolean.FALSE); break;
220                 case Lexer.GE: t.push(JS.toDouble(left) >= JS.toDouble(right) ? Boolean.TRUE : Boolean.FALSE); break;
221                     
222                 case Lexer.EQ:
223                 case Lexer.NE: {
224                     // FIXME: should use Javascript coercion-equality rules
225                     Object l = left;
226                     Object r = right;
227                     boolean ret;
228                     if (l == null) { Object tmp = r; r = l; l = tmp; }
229                     if (l == null && r == null) ret = true;
230                     else if (l instanceof Boolean) ret = new Boolean(JS.toBoolean(r)).equals(l);
231                     else if (l instanceof Number) ret = JS.toNumber(r).doubleValue() == JS.toNumber(l).doubleValue();
232                     else if (l instanceof String) ret = r != null && l.equals(r.toString());
233                     else ret = l.equals(r);
234                     t.push(new Boolean(op[i] == Lexer.EQ ? ret : !ret)); break;
235                 }
236
237                 default: throw new Error("unknown opcode " + op[i]);
238                 } }
239             }
240         if (t.size() != 1) {
241             for(int i=0; i<size; i++) {
242                 System.out.println((op[i] >= 0 ? codeToString[op[i]] : "" + op[i]) + " [" + arg[i] + "]");
243             }
244             throw new EvaluatorException(line, sourceName, "eval() terminated with " + t.size() + " elements on the stack; one expected");
245         }
246         return t.pop();
247     }
248
249     public Object doGet(final Object o, final Object v) {
250         if (o == null) {
251             throw new EvaluatorException(line, sourceName, "tried to get property \"" + v + "\" from the null value");
252         }
253         if (o instanceof String) {
254             if (v.equals("length")) return new Integer(((String)o).length());
255             else if (v.equals("substring")) return new JS.Function() {
256                     public Object _call(JS.Array args) {
257                         if (args.length() == 1) return ((String)o).substring(JS.toNumber(args.elementAt(0)).intValue());
258                         else if (args.length() == 2) return ((String)o).substring(JS.toNumber(args.elementAt(0)).intValue(),
259                                                                                   JS.toNumber(args.elementAt(1)).intValue());
260                         else throw new Error("String.substring() can only take one or two arguments");
261                     }
262                 };
263             else if (v.equals("toLowerCase")) return new JS.Function() {
264                     public Object _call(JS.Array args) {
265                         return ((String)o).toLowerCase();
266                     } };
267             else if (v.equals("toUpperCase")) return new JS.Function() {
268                     public Object _call(JS.Array args) {
269                         return ((String)o).toString().toUpperCase();
270                     } };
271             else if (v.equals("charAt")) return new JS.Function() {
272                     public Object _call(JS.Array args) {
273                         return ((String)o).charAt(JS.toNumber(args.elementAt(0)).intValue()) + "";
274                     } };
275             else if (v.equals("lastIndexOf")) return new JS.Function() {
276                     public Object _call(JS.Array args) {
277                         if (args.length() != 1) return null;
278                         return new Integer(((String)o).lastIndexOf(args.elementAt(0).toString()));
279                     } };
280             else if (v.equals("indexOf")) return new JS.Function() {
281                     public Object _call(JS.Array args) {
282                         if (args.length() != 1) return null;
283                         return new Integer(((String)o).indexOf(args.elementAt(0).toString()));
284                     } };
285             throw new Error("Not Implemented: propery " + v + " on String objects");
286         } else if (o instanceof Boolean) {
287             throw new Error("Not Implemented: properties on Boolean objects");
288         } else if (o instanceof Number) {
289             Log.log(this, "Not Implemented: properties on Number objects");
290             return null;
291             //throw new Error("Not Implemented: properties on Number objects");
292         } else if (o instanceof JS) {
293             return ((JS)o).get(v);
294         }
295         return null;
296     }
297
298     static class Stack {
299         public Object[] os = new Object[256];
300         private int size = 0;
301         public void push(Object o) { os[size++] = o; }
302         public Object pop() { return os[--size]; }
303         public Object peek() { return os[size - 1]; }
304         public void swap() { Object temp = os[size - 1]; os[size - 1] = os[size - 2]; os[size - 2] = temp; }
305         public int size() { return size; }
306     }
307
308     static class EvaluatorException extends RuntimeException {
309         public EvaluatorException(int line, String sourceName, String s) { super(sourceName + ":" + line + " " + s); }
310     }
311
312     static abstract class ControlTransferException extends Exception { }
313     static class BreakException extends ControlTransferException {
314         public String label;
315         BreakException(String label) { this.label = label; }
316     }
317     static class ContinueException extends ControlTransferException {
318         public String label;
319         ContinueException(String label) { this.label = label; }
320     }
321     static class ReturnException extends ControlTransferException {
322         public Object retval;
323         ReturnException(Object retval) { this.retval = retval; }
324     }
325     
326 }