2003/05/07 10:15:42
[org.ibex.core.git] / src / org / xwt / js / Expr.java
1 // Copyright 2002 Adam Megacz, see the COPYING file for licensing [GPL]
2 package org.xwt.js;
3
4 import java.io.*;
5 import org.xwt.util.*;
6
7 /** sorta like gcc trees */
8 public class Expr {
9     int code = -1;
10     
11     final Expr left;
12     final Expr right;
13     Expr next = null;   // if this expr is part of a list
14     
15     String string = null;
16     Number number = null;
17     
18     public String toString() { return toString(0); }
19     public String toString(int indent) {
20         String ret = "";
21         for(int i=0; i<indent; i++) ret += " ";
22         ret += Lexer.codeToString[code];
23         if (code == Lexer.NUMBER) ret += " " + number;
24         else if (string != null) ret += " \"" + string + "\"";
25         ret += "\n";
26         if (left != null) ret += left.toString(indent + 2);
27         if (right != null) ret += right.toString(indent + 2);
28         if (next != null) ret += next.toString(indent);
29         return ret;
30     }
31     
32     public Expr(String s) { this(Lexer.STRING); this.string = s; }  // an identifier or label
33     public Expr(int code, String s) { this(code); this.string = s; }
34     public Expr(Number n) { this(Lexer.NUMBER); this.number = n; }  // an identifier or label
35     public Expr(int code) { this(code, null, null); }
36     public Expr(int code, Expr left) { this(code, left, null); }
37     public Expr(int code, Expr left, Expr right) { this.code = code; this.left = left; this.right = right; }
38
39     public static Number toNumber(Object o) {
40         if (o == null) return new Long(0);
41         if (o instanceof Number) return ((Number)o);
42         if (o instanceof String) return new Double((String)o);
43         if (o instanceof Boolean) return ((Boolean)o).booleanValue() ? new Long(1) : new Long(0);
44         if (o instanceof JS) return ((JS)o).coerceToNumber();
45         throw new Error("toNumber() got object of type " + o.getClass().getName());
46     }
47  
48     public static double toDouble(Object o) { return toNumber(o).doubleValue(); }
49     public static long toLong(Object o) { return toNumber(o).longValue(); }
50     public static boolean toBoolean(Object o) {
51         if (o == null) return false;
52         if (o instanceof Boolean) return ((Boolean)o).booleanValue();
53         if (o instanceof Number) return o.equals(new Integer(0));
54         return true;
55     }
56
57     public Object eval(final JS.Scope s) throws ControlTransferException, JS.Exn {
58         switch(code) {
59
60         case Lexer.BITOR: return new Long(toLong(left.eval(s)) | toLong(right.eval(s)));
61         case Lexer.BITXOR: return new Long(toLong(left.eval(s)) ^ toLong(right.eval(s)));
62         case Lexer.BITAND: return new Long(toLong(left.eval(s)) & toLong(right.eval(s)));
63         case Lexer.BITNOT: return new Long(~toLong(left.eval(s)));
64
65         case Lexer.ADD: {
66             Object l = left.eval(s);
67             Object r = right.eval(s);
68             if (l instanceof String || r instanceof String) return l.toString() + r.toString();
69             return new Double(toDouble(l) + toDouble(r));
70         }
71
72         case Lexer.SUB: return new Double(toDouble(left.eval(s)) - toDouble(right.eval(s)));
73         case Lexer.MUL: return new Double(toDouble(left.eval(s)) * toDouble(right.eval(s)));
74         case Lexer.DIV: return new Double(toDouble(left.eval(s)) / toDouble(right.eval(s)));
75         case Lexer.MOD: return new Double(toDouble(left.eval(s)) % toDouble(right.eval(s)));
76
77         case Lexer.LSH: return new Long(toLong(left.eval(s)) << toLong(right.eval(s)));
78         case Lexer.RSH: return new Long(toLong(left.eval(s)) >> toLong(right.eval(s)));
79         case Lexer.URSH: return new Long(toLong(left.eval(s)) >>> toLong(right.eval(s)));
80
81         case Lexer.LT: return toDouble(left.eval(s)) < toDouble(right.eval(s)) ? Boolean.TRUE : Boolean.FALSE;
82         case Lexer.LE: return toDouble(left.eval(s)) <= toDouble(right.eval(s)) ? Boolean.TRUE : Boolean.FALSE;
83         case Lexer.GT: return toDouble(left.eval(s)) > toDouble(right.eval(s)) ? Boolean.TRUE : Boolean.FALSE;
84         case Lexer.GE: return toDouble(left.eval(s)) >= toDouble(right.eval(s)) ? Boolean.TRUE : Boolean.FALSE;
85
86         case Lexer.OR: return new Boolean(toBoolean(left.eval(s)) || toBoolean(right.eval(s)));
87         case Lexer.AND: return new Boolean(toBoolean(left.eval(s)) && toBoolean(right.eval(s)));
88
89         case Lexer.EQ:
90         case Lexer.NE: {
91             // FIXME: should use Javascript coercion-equality rules
92             boolean ret = left.eval(s).equals(right.eval(s));
93             return new Boolean(code == Lexer.EQ ? ret : !ret);
94         }
95
96         case Lexer.INC:
97         case Lexer.DEC:
98         case Lexer.ASSIGN: {
99             Object v = (code == Lexer.ASSIGN) ? right.eval(s) : new Double(toDouble(left.eval(s)) + (code == Lexer.INC ? 1 : -1));
100             if (left.code == Lexer.DOT) {
101                 Object o = left.left.eval(s);
102                 if (o instanceof String) {
103                     throw new Error("can't set properties on a String");
104                 } else if (o instanceof Number) {
105                     throw new Error("can't set properties on a Number");
106                 } else if (o instanceof Boolean) {
107                     throw new Error("can't set properties on a Boolean");
108                 } else {
109                     ((JS)left.left.eval(s)).put(left.right.eval(s), v);
110                     return v;
111                 }
112             } else {
113                 s.put(left.string, v);
114                 return v;
115             }
116         }
117
118         case Lexer.TYPEOF: {
119             Object o = left.eval(s);
120             if (o == null) return "null";
121             if (o.getClass() == String.class) return "string";
122             if (o.getClass() == Boolean.class) return "boolean";
123             if (o instanceof Number) return "number";
124             if (o instanceof JS.Array) return "array";
125             if (o instanceof JS) return "object";
126             throw new Error("typeof " + o.getClass().getName() + " unknown");
127         }
128
129         case Lexer.NUMBER: return number;
130         case Lexer.STRING: return string;
131
132         case Lexer.NULL: return null;
133         case Lexer.THIS: return s;
134         case Lexer.FALSE: return Boolean.FALSE;
135         case Lexer.TRUE: return Boolean.TRUE;
136         case Lexer.ASSERT: if (!toBoolean(left.eval(s))) throw new Error("assertion failed");
137         case Lexer.THROW: throw new JS.Exn(left.eval(s));
138
139         case Lexer.NAME: return s.get(string);
140         case Lexer.DOT: {
141             Object o = left.eval(s);
142             Object v = right.eval(s);
143             if (o instanceof String) {
144                 if (v.equals("length")) return new Integer(((String)o).length());
145                 throw new Error("Not Implemented: properties on String objects");
146             } else if (o instanceof Boolean) {
147                 throw new Error("Not Implemented: properties on Boolean objects");
148             } else if (o instanceof Number) {
149                 throw new Error("Not Implemented: properties on Number objects");
150             } else if (o instanceof JS) {
151                 return ((JS)o).get(v);
152             }
153         }
154
155         case Lexer.TRY: {
156             boolean safeToExit = false;
157             try {
158                 Object ret = left.eval(s);
159                 safeToExit = true;
160                 return ret;
161             } catch (JS.Exn e) {
162                 Expr c = right;
163                 if (c.code == Lexer.CATCH) {
164                     JS.Scope scope = new JS.Scope(s);
165                     s.put(c.left.string, e);
166                     c.right.eval(scope);
167                     c = c.next;
168                 }
169                 if (c.code == Lexer.FINALLY) {
170                     JS.Scope scope = new JS.Scope(s);
171                     c.left.eval(scope);
172                 }
173             } finally {
174                 if (!safeToExit) Log.log(this, "WARNING: Java exception penetrated a JavaScript try{} block");
175             }
176             return null;
177         }
178
179         case Lexer.LP:
180             JS.Function f = (JS.Function)left.eval(s);
181             JS.Array arguments = new JS.Array();
182             for(Expr e = right; e != null; e = e.next) arguments.addElement(e.eval(s));
183             return f.call(arguments);
184             
185         case Lexer.FUNCTION:
186             return new JS.ObjFunction() {
187                     public JS.Scope getParentScopeOfDeclaration() { return s; }
188                     public Object call(JS.Array args) throws JS.Exn {
189                         JS.Scope scope = new JS.Scope(getParentScopeOfDeclaration()) {
190                                 public Object get(Object key) throws JS.Exn {
191                                     if (key.equals("trapee")) return org.xwt.Trap.currentTrapee();
192                                     return super.get(key);
193                                 }
194                             };
195                         // FIXME
196                         args.put("cascade", org.xwt.Trap.cascadeFunction);
197                         scope.put("arguments", args);
198                         int i = 0;
199                         for(Expr e = left; e != null; e = e.next) scope.put(e.string, args.get(new Integer(i++)));
200                         try {
201                             return right.eval(scope);
202                         } catch (ReturnException r) {
203                             return r.retval;
204                         } catch (ControlTransferException c) {
205                             throw new Error("error, ControlTransferException tried to leave a function: " + c);
206                         }
207                     }
208                 };
209
210         case Lexer.FOR:
211             Object[] keys = ((JS)left.right.eval(s)).enumerateProperties();
212             try {
213                 for(int i=0; i<keys.length; i++) {
214                     JS.Scope scope = new JS.Scope(s);
215                     scope.put(left.left.string, keys[i]);
216                     try {
217                         right.eval(scope);
218                     } catch (ContinueException c) { /* FIXME: handle labels */ }
219                 }
220             } catch (BreakException b) { /* FIXME: handle labels */ }
221             return null;
222
223         case Lexer.SWITCH:
224             Object switchVal = left.eval(s);
225             boolean go = false;
226             try {
227                 for(Expr e = right; e != null; e = e.next) {
228                     if (go || e.code == Lexer.DEFAULT || e.left.eval(s).equals(switchVal)) go = true;
229                     if (go) e.right.eval(s);
230                 }
231             } catch (BreakException b) { /* FIXME: handle labels */ }
232             return null;
233
234         case Lexer.LB: {
235             JS.Array ret = new JS.Array();
236             for(Expr e = left; e != null; e = e.next) ret.addElement(e.eval(s));
237             return ret;
238         }
239             
240         case Lexer.LC: // block
241             for(Expr e = left; e != null; e = e.next) e.eval(s);
242             return null;
243             
244         case Lexer.RC: {   // Object ctor
245             JS.Obj ret = new JS.Obj();
246             for(Expr e = left; e != null; e = e.next)
247                 ret.put(e.left.string, e.right.eval(s));
248             return ret;
249         }
250             
251         case Lexer.VAR:
252             for(Expr e = left; e != null; e = e.next)
253                 if (e.code == Lexer.NAME) {
254                     s.declare(e.string);
255                 } else {
256                     s.declare(e.left.string);
257                     e.eval(s);
258                 }
259             return null;
260
261         case Lexer.HOOK: return toBoolean(left.eval(s)) ? right.left.eval(s) : right.right.eval(s);
262         case Lexer.IF: return toBoolean(left.eval(s)) ? right.left.eval(s) : right.right.eval(s);
263
264         case Lexer.BREAK: throw new BreakException(string);
265         case Lexer.CONTINUE: throw new ContinueException(string);
266         case Lexer.RETURN: throw new ReturnException(left == null ? null : left.eval(s));
267
268         case Lexer.WHILE:
269         case Lexer.DO:
270             try {
271                 boolean first = true;
272                 while((first && code == Lexer.DO) || toBoolean(left.eval(s))) {
273                     first = false;
274                     try { right.eval(s); }
275                     catch (ContinueException e) {
276                         if (e.label != null && !e.label.equals(string)) throw e;
277                     }
278                 }
279             } catch (BreakException e) {
280                 if (e.label != null && !e.label.equals(string)) throw e;
281             }
282             return null;
283
284         default: throw new Error("don't know how to eval an Expr with code " + Lexer.codeToString[code]);
285         }
286     }
287
288     private static abstract class ControlTransferException extends Exception { }
289     private static class BreakException extends ControlTransferException {
290         public String label;
291         BreakException(String label) { this.label = label; }
292     }
293     private static class ContinueException extends ControlTransferException {
294         public String label;
295         ContinueException(String label) { this.label = label; }
296     }
297     private static class ReturnException extends ControlTransferException {
298         public Object retval;
299         ReturnException(Object retval) { this.retval = retval; }
300     }
301 }
302