-
-
- /** sorta like gcc trees */
- class Expr {
- int code = -1;
-
- final Expr left;
- final Expr right;
- 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);
- 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.NULL: return null;
- 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.THROW: throw new JS.Exn(left.eval(s));
-
- case Lexer.TRY: {
- boolean safeToExit = false;
- try {
- Object ret = left.eval(s);
- safeToExit = true;
- return ret;
- } catch (JS.Exn e) {
- 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 = list.elementAt(1);
- }
- 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.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.declare(left.string);
- scope.put(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 {
- 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);
- }
- } catch (BreakException b) {
- if (b.label == null || b.label.equals(string)) return null;
- throw (BreakException)b.fillInStackTrace();
- }
- return null;
-
- 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));
-
- 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); }
- }
+ void _parseStatement(JSFunction b, String label) throws IOException {
+ int tok = peekToken();
+ if (tok == -1) return;
+ switch(tok = getToken()) {
+
+ case THROW: case ASSERT: case RETURN: {
+ if (tok == RETURN && peekToken() == SEMI)
+ b.add(parserLine, LITERAL, null);
+ else
+ startExpr(b, -1);
+ b.add(parserLine, tok);
+ consume(SEMI);
+ break;
+ }
+ case BREAK: case CONTINUE: {
+ if (peekToken() == NAME) consume(NAME);
+ b.add(parserLine, tok, string);
+ consume(SEMI);
+ break;
+ }
+ case VAR: {
+ b.add(parserLine, TOPSCOPE); // push the current scope
+ while(true) {
+ consume(NAME);
+ b.add(parserLine, DECLARE, string); // declare it
+ if (peekToken() == ASSIGN) { // if there is an '=' after the variable name
+ consume(ASSIGN);
+ startExpr(b, NO_COMMA);
+ b.add(parserLine, PUT); // assign it
+ b.add(parserLine, POP); // clean the stack
+ } else {
+ b.add(parserLine, POP); // pop the string pushed by declare
+ }
+ if (peekToken() != COMMA) break;
+ consume(COMMA);
+ }
+ b.add(parserLine, POP); // pop off the topscope
+ if ((mostRecentlyReadToken != RC || peekToken() == SEMI) && peekToken() != -1 && mostRecentlyReadToken != SEMI) consume(SEMI);
+ break;
+ }
+ case IF: {
+ consume(LP);
+ startExpr(b, -1);
+ consume(RP);
+
+ b.add(parserLine, JF, JS.ZERO); // if false, jump to the else-block
+ int size = b.size;
+ parseStatement(b, null);
+
+ if (peekToken() == ELSE) {
+ consume(ELSE);
+ b.add(parserLine, JMP, JS.ZERO); // if we took the true-block, jump over the else-block
+ b.set(size - 1, JS.N(b.size - size + 1));
+ size = b.size;
+ parseStatement(b, null);
+ }
+ b.set(size - 1, JS.N(b.size - size + 1)); // regardless of which branch we took, b[size] needs to point here
+ break;
+ }
+ case WHILE: {
+ consume(LP);
+ if (label != null) b.add(parserLine, LABEL, label);
+ b.add(parserLine, LOOP);
+ int size = b.size;
+ b.add(parserLine, POP); // discard the first-iteration indicator
+ startExpr(b, -1);
+ b.add(parserLine, JT, JS.N(2)); // if the while() clause is true, jump over the BREAK
+ b.add(parserLine, BREAK);
+ consume(RP);
+ parseStatement(b, null);
+ b.add(parserLine, CONTINUE); // if we fall out of the end, definately continue
+ b.set(size - 1, JS.N(b.size - size + 1)); // end of the loop
+ break;
+ }
+ case SWITCH: {
+ consume(LP);
+ if (label != null) b.add(parserLine, LABEL, label);
+ b.add(parserLine, LOOP);
+ int size0 = b.size;
+ startExpr(b, -1);
+ consume(RP);
+ consume(LC);
+ while(true)
+ if (peekToken() == CASE) { // we compile CASE statements like a bunch of if..else's
+ consume(CASE);
+ b.add(parserLine, DUP); // duplicate the switch() value; we'll consume one copy
+ startExpr(b, -1);
+ consume(COLON);
+ b.add(parserLine, EQ); // check if we should do this case-block
+ b.add(parserLine, JF, JS.ZERO); // if not, jump to the next one
+ int size = b.size;
+ while(peekToken() != CASE && peekToken() != DEFAULT && peekToken() != RC) parseStatement(b, null);
+ b.set(size - 1, JS.N(1 + b.size - size));
+ } else if (peekToken() == DEFAULT) {
+ consume(DEFAULT);
+ consume(COLON);
+ while(peekToken() != CASE && peekToken() != DEFAULT && peekToken() != RC) parseStatement(b, null);
+ } else if (peekToken() == RC) {
+ consume(RC);
+ b.add(parserLine, BREAK); // break out of the loop if we 'fall through'
+ break;
+ } else {
+ throw pe("expected CASE, DEFAULT, or RC; got " + codeToString[peekToken()]);
+ }
+ b.set(size0 - 1, JS.N(b.size - size0 + 1)); // end of the loop
+ break;
+ }
+
+ case DO: {
+ if (label != null) b.add(parserLine, LABEL, label);
+ b.add(parserLine, LOOP);
+ int size = b.size;
+ parseStatement(b, null);
+ consume(WHILE);
+ consume(LP);
+ startExpr(b, -1);
+ b.add(parserLine, JT, JS.N(2)); // check the while() clause; jump over the BREAK if true
+ b.add(parserLine, BREAK);
+ b.add(parserLine, CONTINUE);
+ consume(RP);
+ consume(SEMI);
+ b.set(size - 1, JS.N(b.size - size + 1)); // end of the loop; write this location to the LOOP instruction
+ break;
+ }
+
+ case TRY: {
+ b.add(parserLine, TRY); // try bytecode causes a TryMarker to be pushed
+ int tryInsn = b.size - 1;
+ // parse the expression to be TRYed
+ parseStatement(b, null);
+ // pop the try marker. this is pushed when the TRY bytecode is executed
+ b.add(parserLine, POP);
+ // jump forward to the end of the catch block, start of the finally block
+ b.add(parserLine, JMP);
+ int successJMPInsn = b.size - 1;
+
+ if (peekToken() != CATCH && peekToken() != FINALLY)
+ throw pe("try without catch or finally");
+
+ int catchJMPDistance = -1;
+ if (peekToken() == CATCH) {
+ catchJMPDistance = b.size - tryInsn;
+ String exceptionVar;
+ getToken();
+ consume(LP);
+ consume(NAME);
+ exceptionVar = string;
+ consume(RP);
+ b.add(parserLine, TOPSCOPE); // the exception is on top of the stack; put it to the chosen name
+ b.add(parserLine, SWAP);
+ b.add(parserLine, LITERAL,exceptionVar);
+ b.add(parserLine, SWAP);
+ b.add(parserLine, PUT);
+ b.add(parserLine, POP);
+ b.add(parserLine, POP);
+ parseStatement(b, null);
+ // pop the try and catch markers
+ b.add(parserLine,POP);
+ b.add(parserLine,POP);
+ }
+
+ // jump here if no exception was thrown
+ b.set(successJMPInsn, JS.N(b.size - successJMPInsn));
+
+ int finallyJMPDistance = -1;
+ if (peekToken() == FINALLY) {
+ b.add(parserLine, LITERAL, null); // null FinallyData
+ finallyJMPDistance = b.size - tryInsn;
+ consume(FINALLY);
+ parseStatement(b, null);
+ b.add(parserLine,FINALLY_DONE);
+ }
+
+ // setup the TRY arguments
+ b.set(tryInsn, new int[] { catchJMPDistance, finallyJMPDistance });
+
+ break;
+ }
+
+ case FOR: {
+ consume(LP);
+
+ tok = getToken();
+ boolean hadVar = false; // if it's a for..in, we ignore the VAR
+ if (tok == VAR) { hadVar = true; tok = getToken(); }
+ String varName = string;
+ boolean forIn = peekToken() == IN; // determine if this is a for..in loop or not
+ pushBackToken(tok, varName);
+
+ if (forIn) {
+ b.add(parserLine, NEWSCOPE); // for-loops always create new scopes
+ b.add(parserLine, LITERAL, varName); // declare the new variable
+ b.add(parserLine, DECLARE);
+
+ b.add(parserLine, LOOP); // we actually only add this to ensure that BREAK works
+ b.add(parserLine, POP); // discard the first-iteration indicator
+ int size = b.size;
+ consume(NAME);
+ consume(IN);
+ startExpr(b, -1);
+ b.add(parserLine, PUSHKEYS); // push the keys as an array; check the length
+ b.add(parserLine, LITERAL, "length");
+ b.add(parserLine, GET);
+ consume(RP);
+
+ b.add(parserLine, LITERAL, JS.N(1)); // decrement the length
+ b.add(parserLine, SUB);
+ b.add(parserLine, DUP);
+ b.add(parserLine, LITERAL, JS.ZERO); // see if we've exhausted all the elements
+ b.add(parserLine, LT);
+ b.add(parserLine, JF, JS.N(2));
+ b.add(parserLine, BREAK); // if we have, then BREAK
+ b.add(parserLine, GET_PRESERVE); // get the key out of the keys array
+ b.add(parserLine, LITERAL, varName);
+ b.add(parserLine, PUT); // write it to this[varName]
+ parseStatement(b, null); // do some stuff
+ b.add(parserLine, CONTINUE); // continue if we fall out the bottom
+
+ b.set(size - 1, JS.N(b.size - size + 1)); // BREAK to here
+ b.add(parserLine, OLDSCOPE); // restore the scope
+
+ } else {
+ if (hadVar) pushBackToken(VAR, null); // yeah, this actually matters
+ b.add(parserLine, NEWSCOPE); // grab a fresh scope
+
+ parseStatement(b, null); // initializer
+ JSFunction e2 = // we need to put the incrementor before the test
+ new JSFunction(sourceName, parserLine, null); // so we save the test here
+ if (peekToken() != SEMI)
+ startExpr(e2, -1);
+ else
+ e2.add(parserLine, b.LITERAL, Boolean.TRUE); // handle the for(foo;;foo) case
+ consume(SEMI);
+ if (label != null) b.add(parserLine, LABEL, label);
+ b.add(parserLine, LOOP);
+ int size2 = b.size;
+
+ b.add(parserLine, JT, JS.ZERO); // if we're on the first iteration, jump over the incrementor
+ int size = b.size;
+ if (peekToken() != RP) { // do the increment thing
+ startExpr(b, -1);
+ b.add(parserLine, POP);
+ }
+ b.set(size - 1, JS.N(b.size - size + 1));
+ consume(RP);
+
+ b.paste(e2); // ok, *now* test if we're done yet
+ b.add(parserLine, JT, JS.N(2)); // break out if we don't meet the test
+ b.add(parserLine, BREAK);
+ parseStatement(b, null);
+ b.add(parserLine, CONTINUE); // if we fall out the bottom, CONTINUE
+ b.set(size2 - 1, JS.N(b.size - size2 + 1)); // end of the loop
+
+ b.add(parserLine, OLDSCOPE); // get our scope back
+ }
+ break;
+ }
+
+ case NAME: { // either a label or an identifier; this is the one place we're not LL(1)
+ String possiblyTheLabel = string;
+ if (peekToken() == COLON) { // label
+ consume(COLON);
+ parseStatement(b, possiblyTheLabel);
+ break;
+ } else { // expression
+ pushBackToken(NAME, possiblyTheLabel);
+ startExpr(b, -1);
+ b.add(parserLine, POP);
+ if ((mostRecentlyReadToken != RC || peekToken() == SEMI) && peekToken() != -1 && mostRecentlyReadToken != SEMI) consume(SEMI);
+ break;
+ }
+ }
+
+ case SEMI: return; // yep, the null statement is valid
+
+ case LC: { // blocks are statements too
+ pushBackToken();
+ b.add(parserLine, NEWSCOPE);
+ parseBlock(b, label);
+ b.add(parserLine, OLDSCOPE);
+ break;
+ }
+
+ default: { // hope that it's an expression
+ pushBackToken();
+ startExpr(b, -1);
+ b.add(parserLine, POP);
+ if ((mostRecentlyReadToken != RC || peekToken() == SEMI) && peekToken() != -1 && mostRecentlyReadToken != SEMI) consume(SEMI);
+ break;
+ }
+ }