this.line = line;
}
+ /** for debugging */
public static void main(String[] s) throws Exception {
Parser p = new Parser(new InputStreamReader(System.in), "stdin", 0);
while(true) {
// Parsing Logic /////////////////////////////////////////////////////////
- /** a block is either a single statement or a list of statements surrounded by curly braces; all expressions are also statements */
- public Expr parseBlock(boolean requireBraces) throws IOException {
- Expr ret = null;
- int tok = peekToken();
- if (tok == -1) return null;
- boolean braced = tok == LC;
- if (requireBraces && !braced) throw new ParserException("expected {");
- if (braced) getToken();
- Expr head = null;
- Expr tail = null;
- int curLine = line;
- OUTER: while(true) {
- Expr smt;
-
- switch(tok = peekToken()) {
- case -1: break OUTER;
- case LC: smt = parseBlock(true); break;
- case THROW: case RETURN: case ASSERT:
- getToken();
- if (peekToken() == SEMI) {
- getToken();
- smt = new Expr(curLine, tok);
- break;
- }
- smt = new Expr(curLine, tok, parseMaximalExpr());
- if (getToken() != SEMI) throw new ParserException("expected ;");
- break;
-
- /*
- FIXME
- case NAME: {
- getToken();
- 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();
- break OUTER;
-
- case SEMI:
- getToken();
- if (!braced) break OUTER;
- continue;
-
- default:
- smt = parseMaximalExpr();
- if (smt == null) {
- if (head == null) return null;
- break OUTER;
- }
- if (peekToken() == SEMI) getToken();
- break;
- }
- if (!braced) return smt;
- if (head == null) head = tail = smt; else tail = (tail.next = smt);
- }
- return new Expr(curLine, LC, head);
+ public void consume(int code) throws IOException {
+ int got = getToken();
+ if (got != code) throw new ParserException("expected " + codeToString[code] + ", got " + (got == -1 ? "EOL" : codeToString[got]));
}
-
- /** throws an error if the next token is not <tt>code</tt> */
public void expect(int code) throws IOException {
int got = peekToken();
- if (got != code)
- throw new ParserException("expected " + codeToString[code] + ", got " + (got == -1 ? "EOL" : codeToString[got]));
+ if (got != code) throw new ParserException("expected " + codeToString[code] + ", got " + (got == -1 ? "EOL" : codeToString[got]));
}
/** parses the largest possible expression */
return r;
} else { // invocation
+ ExprList list = new ExprList(curLine, LP);
while(peekToken() != RP) {
- Expr e = parseMaximalExpr();
- if (head == null) head = tail = e; else tail = tail.next = e;
- tok = getToken();
- if (tok == RP) { pushBackToken(); break; }
- if (tok != COMMA) throw new ParserException("expected comma or right paren, got " + codeToString[tok]);
+ list.add(parseMaximalExpr());
+ if (peekToken() == RP) break;
+ consume(COMMA);
}
getToken();
- return new Expr(curLine, LP, prefix, head);
+ return new Expr(curLine, LP, prefix, list);
}
case LB:
- if (prefix != null) {
- // subscripting
- e1 = parseMaximalExpr();
- if (getToken() != RB) throw new ParserException("expected a right brace");
- return new Expr(curLine, DOT, prefix, e1);
- } else {
+ if (prefix == null) {
// array ctor
+ ExprList list = new ExprList(curLine, LB);
while(true) {
- 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;
+ Expr e = parseMaximalExpr();
+ if (e != null) {
+ list.add(e);
+ } else {
+ if (peekToken() == COMMA) list.add(new Expr(curLine, NULL));
}
- tok = getToken();
- if (tok == RB) return new Expr(curLine, LB, prefix, head);
- if (tok != COMMA) throw new ParserException("expected right bracket or comma");
+ if (peekToken() == RB) { consume(RB); return list; }
+ consume(COMMA);
}
+ } else {
+ // subscripting
+ e1 = parseMaximalExpr();
+ if (getToken() != RB) throw new ParserException("expected a right brace");
+ return new Expr(curLine, DOT, prefix, e1);
}
- case LC:
+ case LC: {
+ // object ctor
if (prefix != null) { pushBackToken(); return prefix; }
tok = getToken();
- if (tok == RC) return new Expr(curLine, RC, head);
+ ExprList list = new ExprList(curLine, RC);
+ if (tok == RC) return list;
while(true) {
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;
+ list.add(e1);
tok = getToken();
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);
+ if (tok == RC) return list;
tok = getToken();
- if (tok == RC) return new Expr(curLine, RC, head);
+ if (tok == RC) return list;
}
+ }
case HOOK:
e2 = parseMaximalExpr();
Expr switchExpr = parseMaximalExpr();
if (getToken() != RP) throw new ParserException("expected left paren");
if (getToken() != LC) throw new ParserException("expected left brace");
- Expr firstExpr = null;
- Expr lastExpr = null;
+ ExprList toplevel = new ExprList(curLine, LC);
while(true) {
tok = getToken();
Expr caseExpr;
- if (tok == DEFAULT) {
- caseExpr = null;
- } else if (tok == CASE) {
- // FIXME: we don't support non-brace-enclosed CASE blocks
- caseExpr = parseMaximalExpr();
- } else {
- throw new ParserException("expected CASE");
- }
- expect(COLON); getToken();
- head = tail = null;
+ if (tok == DEFAULT) caseExpr = null;
+ else if (tok == CASE) caseExpr = parseMaximalExpr();
+ else throw new ParserException("expected CASE or DEFAULT");
+ consume(COLON);
+ // FIXME: we shouldn't be creating a scope here
+ ExprList list = new ExprList(curLine, LC);
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;
+ if ((e1 = parseBlock(false)) == null) break;
+ list.add(e1);
}
- e1 = new Expr(curLine, tok, caseExpr, new Expr(curLine, LC, head));
- if (lastExpr == null) firstExpr = e1; else lastExpr.next = e1;
- lastExpr = e1;
- if (peekToken() == RC) {getToken(); return new Expr(curLine, SWITCH, switchExpr, firstExpr); }
+ toplevel.add(new Expr(curLine, tok, caseExpr, list));
+ if (peekToken() == RC) { consume(RC); return new Expr(curLine, SWITCH, switchExpr, toplevel); }
}
}
case FUNCTION: {
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 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 ParserException("function argument list must consist of alternating NAMEs and COMMAs");
- tok = getToken();
+ consume(LP);
+ ExprList list = new ExprList(curLine, LC);
+ if (peekToken() == RP) consume(RP);
+ else while(true) {
+ tok = peekToken();
+ if (tok == COMMA) {
+ consume(COMMA);
+ list.add(new Expr(curLine, NULL));
+ } else {
+ consume(NAME);
+ list.add(new Expr(curLine, NAME, string));
+ if (peekToken() == RP) { consume(RP); break; }
+ consume(COMMA);
+ }
}
- return new Expr(curLine, FUNCTION, formalArgs, parseBlock(true));
+ return new Expr(curLine, FUNCTION, list, parseBlock(true));
}
- case VAR:
+ case VAR: {
if (prefix != null) { pushBackToken(); return prefix; }
+ ExprList list = new ExprList(curLine, VAR);
while(true) {
if (getToken() != NAME) throw new ParserException("variable declarations must start with a variable name");
String name = string;
} else {
e = new Expr(curLine, VAR, new Expr(curLine, name));
}
- if (head == null) head = tail = e; else tail = tail.next = e;
+ list.add(e);
if (tok != COMMA) break;
getToken();
}
- return new Expr(curLine, VAR, head);
+ return list;
+ }
case TRY: {
// We deliberately allow you to omit braces in catch{}/finally{} if they are single statements...
Expr tryBlock = parseBlock(true);
tok = peekToken();
+ ExprList list = new ExprList(curLine, TRY);
if (tok == CATCH) {
getToken();
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));
+ list.add(new Expr(curLine, CATCH, name, parseBlock(false)));
tok = peekToken();
}
if (tok == FINALLY) {
getToken();
- e1 = new Expr(curLine, FINALLY, parseBlock(false));
- if (head == null) head = tail = e1; else tail = tail.next = e1;
+ list.add(new Expr(curLine, FINALLY, parseBlock(false)));
}
- if (head == null) throw new ParserException("try without catch or finally");
- return new Expr(curLine, TRY, tryBlock, head);
+ if (list.size() == 0) throw new ParserException("try without catch or finally");
+ return new Expr(curLine, TRY, tryBlock, list);
}
case IF: case WHILE: {
getToken();
return new Expr(curLine, tok, parenExpr, new Expr(curLine, ELSE, firstBlock, parseBlock(false)));
} else {
- return new Expr(curLine, tok, parenExpr, firstBlock);
+ if (tok == IF) return new Expr(curLine, tok, parenExpr, firstBlock);
+ if (tok == WHILE) {
+ ExprList list = new ExprList(curLine, WHILE);
+ list.add(parenExpr);
+ list.add(firstBlock);
+ list.add(new Expr(curLine, NULL));
+ return list;
+ }
}
}
} else {
Expr initExpr = e1;
+ if (initExpr == null) initExpr = new Expr(curLine, NULL);
expect(SEMI); getToken();
Expr whileExpr = parseMaximalExpr();
expect(SEMI); getToken();
Expr incExpr = parseMaximalExpr();
expect(RP); getToken();
Expr body = parseBlock(false);
- body.next = incExpr;
Expr loop = new Expr(curLine, WHILE, whileExpr, body);
- if (initExpr == null) initExpr = loop; else initExpr.next = loop;
- return new Expr(curLine, LC, initExpr);
+ ExprList list = new ExprList(curLine, LC);
+ list.add(initExpr);
+ ExprList list2 = new ExprList(curLine, WHILE);
+ list.add(list2);
+ list2.add(whileExpr);
+ list2.add(body);
+ list2.add(incExpr);
+ return list;
}
case DO: {
if (prefix != null) { pushBackToken(); return prefix; }
- Expr firstBlock = parseBlock(false);
- 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 ParserException("expected right paren");
- if (getToken() != SEMI) throw new ParserException("semicolon");
- return new Expr(curLine, DO, firstBlock, whileExpr);
+ ExprList list = new ExprList(curLine, DO);
+ Expr body = parseBlock(false);
+ consume(WHILE);
+ consume(LP);
+ list.add(parseMaximalExpr());
+ list.add(body);
+ list.add(new Expr(curLine, NULL));
+ consume(RP);
+ consume(SEMI);
+ return list;
}
default:
// Expr //////////////////////////////////////////////////////////////////////
+ class ExprList extends Expr {
+ Vec v = new Vec();
+ public ExprList(int curLine, int code) { super(curLine, code); }
+ public void add(Expr e) { v.addElement(e); }
+ public int numExprs() { return v.size(); }
+ public int size() { return v.size(); }
+ public Expr elementAt(int i) { return (Expr)v.elementAt(i); }
+ public Object eval(final JS.Scope s) throws ControlTransferException, JS.Exn {
+ switch(code) {
+ case LC: {
+ // Block
+ JS.Scope scope = new JS.Scope(s);
+ for(int i=0; i<v.size(); i++) ((Expr)v.elementAt(i)).eval(scope);
+ return null;
+ }
+ case Lexer.LB: {
+ // Array ctor
+ JS.Array ret = new JS.Array();
+ for(int i=0; i<numExprs(); i++) ret.addElement(elementAt(i).eval(s));
+ return ret;
+ }
+ case Lexer.RC: {
+ // Object ctor
+ JS.Obj ret = new JS.Obj();
+ for(int i=0; i<numExprs(); i++) {
+ Expr e = elementAt(i);
+ Object key = e.left.string;
+ Object val = e.right.eval(s);
+ ret.put(key, val);
+ }
+ return ret;
+ }
+ case Lexer.WHILE:
+ case Lexer.DO:
+ try {
+ boolean first = true;
+ Object bogus = null;
+ ExprList list = this;
+ Expr whileExpr = list.elementAt(0);
+ Expr bodyExpr = list.elementAt(1);
+ Expr incExpr = list.elementAt(2);
+ for(;(first && code == Lexer.DO) || toBoolean(whileExpr.eval(s)); bogus = incExpr.eval(s)) {
+ first = false;
+ try { bodyExpr.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;
+ case Lexer.VAR:
+ for(int i=0; i<size(); i++) {
+ Expr e = elementAt(i);
+ s.declare(e.left.string);
+ if (e.right != null) e.right.eval(s);
+ }
+ return null;
+ default: throw new Error("augh!");
+ }
+ }
+ }
+
+ /** a block is either a single statement or a list of statements surrounded by curly braces; all expressions are also statements */
+ public Expr parseBlock(boolean requireBraces) throws IOException {
+ Expr smt = null;
+ int tok = peekToken();
+ if (tok == -1) return null;
+ boolean braced = tok == LC;
+ if (requireBraces && !braced) throw new ParserException("expected {, got " + codeToString[tok]);
+ if (braced) getToken();
+ int curLine = line;
+ ExprList block = new ExprList(curLine, LC);
+ while(true) {
+ switch(tok = peekToken()) {
+
+ case -1:
+ return block;
+
+ case LC:
+ smt = parseBlock(true); break;
+
+ case THROW: case RETURN: case ASSERT:
+ getToken();
+ if (peekToken() == SEMI) {
+ if (tok == THROW || tok == ASSERT) throw new ParserException(codeToString[tok] + " requires an argument");
+ consume(SEMI);
+ smt = new Expr(curLine, tok);
+ } else {
+ smt = new Expr(curLine, tok, parseMaximalExpr());
+ consume(SEMI);
+ }
+ break;
+
+ 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 (tok == GOTO) {
+ throw new ParserException("goto must be followed by a label");
+ }
+ smt = new Expr(curLine, tok, new Expr(curLine, string));
+ consume(SEMI);
+ break;
+ }
+
+ case RC:
+ if (braced) consume(RC);
+ return block;
+
+ case SEMI:
+ consume(SEMI);
+ if (!braced) return block;
+ continue;
+
+/* FIXME: LL(2)
+ case NAME: {
+ String name = string;
+ getToken();
+ if (peekToken() == COLON) {
+ smt = new Expr(curLine, COLON, new Expr(curLine, name), parseBlock(false));
+ break;
+ } else {
+ pushBackToken(tok);
+ string = name;
+ // fall through to default case
+ }
+ }
+*/
+ default:
+ smt = parseMaximalExpr();
+ if (smt == null) {
+ if (block.numExprs() == 0) return null;
+ return block;
+ }
+ if (peekToken() == SEMI) getToken();
+ break;
+ }
+
+ if (!braced) return smt;
+ block.add(smt);
+ }
+ }
+
+
/** 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";
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;
}
safeToExit = true;
return ret;
} catch (JS.Exn e) {
- Expr c = right;
+ ExprList list = (ExprList)right;
+ Expr c = list.elementAt(0);
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;
+ c = list.elementAt(1);
}
if (c.code == Lexer.FINALLY) {
JS.Scope scope = new JS.Scope(s);
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));
+ for(int i=0; i<((ExprList)right).numExprs(); i++) arguments.addElement(((ExprList)right).elementAt(i).eval(s));
if (f == null) throw new JS.Exn(new EvaluatorException("attempted to call null"));
return f.call(arguments);
}
};
int i = 0;
- for(Expr e = left; e != null; e = e.next) {
- scope.declare(e.string);
- scope.put(e.string, args.get(new Integer(i++)));
+ ExprList list = (ExprList)left;
+ for(i=0; i<list.size(); i++) {
+ scope.declare(list.elementAt(i).string);
+ scope.put(list.elementAt(i).string, args.get(new Integer(i)));
}
try {
return right.eval(scope);
Object switchVal = left.eval(s);
boolean go = false;
try {
- for(Expr e = right; e != null; e = e.next) {
+ ExprList list = (ExprList)right;
+ for(int i=0; i<list.size(); i++) {
+ Expr e = list.elementAt(i);
if (go || e.code == Lexer.DEFAULT || e.left.eval(s).equals(switchVal)) go = true;
if (go) e.right.eval(s);
}
}
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
- JS.Scope scope = new JS.Scope(s);
- for(Expr e = left; e != null; e = e.next) e.eval(scope);
- return null;
-
- case Lexer.RC: { // Object ctor
- JS.Obj ret = new JS.Obj();
- for(Expr e = left; e != null; e = e.next) {
- Object key = e.left.string;
- Object val = e.right.eval(s);
- ret.put(key, val);
- }
- return ret;
- }
-
- case Lexer.VAR:
- for(Expr e = this.left; e != null; e = e.next) {
- s.declare(e.left.string);
- if (e.right != null) e.right.eval(s);
- }
- return null;
-
case Lexer.HOOK: return toBoolean(left.eval(s)) ? right.left.eval(s) : right.right.eval(s);
case Lexer.IF:
if (right.code == ELSE) {
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;
- Object bogus = null;
- for(;(first && code == Lexer.DO) || toBoolean(left.eval(s)); bogus = (right.next == null ? null : right.next.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);
}
}