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