+ // Expr //////////////////////////////////////////////////////////////////////
+
+ /** sorta like gcc trees */
+ class Expr {
+ int code = -1;
+
+ final Expr left;
+ final Expr right;
+ Expr next = null; // if this expr is part of a list
+ int line = -1;
+ String sourceName = "unknown";
+
+ 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(int line, String s) { this(line, Lexer.STRING); this.string = s; } // an identifier or label
+ public Expr(int line, int code, String s) { this(line, code); this.string = s; }
+ public Expr(int line, Number n) { this(line, Lexer.NUMBER); this.number = n; } // an identifier or label
+ public Expr(int line, int code) { this(line, code, null, null); }
+ public Expr(int line, int code, Expr left) { this(line, code, left, null); }
+ public Expr(int line, int code, Expr left, Expr right) {
+ this.code = code; this.left = left; this.right = right;
+ this.line = line;
+ this.sourceName = Parser.this.sourceName;
+ }
+
+ public double toDouble(Object o) { return toNumber(o).doubleValue(); }
+ public long toLong(Object o) { return toNumber(o).longValue(); }
+ public 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) {
+ if (l == null) l = "null";
+ if (r == null) r = "null";
+ if (l instanceof Number && ((Number)l).doubleValue() == ((Number)l).longValue())
+ l = new Long(((Number)l).longValue());
+ if (r instanceof Number && ((Number)r).doubleValue() == ((Number)r).longValue())
+ r = new Long(((Number)r).longValue());
+ 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.BANG: return new Boolean(!toBoolean(left.eval(s)));
+
+ case Lexer.EQ:
+ case Lexer.NE: {
+ // FIXME: should use Javascript coercion-equality rules
+ Object l = left.eval(s);
+ Object r = right.eval(s);
+ boolean ret = (l == null && r == null) || (l != null && l.equals(r));
+ 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 EvaluatorException("can't set properties on a String");
+ } else if (o instanceof Number) {
+ throw new EvaluatorException("can't set properties on a Number");
+ } else if (o instanceof Boolean) {
+ throw new EvaluatorException("can't set properties on a Boolean");
+ } else {
+ JS target = (JS)left.left.eval(s);
+ if (target == null) throw new JS.Exn(new EvaluatorException("attempted to put to the null value"));
+ target.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 EvaluatorException("typeof " + o.getClass().getName() + " unknown");
+ }
+
+ case Lexer.NUMBER: return number;
+ case Lexer.STRING: return string;
+
+ case Lexer.NULL: return null;
+ case Lexer.FALSE: return Boolean.FALSE;
+ case Lexer.TRUE: return Boolean.TRUE;
+ case Lexer.ASSERT: if (!toBoolean(left.eval(s))) throw new EvaluatorException("assertion failed");
+ case Lexer.THROW: throw new JS.Exn(left.eval(s));
+ case Lexer.NAME: return s.get(string);
+ case Lexer.THIS: return s.isTransparent() ? s : this.eval(s.getParentScope());
+
+ case Lexer.DOT: {
+ final Object o = left.eval(s);
+ Object v = ((right.code == Lexer.NAME) || (right.code == Lexer.STRING)) ? right.string : right.eval(s);
+ if (o instanceof String) {
+ if (v.equals("length")) return new Integer(((String)o).length());
+ else if (v.equals("substring")) return new JS.Function() {
+ public Object _call(JS.Array args) {
+ if (args.length() == 1) return ((String)o).substring(toNumber(args.elementAt(0)).intValue());
+ else if (args.length() == 2) return ((String)o).substring(toNumber(args.elementAt(0)).intValue(),
+ toNumber(args.elementAt(1)).intValue());
+ else throw new EvaluatorException("String.substring() can only take one or two arguments");
+ }
+ };
+ else if (v.equals("indexOf")) return new JS.Function() {
+ public Object _call(JS.Array args) {
+ if (args.length() != 1) return null;
+ return new Integer(((String)o).indexOf(args.elementAt(0).toString()));
+ }
+ };
+ throw new EvaluatorException("Not Implemented: properties on String objects");
+ } else if (o instanceof Boolean) {
+ throw new EvaluatorException("Not Implemented: properties on Boolean objects");
+ } else if (o instanceof Number) {
+ throw new EvaluatorException("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));
+ if (f == null) throw new JS.Exn(new EvaluatorException("attempted to call null"));
+ return f.call(arguments);
+
+ case Lexer.FUNCTION:
+ return new JS.Function() {
+ public String toString() { return right.sourceName + ":" + right.line; }
+ public String getSourceName() throws JS.Exn { return right.sourceName; }
+ public Object _call(final JS.Array args) throws JS.Exn {
+ Function save = JS.getCurrentFunction();
+ JS.currentFunction.put(Thread.currentThread(), this);
+ JS.Scope scope = new JS.Scope(s) {
+ // FIXME
+ public String getSourceName() { return sourceName; }
+ public boolean isTransparent() { return true; }
+ public Object get(Object key) throws JS.Exn {
+ if (key.equals("trapee")) return org.xwt.Trap.currentTrapee();
+ else if (key.equals("cascade")) return org.xwt.Trap.cascadeFunction;
+ else if (key.equals("arguments")) return args;
+ return super.get(key);
+ }
+ };
+ 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 EvaluatorException("error, ControlTransferException tried to leave a function: "
+ + c);
+ } finally {
+ if (save == null) JS.currentFunction.remove(Thread.currentThread());
+ else JS.currentFunction.put(Thread.currentThread(), save);
+ }
+ }
+ };
+
+ case Lexer.FOR:
+ Object[] keys = ((JS)left.right.eval(s)).keys();
+ 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) {
+ if (c.label == null || c.label.equals(string)) continue;
+ } catch (BreakException b) {
+ if (b.label == null || b.label.equals(string)) return null;
+ throw (BreakException)b.fillInStackTrace();
+ }
+ }
+ 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) {
+ if (b.label == null || b.label.equals(string)) return null;
+ throw (BreakException)b.fillInStackTrace();
+ }
+ 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 c) {
+ if (c.label == null || c.label.equals(string)) continue;
+ } catch (BreakException b) {
+ if (b.label == null || b.label.equals(string)) return null;
+ throw (BreakException)b.fillInStackTrace();
+ }
+ }
+ } catch (BreakException e) {
+ if (e.label != null && !e.label.equals(string)) throw e;
+ }
+ return null;
+
+ default: throw new EvaluatorException("don't know how to eval an Expr with code " + Lexer.codeToString[code] + "\n" + this);
+ }
+ }
+
+ class EvaluatorException extends RuntimeException {
+ public EvaluatorException(String s) { super(sourceName + ":" + line + " " + s); }
+ }
+ }
+
+ static abstract class ControlTransferException extends Exception { }
+ static class BreakException extends ControlTransferException {
+ public String label;
+ BreakException(String label) { this.label = label; }
+ }
+ static class ContinueException extends ControlTransferException {
+ public String label;
+ ContinueException(String label) { this.label = label; }
+ }
+ static class ReturnException extends ControlTransferException {
+ public Object retval;
+ ReturnException(Object retval) { this.retval = retval; }
+ }
+
+ class ParserException extends RuntimeException {
+ public ParserException(String s) { super(sourceName + ":" + line + " " + s); }
+ }
+
+ 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();
+ // FIXME
+ throw new Error("toNumber() got object of type " + o.getClass().getName());
+ }
+ }