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