From: megacz Date: Fri, 30 Jan 2004 07:00:13 +0000 (+0000) Subject: 2003/05/12 05:05:01 X-Git-Tag: RC3~980 X-Git-Url: http://git.megacz.com/?p=org.ibex.core.git;a=commitdiff_plain;h=11fb6d3f994008abe6c16b4402c3904a543e240e 2003/05/12 05:05:01 darcs-hash:20040130070013-2ba56-562ee12c9ba65a88080462f30f31069d8675d051.gz --- diff --git a/src/org/xwt/js/Expr.java b/src/org/xwt/js/Expr.java index 1458186..e69de29 100644 --- a/src/org/xwt/js/Expr.java +++ b/src/org/xwt/js/Expr.java @@ -1,302 +0,0 @@ -// 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> 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= '0' && ci <= '9') || (ci >= 'a' && ci <= 'f') || (ci >= 'A' && ci <= 'F'))) + throw new IOException("illegal character '" + ((char)c) + "' in \\u unicode escape sequence"); + v = (v << 8) | Integer.parseInt(ci + "", 16); + } + c = (char)v; + break; + } + default: + // just use the character that was escaped + break; } } if (stringBuf != null) stringBuf.append((char) c); @@ -377,7 +394,8 @@ class Lexer { do { if (op != EOL) twoBack = op; op = _getToken(); - } while (op == EOL); // FIXME + if (op == EOL) { line++; col = 0; } + } while (op == EOL); return op; } @@ -385,8 +403,7 @@ class Lexer { int c; do { if ((c = in.read()) == '\n') break; } while (isWhiteSpace(c) || c == '\n'); if (c == -1) return -1; - if (c == '\\' && in.peek() == 'u') throw new IOException("\\u and \\0 escapes not currently supported -- use XML entities"); - if (Character.isJavaIdentifierStart((char)c)) return getIdentifier(c); + if (c == '\\' || Character.isJavaIdentifierStart((char)c)) return getIdentifier(c); if (isDigit(c) || (c == '.' && isDigit(in.peek()))) return getNumber(c); if (c == '"' || c == '\'') return getString(c); switch (c) { @@ -431,17 +448,18 @@ class Lexer { } if (c == -1) throw new IOException("msg.unterminated.comment"); return getToken(); // `goto retry' - default: throw new IOException("illegal character: " + c); + default: throw new IOException("illegal character: " + ((char)c)); } } - private static class SmartReader { + private class SmartReader { PushbackReader reader = null; int lastread = -1; public SmartReader(Reader r) { reader = new PushbackReader(r); } - public void unread() throws IOException { - reader.unread(lastread); + public void unread() throws IOException { unread((char)lastread); } + public void unread(char c) throws IOException { + reader.unread(c); if (accumulator != null) accumulator.setLength(accumulator.length() - 1); } public boolean match(char c) throws IOException { if (peek() == c) { reader.read(); return true; } else return false; } @@ -453,10 +471,11 @@ class Lexer { public int read() throws IOException { lastread = reader.read(); if (accumulator != null) accumulator.append((char)lastread); + if (lastread != '\n' && lastread != '\r') col++; return lastread; } - // FIXME: could be much more efficient + // FEATURE: could be much more efficient StringBuffer accumulator = null; public void startString() { accumulator = new StringBuffer(); diff --git a/src/org/xwt/js/Parser.java b/src/org/xwt/js/Parser.java index b09e38c..f70a13d 100644 --- a/src/org/xwt/js/Parser.java +++ b/src/org/xwt/js/Parser.java @@ -10,10 +10,14 @@ public class Parser extends Lexer { // Constructors ////////////////////////////////////////////////////// - public Parser(Reader r) throws IOException { super(r); } + public Parser(Reader r, String sourceName, int line) throws IOException { + super(r); + this.sourceName = sourceName; + this.line = line; + } public static void main(String[] s) throws Exception { - Parser p = new Parser(new InputStreamReader(System.in)); + Parser p = new Parser(new InputStreamReader(System.in), "stdin", 0); while(true) { Expr block = p.parseBlock(false); if (block == null) return; @@ -26,24 +30,28 @@ public class Parser extends Lexer { // Statics //////////////////////////////////////////////////////////// static byte[] precedence = new byte[MAX_TOKEN + 1]; + static boolean[] isRightAssociative = new boolean[MAX_TOKEN + 1]; static { - precedence[COMMA] = 1; - precedence[ASSIGN] = 2; - precedence[GT] = precedence[GE] = 3; - precedence[OR] = precedence[AND] = 4; - precedence[BITOR] = 5; - precedence[BITXOR] = 6; - precedence[BITAND] = 7; - precedence[EQ] = precedence[NE] = 8; - precedence[LT] = precedence[LE] = 9; - precedence[SHEQ] = precedence[SHNE] = 10; - precedence[LSH] = precedence[RSH] = precedence[URSH] = 11; - precedence[ADD] = precedence[SUB] = 12; - precedence[MUL] = precedence[DIV] = precedence[MOD] = 13; - precedence[BITNOT] = precedence[INSTANCEOF] = 14; - precedence[INC] = precedence[DEC] = 15; - precedence[LP] = 16; - precedence[DOT] = 17; + precedence[ASSIGN] = 1; + isRightAssociative[ASSIGN] = true; + precedence[HOOK] = 2; + precedence[COMMA] = 3; + precedence[GT] = precedence[GE] = 4; + precedence[OR] = precedence[AND] = 5; + precedence[BITOR] = 6; + precedence[BITXOR] = 7; + precedence[BITAND] = 8; + precedence[EQ] = precedence[NE] = 9; + precedence[LT] = precedence[LE] = 10; + precedence[SHEQ] = precedence[SHNE] = 11; + precedence[LSH] = precedence[RSH] = precedence[URSH] = 12; + precedence[ADD] = precedence[SUB] = 13; + precedence[MUL] = precedence[DIV] = precedence[MOD] = 14; + precedence[BITNOT] = precedence[INSTANCEOF] = 15; + precedence[INC] = precedence[DEC] = 16; + precedence[LP] = 17; + precedence[LB] = 18; + precedence[DOT] = 19; } @@ -54,10 +62,11 @@ public class Parser extends Lexer { Expr ret = null; int tok = peekToken(); boolean braced = tok == LC; - if (requireBraces && !braced) throw new Error("expected {"); + if (requireBraces && !braced) throw new ParserException("expected {"); if (braced) getToken(); Expr head = null; Expr tail = null; + int curLine = line; OUTER: while(true) { Expr smt; @@ -66,20 +75,40 @@ public class Parser extends Lexer { case LC: smt = parseBlock(true); break; case THROW: case RETURN: case ASSERT: getToken(); - smt = new Expr(tok, parseMaximalExpr()); - if (getToken() != SEMI) throw new Error("expected ;"); + smt = new Expr(curLine, tok, parseMaximalExpr()); + if (getToken() != SEMI) throw new ParserException("expected ;"); break; - case GOTO: case BREAK: case CONTINUE: + /* + FIXME + case NAME: { getToken(); - if (getToken() == NAME) - smt = new Expr(tok, new Expr(string)); - else if (tok == GOTO) - throw new Error("goto must be followed by a label"); - else - smt = new Expr(tok); - if (getToken() != SEMI) throw new Error("expected ;"); + String str = string; + if (getToken() != COLON) throw new ParserException("expected COLON after label"); + Expr labeledBlock = parseBlock(false); + if (labeledBlock.code != WHILE && labeledBlock.code != FOR && labeledBlock.code != SWITCH) + throw new ParserException("you can only label a WHILE, FOR, or SWITCH block"); + labeledBlock.string = str; + return labeledBlock; + } + */ + + case GOTO: case BREAK: case CONTINUE: { + getToken(); + int t = peekToken(); + if (t == NAME) { + getToken(); + smt = new Expr(curLine, tok, new Expr(curLine, string)); + } else if (t == GOTO) { + throw new ParserException("goto must be followed by a label"); + } else if (t == SEMI) { + getToken(); + smt = new Expr(curLine, tok); + } else { + throw new ParserException(codeToString[tok] + " followed by a " + codeToString[t] + "; expected NAME or SEMI"); + } break; + } case RC: if (braced) getToken(); @@ -93,21 +122,21 @@ public class Parser extends Lexer { default: smt = parseMaximalExpr(); if (smt == null) { - if (head == null) throw new Error("empty statement list; next token is " + codeToString[peekToken()]); + if (head == null) throw new ParserException("empty statement list; next token is " + codeToString[peekToken()]); break OUTER; } break; } if (head == null) head = tail = smt; else tail = (tail.next = smt); } - return new Expr(LC, head); + return new Expr(curLine, LC, head); } /** throws an error if the next token is not code */ public void expect(int code) throws IOException { int got = peekToken(); if (got != code) - throw new Error("expected " + codeToString[got] + ", got " + (got == -1 ? "EOL" : codeToString[got])); + throw new ParserException("expected " + codeToString[code] + ", got " + (got == -1 ? "EOL" : codeToString[got])); } /** parses the largest possible expression */ @@ -118,7 +147,7 @@ public class Parser extends Lexer { Expr save = prefix; prefix = parseSingleExpr(prefix, minPrecedence); if (save == prefix) break; - if (prefix == null) throw new Error("parseSingleExpr() returned null"); + if (prefix == null) throw new ParserException("parseSingleExpr() returned null"); } return prefix; } @@ -127,47 +156,71 @@ public class Parser extends Lexer { Expr e1 = null, e2 = null, e3 = null, head = null, tail = null, ret = null; int tok = peekToken(); - if (minPrecedence > 0 && tok < precedence.length && precedence[tok] != 0 && precedence[tok] <= minPrecedence) return prefix; + if (minPrecedence > 0 && + tok < precedence.length && + precedence[tok] != 0 && + (isRightAssociative[tok] ? + (precedence[tok] < minPrecedence) : + (precedence[tok] <= minPrecedence))) { + return prefix; + } getToken(); + int curLine = line; // these case arms match the precedence of operators; each arm is a precedence level. switch (tok) { - case WITH: throw new Error("XWT does not allow the WITH keyword"); - case VOID: case RESERVED: throw new Error("reserved word that you shouldn't be using"); - case NAME: if (prefix != null) { pushBackToken(); return prefix; } else return parseMaximalExpr(new Expr(NAME, string), minPrecedence); - case STRING: if (prefix != null) { pushBackToken(); return prefix; } else return new Expr(string); - case NUMBER: if (prefix != null) { pushBackToken(); return prefix; } else return new Expr(number); - case NULL: case TRUE: case FALSE: case NOP: if (prefix != null) { pushBackToken(); return prefix; } else return new Expr(tok); + case WITH: throw new ParserException("XWT does not allow the WITH keyword"); + case VOID: case RESERVED: throw new ParserException("reserved word that you shouldn't be using"); + case NAME: if (prefix != null) { pushBackToken(); return prefix; } else return parseMaximalExpr(new Expr(curLine, NAME, string), minPrecedence); + case STRING: if (prefix != null) { pushBackToken(); return prefix; } else return new Expr(curLine, string); + case NUMBER: if (prefix != null) { pushBackToken(); return prefix; } else return new Expr(curLine, number); + case NULL: case TRUE: case FALSE: case NOP: if (prefix != null) { pushBackToken(); return prefix; } else return new Expr(curLine, tok); case ASSIGN_BITOR: case ASSIGN_BITXOR: case ASSIGN_BITAND: case ASSIGN_LSH: case ASSIGN_RSH: case ASSIGN_URSH: case ASSIGN_ADD: case ASSIGN_SUB: case ASSIGN_MUL: case ASSIGN_DIV: case ASSIGN_MOD: - return new Expr(ASSIGN, prefix, new Expr(tok - 1, prefix, parseMaximalExpr(null, precedence[ASSIGN]))); + return new Expr(curLine, ASSIGN, prefix, new Expr(curLine, tok - 1, prefix, parseMaximalExpr(null, precedence[ASSIGN]))); + case COMMA: pushBackToken();return prefix; + + case THIS: + if (prefix != null) { pushBackToken(); return prefix; } + return new Expr(curLine, THIS); + + case SUB: + if (prefix == null && peekToken() == NUMBER) { + getToken(); + return new Expr(curLine, new Double(number.doubleValue() * -1)); + } // else fall through case BITOR: case BITXOR: case BITAND: case SHEQ: case SHNE: case LSH: - case RSH: case URSH: case ADD: case SUB: case MUL: case DIV: case MOD: - case COMMA: case ASSIGN: case GT: case GE: case OR: case AND: + case RSH: case URSH: case ADD: case MUL: case DIV: case MOD: + case ASSIGN: case GT: case GE: case OR: case AND: case EQ: case NE: case LT: case LE: - return new Expr(tok, prefix, parseMaximalExpr(null, precedence[tok])); - - case DOT: - e1 = parseMaximalExpr(null, precedence[tok]); - if (e1.code == NAME) e1.code = STRING; - else throw new Error("argument to DOT must be a NAME"); - return new Expr(DOT, prefix, e1); + if (prefix == null) throw new ParserException("the " + codeToString[tok] + " token cannot start an expression"); + return new Expr(curLine, tok, prefix, parseMaximalExpr(null, precedence[tok])); + + case DOT: { + //e1 = parseMaximalExpr(null, precedence[DOT]); + //if (e1.code == NAME) e1.code = STRING; + //else throw new ParserException("argument to DOT must be a NAME; got a " + codeToString[e1.code]); + //return new Expr(curLine, DOT, prefix, e1); + tok = getToken(); + if (tok != NAME) throw new ParserException("DOT must be followed by a NAME"); + return new Expr(curLine, DOT, prefix, new Expr(curLine, STRING, string)); + } - case BITNOT: case INSTANCEOF: - if (prefix != null) throw new Error("didn't expect non-null prefix!"); - return new Expr(tok, parseMaximalExpr(null, precedence[tok])); + case BANG: case BITNOT: case INSTANCEOF: case TYPEOF: + if (prefix != null) { pushBackToken(); return prefix; } + return new Expr(curLine, tok, parseMaximalExpr(null, precedence[tok])); case INC: case DEC: if (prefix == null) { // prefix e1 = parseMaximalExpr(null, precedence[tok]); - return new Expr(ASSIGN, e1, new Expr(tok == INC ? ADD : SUB, e1, new Expr(new Integer(1)))); + return new Expr(curLine, ASSIGN, e1, new Expr(curLine, tok == INC ? ADD : SUB, e1, new Expr(curLine, new Integer(1)))); } else { // postfix - return new Expr(tok, prefix); + return new Expr(curLine, tok, prefix); } case LP: @@ -182,57 +235,62 @@ public class Parser extends Lexer { if (head == null) head = tail = e; else tail = tail.next = e; tok = getToken(); if (tok == RP) { pushBackToken(); break; } - if (tok != COMMA) throw new Error("expected comma or right paren, got " + codeToString[tok]); + if (tok != COMMA) throw new ParserException("expected comma or right paren, got " + codeToString[tok]); } getToken(); - return new Expr(LP, prefix, head); + return new Expr(curLine, LP, prefix, head); } case LB: if (prefix != null) { // subscripting e1 = parseMaximalExpr(); - if (getToken() != RB) throw new Error("expected a right brace"); - return new Expr(DOT, prefix, e1); + if (getToken() != RB) throw new ParserException("expected a right brace"); + return new Expr(curLine, DOT, prefix, e1); } else { // array ctor - tok = getToken(); while(true) { - if (tok == RB) return new Expr(LB, prefix, head); - if (head == null) head = tail = parseMaximalExpr(); - else tail = tail.next = parseMaximalExpr(); + if (peekToken() == RB) { getToken(); return new Expr(curLine, LB, prefix, head); } + Expr eee = parseMaximalExpr(); + if (eee != null) { + if (head == null) head = tail = eee; + else tail.next = tail = eee; + } tok = getToken(); - if (tok != COMMA && tok != RP) throw new Error("expected right bracket or comma"); + if (tok == RB) return new Expr(curLine, LB, prefix, head); + if (tok != COMMA) throw new ParserException("expected right bracket or comma"); } } case LC: - if (prefix != null) throw new Error("didn't expect non-null prefix"); + if (prefix != null) { pushBackToken(); return prefix; } tok = getToken(); + if (tok == RC) return new Expr(curLine, RC, head); while(true) { - if (tok == RC) return new Expr(RC, head); - if (tok != NAME) throw new Error("expecting name"); - expect(NAME); getToken(); - Expr name = new Expr(NAME, string); - if (tok != COLON) throw new Error("expecting colon"); - e1 = new Expr(COLON, name, parseMaximalExpr()); + if (tok != NAME && tok != STRING) throw new ParserException("expecting name, got " + codeToString[tok]); + Expr name = new Expr(curLine, NAME, string); + if (getToken() != COLON) throw new ParserException("expecting colon"); + e1 = new Expr(curLine, COLON, name, parseMaximalExpr()); if (head == null) head = tail = e1; else tail = tail.next = e1; tok = getToken(); - if (tok != COMMA && tok != RP) throw new Error("expected right curly or comma"); + if (tok != COMMA && tok != RC) throw new ParserException("expected right curly or comma, got " + codeToString[tok]); + if (tok == RC) return new Expr(curLine, RC, head); + tok = getToken(); + if (tok == RC) return new Expr(curLine, RC, head); } case HOOK: e2 = parseMaximalExpr(); - if (getToken() != COLON) throw new Error("expected colon to close ?: expression"); + if (getToken() != COLON) throw new ParserException("expected colon to close ?: expression"); e3 = parseMaximalExpr(); - return new Expr(HOOK, prefix, new Expr(ELSE, e2, e3)); + return new Expr(curLine, HOOK, prefix, new Expr(curLine, ELSE, e2, e3)); case SWITCH: { - if (prefix != null) throw new Error("didn't expect non-null prefix"); - if (getToken() != LP) throw new Error("expected left paren"); + if (prefix != null) { pushBackToken(); return prefix; } + if (getToken() != LP) throw new ParserException("expected left paren"); Expr switchExpr = parseMaximalExpr(); - if (getToken() != RP) throw new Error("expected left paren"); - if (getToken() != LC) throw new Error("expected left brace"); + if (getToken() != RP) throw new ParserException("expected left paren"); + if (getToken() != LC) throw new ParserException("expected left brace"); Expr firstExpr = null; Expr lastExpr = null; while(true) { @@ -241,41 +299,47 @@ public class Parser extends Lexer { if (tok == DEFAULT) { caseExpr = null; } else if (tok == CASE) { + // FIXME: we don't support non-brace-enclosed CASE blocks caseExpr = parseMaximalExpr(); } else { - throw new Error("expected CASE"); + throw new ParserException("expected CASE"); } expect(COLON); getToken(); - e1 = new Expr(tok, caseExpr, parseBlock(false)); - if (lastExpr == null) firstExpr = e1; - else lastExpr.next = e1; + head = tail = null; + while(peekToken() != CASE && peekToken() != DEFAULT && peekToken() != RC) { + e1 = parseBlock(false); + if (e1 == null) break; + if (head == null) head = tail = e1; else tail = tail.next = e1; + } + e1 = new Expr(curLine, tok, caseExpr, new Expr(curLine, LC, head)); + if (lastExpr == null) firstExpr = e1; else lastExpr.next = e1; lastExpr = e1; - if (getToken() == RC) return new Expr(SWITCH, switchExpr, firstExpr); + if (peekToken() == RC) {getToken(); return new Expr(curLine, SWITCH, switchExpr, firstExpr); } } } case FUNCTION: { - if (prefix != null) throw new Error("didn't expect non-null prefix"); - if (getToken() != LP) throw new Error("function keyword must be followed by a left paren"); + if (prefix != null) { pushBackToken(); return prefix; } + if (getToken() != LP) throw new ParserException("function keyword must be followed by a left paren"); Expr formalArgs = null, cur = null; tok = getToken(); while(tok != RP) { - if (tok != NAME) throw new Error("expected a variable name"); - if (cur == null) { formalArgs = cur = new Expr(string); } - else { cur.next = new Expr(NAME, string); cur = cur.next; } + if (tok != NAME) throw new ParserException("expected a variable name"); + if (cur == null) { formalArgs = cur = new Expr(curLine, string); } + else { cur.next = new Expr(curLine, NAME, string); cur = cur.next; } tok = getToken(); if (tok == RP) break; - if (tok != COMMA) throw new Error("function argument list must consist of alternating NAMEs and COMMAs"); + if (tok != COMMA) throw new ParserException("function argument list must consist of alternating NAMEs and COMMAs"); tok = getToken(); } - return new Expr(FUNCTION, formalArgs, parseBlock(true)); + return new Expr(curLine, FUNCTION, formalArgs, parseBlock(true)); } case VAR: - if (prefix != null) throw new Error("didn't expect non-null prefix"); + if (prefix != null) { pushBackToken(); return prefix; } while(true) { - if (getToken() != NAME) throw new Error("variable declarations must start with a variable name"); - Expr name = new Expr(NAME, string); + if (getToken() != NAME) throw new ParserException("variable declarations must start with a variable name"); + Expr name = new Expr(curLine, NAME, string); Expr initVal = null; tok = peekToken(); Expr e = null; @@ -283,84 +347,88 @@ public class Parser extends Lexer { getToken(); initVal = parseMaximalExpr(); tok = peekToken(); - e = new Expr(ASSIGN, name, initVal); + e = new Expr(curLine, ASSIGN, name, initVal); } else { - e = new Expr(NAME, name); + e = new Expr(curLine, NAME, name); } if (head == null) head = tail = e; else tail = tail.next = e; if (tok != COMMA) break; getToken(); } - return new Expr(VAR, head); + return new Expr(curLine, VAR, head); case TRY: { // We deliberately allow you to omit braces in catch{}/finally{} if they are single statements... - if (prefix != null) throw new Error("didn't expect non-null prefix"); + if (prefix != null) { pushBackToken(); return prefix; } Expr tryBlock = parseBlock(true); tok = peekToken(); if (tok == CATCH) { getToken(); - if (getToken() != LP) throw new Error("expected ("); - if (getToken() != NAME) throw new Error("expected name"); - Expr name = new Expr(NAME, string); - if (getToken() != RP) throw new Error("expected )"); - head = tail = new Expr(CATCH, name, parseBlock(false)); + if (getToken() != LP) throw new ParserException("expected ("); + if (getToken() != NAME) throw new ParserException("expected name"); + Expr name = new Expr(curLine, NAME, string); + if (getToken() != RP) throw new ParserException("expected )"); + head = tail = new Expr(curLine, CATCH, name, parseBlock(false)); tok = peekToken(); } if (tok == FINALLY) { getToken(); - e1 = new Expr(FINALLY, parseBlock(false)); + e1 = new Expr(curLine, FINALLY, parseBlock(false)); if (head == null) head = tail = e1; else tail = tail.next = e1; } - if (head == null) throw new Error("try without catch or finally"); - return new Expr(TRY, tryBlock, head); + if (head == null) throw new ParserException("try without catch or finally"); + return new Expr(curLine, TRY, tryBlock, head); } case IF: case WHILE: { - if (prefix != null) throw new Error("didn't expect non-null prefix"); - if (getToken() != LP) throw new Error("expected left paren"); + if (prefix != null) { pushBackToken(); return prefix; } + if (getToken() != LP) throw new ParserException("expected left paren"); Expr parenExpr = parseMaximalExpr(); int t; - if ((t = getToken()) != RP) throw new Error("expected right paren, but got " + codeToString[t]); + if ((t = getToken()) != RP) throw new ParserException("expected right paren, but got " + codeToString[t]); Expr firstBlock = parseBlock(false); if (tok == IF && peekToken() == ELSE) { getToken(); - return new Expr(tok, parenExpr, new Expr(ELSE, firstBlock, parseBlock(false))); + return new Expr(curLine, tok, parenExpr, new Expr(curLine, ELSE, firstBlock, parseBlock(false))); + } else if (tok == IF) { + return new Expr(curLine, tok, parenExpr, new Expr(curLine, ELSE, firstBlock, new Expr(curLine, LC))); + } else if (tok == WHILE) { + return new Expr(curLine, tok, parenExpr, firstBlock); } - return new Expr(tok, parenExpr, firstBlock); } - case IN: return prefix; + case IN: pushBackToken(); return prefix; + case FOR: - if (prefix != null) throw new Error("didn't expect non-null prefix"); - if (getToken() != LP) throw new Error("expected left paren"); + if (prefix != null) { pushBackToken(); return prefix; } + if (getToken() != LP) throw new ParserException("expected left paren"); e1 = parseMaximalExpr(null, -1); - if (e1.code == NAME && peekToken() == IN) { + if (peekToken() == IN) { getToken(); e2 = parseMaximalExpr(null, -1); - if (getToken() != RP) throw new Error("expected right paren"); - return new Expr(FOR, new Expr(IN, e1, e2), parseBlock(false)); + if (getToken() != RP) throw new ParserException("expected right paren"); + return new Expr(curLine, FOR, new Expr(curLine, IN, e1, e2), parseBlock(false)); } else { - if (getToken() != SEMI) throw new Error("expected ;"); - e2 = parseMaximalExpr(null, -1); - if (getToken() != SEMI) throw new Error("expected ;"); - e3 = parseMaximalExpr(null, -1); - if (getToken() != RP) throw new Error("expected right paren"); - return new Expr(LC, e1, new Expr(WHILE, e2, new Expr(LC, parseBlock(false), e3))); + if (getToken() != SEMI) throw new ParserException("expected ;"); + e2 = parseMaximalExpr(); + if (getToken() != SEMI) throw new ParserException("expected ;"); + e3 = parseMaximalExpr(); + if (getToken() != RP) throw new ParserException("expected right paren"); + return new Expr(curLine, LC, e1, new Expr(curLine, WHILE, e2, new Expr(curLine, LC, parseBlock(false), e3))); } case DO: { - if (prefix != null) throw new Error("didn't expect non-null prefix"); + if (prefix != null) { pushBackToken(); return prefix; } Expr firstBlock = parseBlock(false); - if (getToken() != WHILE) throw new Error("expecting WHILE"); - if (getToken() != LP) throw new Error("expected left paren"); + if (getToken() != WHILE) throw new ParserException("expecting WHILE"); + if (getToken() != LP) throw new ParserException("expected left paren"); Expr whileExpr = parseMaximalExpr(); - if (getToken() != RP) throw new Error("expected right paren"); - if (getToken() != SEMI) throw new Error("semicolon"); - return new Expr(DO, firstBlock, whileExpr); + if (getToken() != RP) throw new ParserException("expected right paren"); + if (getToken() != SEMI) throw new ParserException("semicolon"); + return new Expr(curLine, DO, firstBlock, whileExpr); } default: @@ -369,5 +437,362 @@ public class Parser extends Lexer { } } -} + // 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> 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