From ce05c727a51c093b14816a88c0cdd33951396d93 Mon Sep 17 00:00:00 2001 From: megacz Date: Fri, 30 Jan 2004 06:59:45 +0000 Subject: [PATCH] 2003/05/03 03:18:55 darcs-hash:20040130065945-2ba56-821c9f02ff6ad21cf9d8699ede2f6ae79d9576e8.gz --- src/org/xwt/js/JS.java | 105 +++++++++--------- src/org/xwt/js/Parser.java | 256 +++++++++++++++++++++++--------------------- 2 files changed, 184 insertions(+), 177 deletions(-) diff --git a/src/org/xwt/js/JS.java b/src/org/xwt/js/JS.java index f086de6..1da4c54 100644 --- a/src/org/xwt/js/JS.java +++ b/src/org/xwt/js/JS.java @@ -1,52 +1,53 @@ -// Copyright 2002 Adam Megacz, see the COPYING file for licensing [GPL] - -package org.xwt.js; -import org.xwt.util.*; - -/** all objects other than Strings and Numbers which are exposed to JS code must implement this interface */ -public interface JS { - - public Object get(Object key) throws JS.Exn; - public Object put(Object key, Object val) throws JS.Exn; - public Object[] enumerateProperties(); - public String coerceToString() throws JS.Exn; - public Num coerceToNumber() throws JS.Exn; - public Object call(Object[] args) throws JS.Exn; - - /** if JS calls a Java method, and the Java method throws an exception, it can only be caught by JS if it is a subclass of Exn. */ - public static class Exn extends RuntimeException { - private Object js = null; - public Exn(Object js) { this.js = js; } - public Object getObject() { return js; } - } - - /** Any object which becomes part of the scope chain must support this interface */ - public static interface Scope extends JS { - public boolean has(Object key); - public JS getParentScope(); - } - - /** A mutable, boxed numeric value. These are recycled -- never duplicate references -- use duplicate() instead. */ - public static class Num implements Cloneable, JS { - - private Num() { } - - public boolean isDouble = false; - public long longVal = -1; - public double doubleVal = -1; - - private static Vec pool = new Vec(); - public static synchronized void recycle(Num n) { pool.push(n); } - public static synchronized Num getOne() { return (pool.size() > 0) ? (Num)pool.pop() : new Num(); } - - public Num duplicate() { try { return (Num)clone(); } catch (CloneNotSupportedException c) { throw new Error(c); } } - - public Object get(Object key) throws JS.Exn { return null; } - public Object put(Object key, Object val) throws JS.Exn { throw new JS.Exn("attempt to set a property on a Number"); } - public Object[] enumerateProperties() { return new Object[] { }; } - public String coerceToString() throws JS.Exn { return isDouble ? String.valueOf(doubleVal) : String.valueOf(longVal); } - public Num coerceToNumber() throws JS.Exn { return duplicate(); } - public Object call(Object[] args) throws JS.Exn { throw new JS.Exn("attempt to apply the () operator to a Number"); } - - } -} +// Copyright 2002 Adam Megacz, see the COPYING file for licensing [GPL] + +package org.xwt.js; +import org.xwt.util.*; + +/** all objects other than Strings and Numbers which are exposed to JS code must implement this interface */ +public interface JS { + + public Object get(Object key) throws JS.Exn; + public Object put(Object key, Object val) throws JS.Exn; + public Object[] enumerateProperties(); + public String coerceToString() throws JS.Exn; + public Num coerceToNumber() throws JS.Exn; + public Object call(Object[] args) throws JS.Exn; + + /** if JS calls a Java method, and the Java method throws an exception, it can only be caught by JS if it is a subclass of Exn. */ + public static class Exn extends RuntimeException { + private Object js = null; + public Exn(Object js) { this.js = js; } + public Object getObject() { return js; } + } + + /** Any object which becomes part of the scope chain must support this interface */ + public static interface Scope extends JS { + public boolean has(Object key); + public void declare(String s); + public JS getParentScope(); + } + + /** A mutable, boxed numeric value. These are recycled -- never duplicate references -- use duplicate() instead. */ + public static class Num implements Cloneable, JS { + + private Num() { } + + public boolean isDouble = false; + public long longVal = -1; + public double doubleVal = -1; + + private static Vec pool = new Vec(); + public static synchronized void recycle(Num n) { pool.push(n); } + public static synchronized Num getOne() { return (pool.size() > 0) ? (Num)pool.pop() : new Num(); } + + public Num duplicate() { try { return (Num)clone(); } catch (CloneNotSupportedException c) { throw new Error(c); } } + + public Object get(Object key) throws JS.Exn { return null; } + public Object put(Object key, Object val) throws JS.Exn { throw new JS.Exn("attempt to set a property on a Number"); } + public Object[] enumerateProperties() { return new Object[] { }; } + public String coerceToString() throws JS.Exn { return isDouble ? String.valueOf(doubleVal) : String.valueOf(longVal); } + public Num coerceToNumber() throws JS.Exn { return duplicate(); } + public Object call(Object[] args) throws JS.Exn { throw new JS.Exn("attempt to apply the () operator to a Number"); } + + } +} diff --git a/src/org/xwt/js/Parser.java b/src/org/xwt/js/Parser.java index a7f29b7..0535d50 100644 --- a/src/org/xwt/js/Parser.java +++ b/src/org/xwt/js/Parser.java @@ -1,10 +1,16 @@ +// Copyright 2002 Adam Megacz, see the COPYING file for licensing [GPL] package org.xwt.js; + import org.xwt.util.*; import java.io.*; -// FIXME: for..in +/** parses a stream of lexed tokens into a tree of Expr's */ public class Parser extends Lexer { + // Constructors ////////////////////////////////////////////////////// + + public Parser(Reader r) throws IOException { super(r); } + public static void main(String[] s) throws Exception { Parser p = new Parser(new InputStreamReader(System.in)); while(true) { @@ -15,24 +21,46 @@ public class Parser extends Lexer { } } - public Parser(Reader r) throws IOException { super(r); } - private Parser skipToken() throws IOException { getToken(); return this; } - + + // Statics //////////////////////////////////////////////////////////// + + static byte[] precedence = new byte[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; + } + + + // Useful Types ///////////////////////////////////////////////////////// + /** sorta like gcc trees */ public static class Expr { + int code = -1; Expr left = null; Expr right = null; - Expr extra = null; - 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; icode */ + 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])); } - // called after each parseExpr(); returns null if we can't make the expression any bigger - public Expr parseExpr() throws IOException { return parseExpr_(null, 0); } - public Expr parseExpr(Expr prefix, int minPrecedence) throws IOException { + /** parses the largest possible expression */ + public Expr parseMaximalExpr() throws IOException { return parseMaximalExpr(null, -1); } + public Expr parseMaximalExpr(Expr prefix, int minPrecedence) throws IOException { Expr save = null; do { save = prefix; if (peekToken() == -1) break; - prefix = parseExpr_(prefix, minPrecedence); - if (prefix == null) throw new Error("parseExpr_() returned null"); + prefix = parseSingleExpr(prefix, minPrecedence); + if (prefix == null) throw new Error("parseSingleExpr_() returned null"); } while (save != prefix); return prefix; } - public Expr parseExpr_(Expr prefix, int minPrecedence) throws IOException { - Expr e1 = null; - Expr e2 = null; - Expr e3 = null; - Expr head = null; - Expr tail = null; - Expr ret = null; - int tok = getToken(); + /** parses the smallest possible complete expression */ + public Expr parseSingleExpr() throws IOException { return parseSingleExpr(null, 0); } - if (minPrecedence != -1 && minPrecedence != 0 && tok < precedence.length && precedence[tok] != 0 && precedence[tok] <= minPrecedence) { - pushBackToken(); - return prefix; - } + /** parses the smallest possible complete expression beginning with prefix and only using operators with at least minPrecedence */ + public Expr parseSingleExpr(Expr prefix, int minPrecedence) throws IOException { + 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; + getToken(); // 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 COMMA: case ASSIGN: case GT: case GE: case OR: case AND: case BITOR: case BITXOR: case BITAND: case EQ: case NE: case LT: case LE: case SHEQ: case SHNE: case LSH: case RSH: case URSH: case ADD: case SUB: case MUL: case DIV: case MOD: case DOT: - return new Expr(tok, prefix, parseExpr(null, precedence[tok])); + return new Expr(tok, prefix, parseMaximalExpr(null, precedence[tok])); case BITNOT: case INSTANCEOF: - return new Expr(tok, parseExpr(null, precedence[tok])); + if (prefix != null) throw new Error("didn't expect non-null prefix!"); + return new Expr(tok, parseMaximalExpr(null, precedence[tok])); - // FIXME: this isn't 100% right case INC: case DEC: - return new Expr(tok, prefix, (tok == INC || tok == DEC) ? null : parseExpr()); + if (prefix == null) { + // prefix + return new Expr(tok, parseMaximalExpr(null, precedence[tok])); + } else { + // postfix + return new Expr(tok, null, prefix); + } case LP: - if (prefix == null) { - // grouping - Expr r = parseExpr(null, -1); - if ((tok = getToken()) != RP) throw new Error("expected ), got " + codeToString[tok]); + if (prefix == null) { // grouping + Expr r = parseMaximalExpr(); + expect(RP); return r; - } else { - // invocation + } else { // invocation while(peekToken() != RP) { - Expr e = parseExpr(null, precedence[COMMA]); + Expr e = parseMaximalExpr(null, precedence[COMMA]); 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]); } - skipToken(); + getToken(); return new Expr(LP, prefix, head); } case LB: if (prefix != null) { // subscripting - e1 = parseExpr(); + e1 = parseSingleExpr(); if (getToken() != RB) throw new Error("expected a right brace"); return new Expr(LB, prefix, e1); } else { @@ -203,7 +223,7 @@ public class Parser extends Lexer { tok = getToken(); while(true) { if (tok == RB) return new Expr(LB, prefix, head); - if (head == null) head = tail = parseExpr(); else tail = tail.next = parseExpr(); + if (head == null) head = tail = parseSingleExpr(); else tail = tail.next = parseSingleExpr(); tok = getToken(); if (tok != COMMA && tok != RP) throw new Error("expected right bracket or comma"); } @@ -215,31 +235,32 @@ public class Parser extends Lexer { while(true) { if (tok == RC) return new Expr(LC, head); if (tok != NAME) throw new Error("expecting name"); - Expr name = parseExpr(); + Expr name = parseSingleExpr(); if (tok != COLON) throw new Error("expecting colon"); - e1 = new Expr(COLON, name, parseExpr()); + e1 = new Expr(COLON, name, parseSingleExpr()); 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"); } case HOOK: - e2 = parseExpr(); + e2 = parseSingleExpr(); if (getToken() != COLON) throw new Error("expected colon to close ?: expression"); - e3 = parseExpr(); - return new Expr(HOOK, prefix, e2, e3); + e3 = parseSingleExpr(); + e2.next = e3; + return new Expr(HOOK, prefix, e2); case SWITCH: { if (prefix != null) throw new Error("didn't expect non-null prefix"); if (getToken() != LP) throw new Error("expected left paren"); - Expr switchExpr = parseExpr(); + Expr switchExpr = parseSingleExpr(); if (getToken() != RP) throw new Error("expected left paren"); if (getToken() != LC) throw new Error("expected left brace"); Expr firstExpr = null; Expr lastExpr = null; while(true) { if (getToken() != CASE) throw new Error("expected CASE"); - Expr caseExpr = parseExpr(); + Expr caseExpr = parseSingleExpr(); if (getToken() != COLON) throw new Error("expected COLON"); Expr e = new Expr(CASE, caseExpr, parseBlock(false)); if (lastExpr == null) firstExpr = e; @@ -266,12 +287,6 @@ public class Parser extends Lexer { return new Expr(FUNCTION, formalArgs, parseBlock(true)); } - case STRING: - return new Expr(string); - - case NUMBER: - return new Expr(number); - case VAR: if (prefix != null) throw new Error("didn't expect non-null prefix"); while(true) { @@ -281,8 +296,8 @@ public class Parser extends Lexer { tok = peekToken(); Expr e = null; if (tok == ASSIGN) { - skipToken(); - initVal = parseExpr(); + getToken(); + initVal = parseSingleExpr(); tok = peekToken(); e = new Expr(ASSIGN, name, initVal); } else { @@ -290,82 +305,73 @@ public class Parser extends Lexer { } if (head == null) head = tail = e; else tail = tail.next = e; if (tok != COMMA) break; - skipToken(); + getToken(); } return new Expr(VAR, head); - case NULL: - return new Expr(NULL); - - case NAME: - if (prefix != null) { pushBackToken(); return prefix; } - return parseExpr(new Expr(NAME, string), minPrecedence); - - case TRUE: case FALSE: case NOP: - if (prefix != null) throw new Error("didn't expect non-null prefix"); - return new Expr(tok); - case TRY: { - // FIXME: we deliberately allow you to omit braces in catch{}/finally{} if they are single statements... + // 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"); Expr tryBlock = parseBlock(true); - while ((tok = peekToken()) == CATCH) { - skipToken(); + while ((tok = peekToken()) == CATCH || tok == FINALLY) { + getToken(); if (getToken() != LP) throw new Error("expected ("); if (getToken() != NAME) throw new Error("expected name"); - // FIXME: record the name + Expr name = new Expr(NAME, string); if (getToken() != RP) throw new Error("expected )"); - if (head == null) head = tail = parseBlock(false); - else tail = tail.next = parseBlock(false); + e1 = new Expr(tok, name, parseBlock(false)); + if (head == null) head = tail = e1; else tail = tail.next = e1; } - if (head == null) throw new Error("try without catch"); - return new Expr(TRY, tryBlock, head, tok == FINALLY ? skipToken().parseBlock(false) : null); + if (head == null) throw new Error("try without catch or finally"); + return new Expr(TRY, tryBlock, head); } - // FIXME: a.b=c becomes PUT(a,b,c) not ASSIGN(DOT(a,b),c) - 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"); - Expr parenExpr = parseExpr(null, -1); + Expr parenExpr = parseMaximalExpr(null, -1); int t; if ((t = getToken()) != RP) throw new Error("expected right paren, but got " + codeToString[t]); Expr firstBlock = parseBlock(false); - if (tok == IF && peekToken() == ELSE) - return new Expr(tok, parenExpr, firstBlock, skipToken().parseBlock(false)); + if (tok == IF && peekToken() == ELSE) { + getToken(); + firstBlock.next = parseBlock(false); + return new Expr(tok, parenExpr, firstBlock); + } return new Expr(tok, parenExpr, firstBlock); } + case IN: return prefix; case FOR: - // FIXME: for..in if (prefix != null) throw new Error("didn't expect non-null prefix"); if (getToken() != LP) throw new Error("expected left paren"); - e1 = parseExpr(null, -1); - if (getToken() != SEMI) throw new Error("expected ;"); - e2 = parseExpr(null, -1); - if (getToken() != SEMI) throw new Error("expected ;"); - e3 = parseExpr(null, -1); // FIXME: this guy has to be okay with ending via a ) - if (getToken() != RP) throw new Error("expected right paren"); - throw new Error("not yet implemented"); - //return new Expr(FOR, e1, e2, e3, parseBlock(false)); + e1 = parseMaximalExpr(null, -1); + if (e1.code == NAME && 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)); + + } 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))); + } case DO: { if (prefix != null) throw new Error("didn't expect non-null prefix"); Expr firstBlock = parseBlock(false); if (getToken() != WHILE) throw new Error("expecting WHILE"); if (getToken() != LP) throw new Error("expected left paren"); - Expr whileExpr = parseExpr(); + Expr whileExpr = parseSingleExpr(); if (getToken() != RP) throw new Error("expected right paren"); if (getToken() != SEMI) throw new Error("semicolon"); return new Expr(DO, firstBlock, whileExpr); } - case VOID: case RESERVED: - throw new Error("reserved word that you shouldn't be using"); - - case WITH: - throw new Error("WITH not yet implemented"); // FIXME - default: pushBackToken(); return prefix; -- 1.7.10.4