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