2003/05/06 09:05:49
authormegacz <megacz@xwt.org>
Fri, 30 Jan 2004 06:59:55 +0000 (06:59 +0000)
committermegacz <megacz@xwt.org>
Fri, 30 Jan 2004 06:59:55 +0000 (06:59 +0000)
darcs-hash:20040130065955-2ba56-7d730002f599aa834a397dce388c272093ef97fb.gz

src/org/xwt/js/Expr.java [new file with mode: 0644]

diff --git a/src/org/xwt/js/Expr.java b/src/org/xwt/js/Expr.java
new file mode 100644 (file)
index 0000000..7f5df62
--- /dev/null
@@ -0,0 +1,302 @@
+// Copyright 2002 Adam Megacz, see the COPYING file for licensing [GPL]
+package org.xwt.js;
+
+import java.io.*;
+import org.xwt.util.*;
+
+/** sorta like gcc trees */
+public class Expr {
+    int code = -1;
+    
+    final Expr left;
+    final Expr right;
+    Expr next = null;   // if this expr is part of a list
+    
+    String string = null;
+    Number number = null;
+    
+    public String toString() { return toString(0); }
+    public String toString(int indent) {
+       String ret = "";
+       for(int i=0; i<indent; i++) ret += " ";
+       ret += Lexer.codeToString[code];
+       if (code == Lexer.NUMBER) ret += " " + number;
+       else if (string != null) ret += " \"" + string + "\"";
+       ret += "\n";
+       if (left != null) ret += left.toString(indent + 2);
+       if (right != null) ret += right.toString(indent + 2);
+       if (next != null) ret += next.toString(indent);
+       return ret;
+    }
+    
+    public Expr(String s) { this(Lexer.STRING); this.string = s; }  // an identifier or label
+    public Expr(int code, String s) { this(code); this.string = s; }
+    public Expr(Number n) { this(Lexer.NUMBER); this.number = n; }  // an identifier or label
+    public Expr(int code) { this(code, null, null); }
+    public Expr(int code, Expr left) { this(code, left, null); }
+    public Expr(int code, Expr left, Expr right) { this.code = code; this.left = left; this.right = right; }
+
+    public static Number toNumber(Object o) {
+       if (o == null) return new Long(0);
+       if (o instanceof Number) return ((Number)o);
+       if (o instanceof String) return new Double((String)o);
+       if (o instanceof Boolean) return ((Boolean)o).booleanValue() ? new Long(1) : new Long(0);
+       if (o instanceof JS) return ((JS)o).coerceToNumber();
+       throw new Error("toNumber() got object of type " + o.getClass().getName());
+    }
+    public static double toDouble(Object o) { return toNumber(o).doubleValue(); }
+    public static long toLong(Object o) { return toNumber(o).longValue(); }
+    public static boolean toBoolean(Object o) {
+       if (o == null) return false;
+       if (o instanceof Boolean) return ((Boolean)o).booleanValue();
+       if (o instanceof Number) return o.equals(new Integer(0));
+       return true;
+    }
+
+    public Object eval(final JS.Scope s) throws ControlTransferException, JS.Exn {
+       switch(code) {
+
+       case Lexer.BITOR: return new Long(toLong(left.eval(s)) | toLong(right.eval(s)));
+       case Lexer.BITXOR: return new Long(toLong(left.eval(s)) ^ toLong(right.eval(s)));
+       case Lexer.BITAND: return new Long(toLong(left.eval(s)) & toLong(right.eval(s)));
+       case Lexer.BITNOT: return new Long(~toLong(left.eval(s)));
+
+       case Lexer.ADD: {
+           Object l = left.eval(s);
+           Object r = right.eval(s);
+           if (l instanceof String || r instanceof String) return l.toString() + r.toString();
+           return new Double(toDouble(l) + toDouble(r));
+       }
+
+       case Lexer.SUB: return new Double(toDouble(left.eval(s)) - toDouble(right.eval(s)));
+       case Lexer.MUL: return new Double(toDouble(left.eval(s)) * toDouble(right.eval(s)));
+       case Lexer.DIV: return new Double(toDouble(left.eval(s)) / toDouble(right.eval(s)));
+       case Lexer.MOD: return new Double(toDouble(left.eval(s)) % toDouble(right.eval(s)));
+
+       case Lexer.LSH: return new Long(toLong(left.eval(s)) << toLong(right.eval(s)));
+       case Lexer.RSH: return new Long(toLong(left.eval(s)) >> toLong(right.eval(s)));
+       case Lexer.URSH: return new Long(toLong(left.eval(s)) >>> toLong(right.eval(s)));
+
+       case Lexer.LT: return toDouble(left.eval(s)) < toDouble(right.eval(s)) ? Boolean.TRUE : Boolean.FALSE;
+       case Lexer.LE: return toDouble(left.eval(s)) <= toDouble(right.eval(s)) ? Boolean.TRUE : Boolean.FALSE;
+       case Lexer.GT: return toDouble(left.eval(s)) > toDouble(right.eval(s)) ? Boolean.TRUE : Boolean.FALSE;
+       case Lexer.GE: return toDouble(left.eval(s)) >= toDouble(right.eval(s)) ? Boolean.TRUE : Boolean.FALSE;
+
+       case Lexer.OR: return new Boolean(toBoolean(left.eval(s)) || toBoolean(right.eval(s)));
+       case Lexer.AND: return new Boolean(toBoolean(left.eval(s)) && toBoolean(right.eval(s)));
+
+       case Lexer.EQ:
+       case Lexer.NE: {
+           // FIXME: should use Javascript coercion-equality rules
+           boolean ret = left.eval(s).equals(right.eval(s));
+           return new Boolean(code == Lexer.EQ ? ret : !ret);
+       }
+
+       case Lexer.INC:
+       case Lexer.DEC:
+       case Lexer.ASSIGN: {
+           Object v = (code == Lexer.ASSIGN) ? right.eval(s) : new Double(toDouble(left.eval(s)) + (code == Lexer.INC ? 1 : -1));
+           if (left.code == Lexer.DOT) {
+               Object o = left.left.eval(s);
+               if (o instanceof String) {
+                   throw new Error("can't set properties on a String");
+               } else if (o instanceof Number) {
+                   throw new Error("can't set properties on a Number");
+               } else if (o instanceof Boolean) {
+                   throw new Error("can't set properties on a Boolean");
+               } else {
+                   ((JS)left.left.eval(s)).put(left.right.eval(s), v);
+                   return v;
+               }
+           } else {
+               s.put(left.string, v);
+               return v;
+           }
+       }
+
+       case Lexer.TYPEOF: {
+           Object o = left.eval(s);
+           if (o == null) return "null";
+           if (o.getClass() == String.class) return "string";
+           if (o.getClass() == Boolean.class) return "boolean";
+           if (o instanceof Number) return "number";
+           if (o instanceof JS.Array) return "array";
+           if (o instanceof JS) return "object";
+           throw new Error("typeof " + o.getClass().getName() + " unknown");
+       }
+
+       case Lexer.NUMBER: return number;
+       case Lexer.STRING: return string;
+
+       case Lexer.NULL: return null;
+       case Lexer.THIS: return s;
+       case Lexer.FALSE: return Boolean.FALSE;
+       case Lexer.TRUE: return Boolean.TRUE;
+       case Lexer.ASSERT: if (!toBoolean(left.eval(s))) throw new Error("assertion failed");
+       case Lexer.THROW: throw new JS.Exn(left.eval(s));
+
+       case Lexer.NAME: return s.get(string);
+       case Lexer.DOT: {
+           Object o = left.eval(s);
+           Object v = right.eval(s);
+           if (o instanceof String) {
+               if (v.equals("length")) return new Integer(((String)o).length());
+               throw new Error("Not Implemented: properties on String objects");
+           } else if (o instanceof Boolean) {
+               throw new Error("Not Implemented: properties on Boolean objects");
+           } else if (o instanceof Number) {
+               throw new Error("Not Implemented: properties on Number objects");
+           } else if (o instanceof JS) {
+               return ((JS)o).get(v);
+           }
+       }
+
+       case Lexer.TRY: {
+           boolean safeToExit = false;
+           try {
+               Object ret = left.eval(s);
+               safeToExit = true;
+               return ret;
+           } catch (JS.Exn e) {
+               Expr c = right;
+               if (c.code == Lexer.CATCH) {
+                   JS.Scope scope = new JS.Scope(s);
+                   s.put(c.left.string, e);
+                   c.right.eval(scope);
+                   c = c.next;
+               }
+               if (c.code == Lexer.FINALLY) {
+                   JS.Scope scope = new JS.Scope(s);
+                   c.left.eval(scope);
+               }
+           } finally {
+               if (!safeToExit) Log.log(this, "WARNING: Java exception penetrated a JavaScript try{} block");
+           }
+           return null;
+       }
+
+       case Lexer.LP:
+           JS.Function f = (JS.Function)left.eval(s);
+           JS.Array arguments = new JS.Array();
+           for(Expr e = right; e != null; e = e.next) arguments.addElement(e.eval(s));
+           return f.call(arguments);
+           
+       case Lexer.FUNCTION:
+           return new JS.ObjFunction() {
+                   public JS.Scope getParentScopeOfDeclaration() { return s; }
+                   public Object call(JS.Array args) throws JS.Exn {
+                       JS.Scope scope = new JS.Scope(getParentScopeOfDeclaration()) {
+                               public Object get(Object key) throws JS.Exn {
+                                   if (key.equals("trapee")) return org.xwt.Trap.currentTrapee();
+                                   return super.get(key);
+                               }
+                           }
+                       // FIXME
+                       args.put("cascade", org.xwt.Trap.cascadeFunction);
+                       scope.put("arguments", args);
+                       int i = 0;
+                       for(Expr e = left; e != null; e = e.next) scope.put(e.string, args.get(new Integer(i++)));
+                       try {
+                           return right.eval(scope);
+                       } catch (ReturnException r) {
+                           return r.retval;
+                       } catch (ControlTransferException c) {
+                           throw new Error("error, ControlTransferException tried to leave a function: " + c);
+                       }
+                   }
+               };
+
+       case Lexer.FOR:
+           Object[] keys = ((JS)left.right.eval(s)).enumerateProperties();
+           try {
+               for(int i=0; i<keys.length; i++) {
+                   JS.Scope scope = new JS.Scope(s);
+                   scope.put(left.left.string, keys[i]);
+                   try {
+                       right.eval(scope);
+                   } catch (ContinueException c) { /* FIXME: handle labels */ }
+               }
+           } catch (BreakException b) { /* FIXME: handle labels */ }
+           return null;
+
+       case Lexer.SWITCH:
+           Object switchVal = left.eval(s);
+           boolean go = false;
+           try {
+               for(Expr e = right; e != null; e = e.next) {
+                   if (go || e.code == Lexer.DEFAULT || e.left.eval(s).equals(switchVal)) go = true;
+                   if (go) e.right.eval(s);
+               }
+           } catch (BreakException b) { /* FIXME: handle labels */ }
+           return null;
+
+       case Lexer.LB: {
+           JS.Array ret = new JS.Array();
+           for(Expr e = left; e != null; e = e.next) ret.addElement(e.eval(s));
+           return ret;
+       }
+           
+       case Lexer.LC: // block
+           for(Expr e = left; e != null; e = e.next) e.eval(s);
+           return null;
+           
+       case Lexer.RC: {   // Object ctor
+           JS.Obj ret = new JS.Obj();
+           for(Expr e = left; e != null; e = e.next)
+               ret.put(e.left.string, e.right.eval(s));
+           return ret;
+       }
+           
+       case Lexer.VAR:
+           for(Expr e = left; e != null; e = e.next)
+               if (e.code == Lexer.NAME) {
+                   s.declare(e.string);
+               } else {
+                   s.declare(e.left.string);
+                   e.eval(s);
+               }
+           return null;
+
+       case Lexer.HOOK: return toBoolean(left.eval(s)) ? right.left.eval(s) : right.right.eval(s);
+       case Lexer.IF: return toBoolean(left.eval(s)) ? right.left.eval(s) : right.right.eval(s);
+
+       case Lexer.BREAK: throw new BreakException(string);
+       case Lexer.CONTINUE: throw new ContinueException(string);
+       case Lexer.RETURN: throw new ReturnException(left == null ? null : left.eval(s));
+
+       case Lexer.WHILE:
+       case Lexer.DO:
+           try {
+               boolean first = true;
+               while((first && code == Lexer.DO) || toBoolean(left.eval(s))) {
+                   first = false;
+                   try { right.eval(s); }
+                   catch (ContinueException e) {
+                       if (e.label != null && !e.label.equals(string)) throw e;
+                   }
+               }
+           } catch (BreakException e) {
+               if (e.label != null && !e.label.equals(string)) throw e;
+           }
+           return null;
+
+       default: throw new Error("don't know how to eval an Expr with code " + Lexer.codeToString[code]);
+       }
+    }
+
+    private static abstract class ControlTransferException extends Exception { }
+    private static class BreakException extends ControlTransferException {
+       public String label;
+       BreakException(String label) { this.label = label; }
+    }
+    private static class ContinueException extends ControlTransferException {
+       public String label;
+       ContinueException(String label) { this.label = label; }
+    }
+    private static class ReturnException extends ControlTransferException {
+       public Object retval;
+       ReturnException(Object retval) { this.retval = retval; }
+    }
+}
+