| /* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
| * |
| * The contents of this file are subject to the Netscape Public |
| * License Version 1.1 (the "License"); you may not use this file |
| * except in compliance with the License. You may obtain a copy of |
| * the License at http://www.mozilla.org/NPL/ |
| * |
| * Software distributed under the License is distributed on an "AS |
| * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr |
| * implied. See the License for the specific language governing |
| * rights and limitations under the License. |
| * |
| * The Original Code is Rhino code, released |
| * May 6, 1999. |
| * |
| * The Initial Developer of the Original Code is Netscape |
| * Communications Corporation. Portions created by Netscape are |
| * Copyright (C) 1997-1999 Netscape Communications Corporation. All |
| * Rights Reserved. |
| * |
| * Contributor(s): |
| * Mike Ang |
| * Mike McCabe |
| * |
| * Alternatively, the contents of this file may be used under the |
| * terms of the GNU Public License (the "GPL"), in which case the |
| * provisions of the GPL are applicable instead of those above. |
| * If you wish to allow use of your version of this file only |
| * under the terms of the GPL and not to allow others to use your |
| * version of this file under the NPL, indicate your decision by |
| * deleting the provisions above and replace them with the notice |
| * and other provisions required by the GPL. If you do not delete |
| * the provisions above, a recipient may use your version of this |
| * file under either the NPL or the GPL. |
| */ |
| // Modified by Google |
| |
| package com.google.gwt.dev.js.rhino; |
| |
| import java.io.IOException; |
| |
| /** |
| * This class implements the JavaScript parser. |
| * |
| * It is based on the C source files jsparse.c and jsparse.h in the jsref |
| * package. |
| * |
| * @see TokenStream |
| * |
| * @author Mike McCabe |
| * @author Brendan Eich |
| */ |
| |
| public class Parser { |
| |
| public Parser(IRFactory nf) { |
| this.nf = nf; |
| } |
| |
| private void mustMatchToken(TokenStream ts, int toMatch, String messageId) |
| throws IOException, JavaScriptException { |
| int tt; |
| if ((tt = ts.getToken()) != toMatch) { |
| reportError(ts, messageId); |
| ts.ungetToken(tt); // In case the parser decides to continue |
| } |
| } |
| |
| private void reportError(TokenStream ts, String messageId) |
| throws JavaScriptException { |
| this.ok = false; |
| ts.reportSyntaxError(messageId, null); |
| |
| /* |
| * Throw an exception to unwind the recursive descent parse. We use |
| * JavaScriptException here even though it is really a different use of the |
| * exception than it is usually used for. |
| */ |
| throw new JavaScriptException(messageId); |
| } |
| |
| /* |
| * Build a parse tree from the given TokenStream. |
| * |
| * @param ts the TokenStream to parse |
| * |
| * @return an Object representing the parsed program. If the parse fails, null |
| * will be returned. (The parse failure will result in a call to the current |
| * Context's ErrorReporter.) |
| */ |
| public Object parse(TokenStream ts) throws IOException { |
| this.ok = true; |
| sourceTop = 0; |
| functionNumber = 0; |
| |
| int tt; // last token from getToken(); |
| int baseLineno = ts.getLineno(); // line number where source starts |
| |
| /* |
| * so we have something to add nodes to until we've collected all the source |
| */ |
| Object tempBlock = nf.createLeaf(TokenStream.BLOCK); |
| ((Node) tempBlock).setIsSyntheticBlock(true); |
| |
| while (true) { |
| ts.flags |= ts.TSF_REGEXP; |
| tt = ts.getToken(); |
| ts.flags &= ~ts.TSF_REGEXP; |
| |
| if (tt <= ts.EOF) { |
| break; |
| } |
| |
| if (tt == ts.FUNCTION) { |
| try { |
| nf.addChildToBack(tempBlock, function(ts, false)); |
| } catch (JavaScriptException e) { |
| this.ok = false; |
| break; |
| } |
| } else { |
| ts.ungetToken(tt); |
| nf.addChildToBack(tempBlock, statement(ts)); |
| } |
| } |
| |
| if (!this.ok) { |
| // XXX ts.clearPushback() call here? |
| return null; |
| } |
| |
| Object pn = nf.createScript(tempBlock, ts.getSourceName(), baseLineno, ts |
| .getLineno(), sourceToString(0)); |
| ((Node) pn).setIsSyntheticBlock(true); |
| return pn; |
| } |
| |
| /* |
| * The C version of this function takes an argument list, which doesn't seem |
| * to be needed for tree generation... it'd only be useful for checking |
| * argument hiding, which I'm not doing anyway... |
| */ |
| private Object parseFunctionBody(TokenStream ts) throws IOException { |
| int oldflags = ts.flags; |
| ts.flags &= ~(TokenStream.TSF_RETURN_EXPR | TokenStream.TSF_RETURN_VOID); |
| ts.flags |= TokenStream.TSF_FUNCTION; |
| |
| Object pn = nf.createBlock(ts.getLineno()); |
| try { |
| int tt; |
| while ((tt = ts.peekToken()) > ts.EOF && tt != ts.RC) { |
| if (tt == TokenStream.FUNCTION) { |
| ts.getToken(); |
| nf.addChildToBack(pn, function(ts, false)); |
| } else { |
| nf.addChildToBack(pn, statement(ts)); |
| } |
| } |
| } catch (JavaScriptException e) { |
| this.ok = false; |
| } finally { |
| // also in finally block: |
| // flushNewLines, clearPushback. |
| |
| ts.flags = oldflags; |
| } |
| |
| return pn; |
| } |
| |
| private Object function(TokenStream ts, boolean isExpr) throws IOException, |
| JavaScriptException { |
| int baseLineno = ts.getLineno(); // line number where source starts |
| |
| String name; |
| Object memberExprNode = null; |
| if (ts.matchToken(ts.NAME)) { |
| name = ts.getString(); |
| if (!ts.matchToken(ts.LP)) { |
| if (Context.getContext().hasFeature( |
| Context.FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME)) { |
| // Extension to ECMA: if 'function <name>' does not follow |
| // by '(', assume <name> starts memberExpr |
| sourceAddString(ts.NAME, name); |
| Object memberExprHead = nf.createName(name); |
| name = null; |
| memberExprNode = memberExprTail(ts, false, memberExprHead); |
| } |
| mustMatchToken(ts, ts.LP, "msg.no.paren.parms"); |
| } |
| } else if (ts.matchToken(ts.LP)) { |
| // Anonymous function |
| name = null; |
| } else { |
| name = null; |
| if (Context.getContext().hasFeature( |
| Context.FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME)) { |
| // Note that memberExpr can not start with '(' like |
| // in (1+2).toString, because 'function (' already |
| // processed as anonymous function |
| memberExprNode = memberExpr(ts, false); |
| } |
| mustMatchToken(ts, ts.LP, "msg.no.paren.parms"); |
| } |
| |
| if (memberExprNode != null) { |
| // transform 'function' <memberExpr> to <memberExpr> = function |
| // even in the decompilated source |
| sourceAdd((char) ts.ASSIGN); |
| sourceAdd((char) ts.NOP); |
| } |
| |
| // save a reference to the function in the enclosing source. |
| sourceAdd((char) ts.FUNCTION); |
| sourceAdd((char) functionNumber); |
| ++functionNumber; |
| |
| // Save current source top to restore it on exit not to include |
| // function to parent source |
| int savedSourceTop = sourceTop; |
| int savedFunctionNumber = functionNumber; |
| Object args; |
| Object body; |
| String source; |
| try { |
| functionNumber = 0; |
| |
| // FUNCTION as the first token in a Source means it's a function |
| // definition, and not a reference. |
| sourceAdd((char) ts.FUNCTION); |
| if (name != null) { |
| sourceAddString(ts.NAME, name); |
| } |
| sourceAdd((char) ts.LP); |
| args = nf.createLeaf(ts.LP); |
| |
| if (!ts.matchToken(ts.GWT)) { |
| boolean first = true; |
| do { |
| if (!first) |
| sourceAdd((char) ts.COMMA); |
| first = false; |
| mustMatchToken(ts, ts.NAME, "msg.no.parm"); |
| String s = ts.getString(); |
| nf.addChildToBack(args, nf.createName(s)); |
| |
| sourceAddString(ts.NAME, s); |
| } while (ts.matchToken(ts.COMMA)); |
| |
| mustMatchToken(ts, ts.GWT, "msg.no.paren.after.parms"); |
| } |
| sourceAdd((char) ts.GWT); |
| |
| mustMatchToken(ts, ts.LC, "msg.no.brace.body"); |
| sourceAdd((char) ts.LC); |
| sourceAdd((char) ts.EOL); |
| body = parseFunctionBody(ts); |
| mustMatchToken(ts, ts.RC, "msg.no.brace.after.body"); |
| sourceAdd((char) ts.RC); |
| // skip the last EOL so nested functions work... |
| |
| // name might be null; |
| source = sourceToString(savedSourceTop); |
| } finally { |
| sourceTop = savedSourceTop; |
| functionNumber = savedFunctionNumber; |
| } |
| |
| Object pn = nf.createFunction(name, args, body, ts.getSourceName(), |
| baseLineno, ts.getLineno(), source, isExpr || memberExprNode != null); |
| if (memberExprNode != null) { |
| pn = nf.createBinary(ts.ASSIGN, ts.NOP, memberExprNode, pn); |
| } |
| |
| // Add EOL but only if function is not part of expression, in which |
| // case it gets SEMI + EOL from Statement. |
| if (!isExpr) { |
| if (memberExprNode != null) { |
| // Add ';' to make 'function x.f(){}' and 'x.f = function(){}' |
| // to print the same strings when decompiling |
| sourceAdd((char) ts.SEMI); |
| } |
| sourceAdd((char) ts.EOL); |
| wellTerminated(ts, ts.FUNCTION); |
| } |
| |
| return pn; |
| } |
| |
| private Object statements(TokenStream ts) throws IOException { |
| Object pn = nf.createBlock(ts.getLineno()); |
| |
| int tt; |
| while ((tt = ts.peekToken()) > ts.EOF && tt != ts.RC) { |
| nf.addChildToBack(pn, statement(ts)); |
| } |
| |
| return pn; |
| } |
| |
| private Object condition(TokenStream ts) throws IOException, |
| JavaScriptException { |
| Object pn; |
| mustMatchToken(ts, ts.LP, "msg.no.paren.cond"); |
| sourceAdd((char) ts.LP); |
| pn = expr(ts, false); |
| mustMatchToken(ts, ts.GWT, "msg.no.paren.after.cond"); |
| sourceAdd((char) ts.GWT); |
| |
| // there's a check here in jsparse.c that corrects = to == |
| |
| return pn; |
| } |
| |
| private boolean wellTerminated(TokenStream ts, int lastExprType) |
| throws IOException, JavaScriptException { |
| int tt = ts.peekTokenSameLine(); |
| if (tt == ts.ERROR) { |
| return false; |
| } |
| |
| if (tt != ts.EOF && tt != ts.EOL && tt != ts.SEMI && tt != ts.RC) { |
| int version = Context.getContext().getLanguageVersion(); |
| if ((tt == ts.FUNCTION || lastExprType == ts.FUNCTION) |
| && (version < Context.VERSION_1_2)) { |
| /* |
| * Checking against version < 1.2 and version >= 1.0 in the above line |
| * breaks old javascript, so we keep it this way for now... XXX warning |
| * needed? |
| */ |
| return true; |
| } else { |
| reportError(ts, "msg.no.semi.stmt"); |
| } |
| } |
| return true; |
| } |
| |
| // match a NAME; return null if no match. |
| private String matchLabel(TokenStream ts) throws IOException, |
| JavaScriptException { |
| int lineno = ts.getLineno(); |
| |
| String label = null; |
| int tt; |
| tt = ts.peekTokenSameLine(); |
| if (tt == ts.NAME) { |
| ts.getToken(); |
| label = ts.getString(); |
| } |
| |
| if (lineno == ts.getLineno()) |
| wellTerminated(ts, ts.ERROR); |
| |
| return label; |
| } |
| |
| private Object statement(TokenStream ts) throws IOException { |
| try { |
| return statementHelper(ts); |
| } catch (JavaScriptException e) { |
| // skip to end of statement |
| int lineno = ts.getLineno(); |
| int t; |
| do { |
| t = ts.getToken(); |
| } while (t != TokenStream.SEMI && t != TokenStream.EOL |
| && t != TokenStream.EOF && t != TokenStream.ERROR); |
| return nf.createExprStatement(nf.createName("error"), lineno); |
| } |
| } |
| |
| /** |
| * Whether the "catch (e: e instanceof Exception) { ... }" syntax is |
| * implemented. |
| */ |
| |
| private Object statementHelper(TokenStream ts) throws IOException, |
| JavaScriptException { |
| Object pn = null; |
| |
| // If skipsemi == true, don't add SEMI + EOL to source at the |
| // end of this statment. For compound statements, IF/FOR etc. |
| boolean skipsemi = false; |
| |
| int tt; |
| |
| int lastExprType = 0; // For wellTerminated. 0 to avoid warning. |
| |
| tt = ts.getToken(); |
| |
| switch (tt) { |
| case TokenStream.IF: { |
| skipsemi = true; |
| |
| sourceAdd((char) ts.IF); |
| int lineno = ts.getLineno(); |
| Object cond = condition(ts); |
| sourceAdd((char) ts.LC); |
| sourceAdd((char) ts.EOL); |
| Object ifTrue = statement(ts); |
| Object ifFalse = null; |
| if (ts.matchToken(ts.ELSE)) { |
| sourceAdd((char) ts.RC); |
| sourceAdd((char) ts.ELSE); |
| sourceAdd((char) ts.LC); |
| sourceAdd((char) ts.EOL); |
| ifFalse = statement(ts); |
| } |
| sourceAdd((char) ts.RC); |
| sourceAdd((char) ts.EOL); |
| pn = nf.createIf(cond, ifTrue, ifFalse, lineno); |
| break; |
| } |
| |
| case TokenStream.SWITCH: { |
| skipsemi = true; |
| |
| sourceAdd((char) ts.SWITCH); |
| pn = nf.createSwitch(ts.getLineno()); |
| |
| Object cur_case = null; // to kill warning |
| Object case_statements; |
| |
| mustMatchToken(ts, ts.LP, "msg.no.paren.switch"); |
| sourceAdd((char) ts.LP); |
| nf.addChildToBack(pn, expr(ts, false)); |
| mustMatchToken(ts, ts.GWT, "msg.no.paren.after.switch"); |
| sourceAdd((char) ts.GWT); |
| mustMatchToken(ts, ts.LC, "msg.no.brace.switch"); |
| sourceAdd((char) ts.LC); |
| sourceAdd((char) ts.EOL); |
| |
| while ((tt = ts.getToken()) != ts.RC && tt != ts.EOF) { |
| switch (tt) { |
| case TokenStream.CASE: |
| sourceAdd((char) ts.CASE); |
| cur_case = nf.createUnary(ts.CASE, expr(ts, false)); |
| sourceAdd((char) ts.COLON); |
| sourceAdd((char) ts.EOL); |
| break; |
| |
| case TokenStream.DEFAULT: |
| cur_case = nf.createLeaf(ts.DEFAULT); |
| sourceAdd((char) ts.DEFAULT); |
| sourceAdd((char) ts.COLON); |
| sourceAdd((char) ts.EOL); |
| // XXX check that there isn't more than one default |
| break; |
| |
| default: |
| reportError(ts, "msg.bad.switch"); |
| break; |
| } |
| mustMatchToken(ts, ts.COLON, "msg.no.colon.case"); |
| |
| case_statements = nf.createLeaf(TokenStream.BLOCK); |
| ((Node) case_statements).setIsSyntheticBlock(true); |
| |
| while ((tt = ts.peekToken()) != ts.RC && tt != ts.CASE |
| && tt != ts.DEFAULT && tt != ts.EOF) { |
| nf.addChildToBack(case_statements, statement(ts)); |
| } |
| // assert cur_case |
| nf.addChildToBack(cur_case, case_statements); |
| |
| nf.addChildToBack(pn, cur_case); |
| } |
| sourceAdd((char) ts.RC); |
| sourceAdd((char) ts.EOL); |
| break; |
| } |
| |
| case TokenStream.WHILE: { |
| skipsemi = true; |
| |
| sourceAdd((char) ts.WHILE); |
| int lineno = ts.getLineno(); |
| Object cond = condition(ts); |
| sourceAdd((char) ts.LC); |
| sourceAdd((char) ts.EOL); |
| Object body = statement(ts); |
| sourceAdd((char) ts.RC); |
| sourceAdd((char) ts.EOL); |
| |
| pn = nf.createWhile(cond, body, lineno); |
| break; |
| |
| } |
| |
| case TokenStream.DO: { |
| sourceAdd((char) ts.DO); |
| sourceAdd((char) ts.LC); |
| sourceAdd((char) ts.EOL); |
| |
| int lineno = ts.getLineno(); |
| |
| Object body = statement(ts); |
| |
| sourceAdd((char) ts.RC); |
| mustMatchToken(ts, ts.WHILE, "msg.no.while.do"); |
| sourceAdd((char) ts.WHILE); |
| Object cond = condition(ts); |
| |
| pn = nf.createDoWhile(body, cond, lineno); |
| break; |
| } |
| |
| case TokenStream.FOR: { |
| skipsemi = true; |
| |
| sourceAdd((char) ts.FOR); |
| int lineno = ts.getLineno(); |
| |
| Object init; // Node init is also foo in 'foo in Object' |
| Object cond; // Node cond is also object in 'foo in Object' |
| Object incr = null; // to kill warning |
| Object body; |
| |
| mustMatchToken(ts, ts.LP, "msg.no.paren.for"); |
| sourceAdd((char) ts.LP); |
| tt = ts.peekToken(); |
| if (tt == ts.SEMI) { |
| init = nf.createLeaf(ts.VOID); |
| } else { |
| if (tt == ts.VAR) { |
| // set init to a var list or initial |
| ts.getToken(); // throw away the 'var' token |
| init = variables(ts, true); |
| } else { |
| init = expr(ts, true); |
| } |
| } |
| |
| tt = ts.peekToken(); |
| if (tt == ts.RELOP && ts.getOp() == ts.IN) { |
| ts.matchToken(ts.RELOP); |
| sourceAdd((char) ts.IN); |
| // 'cond' is the object over which we're iterating |
| cond = expr(ts, false); |
| } else { // ordinary for loop |
| mustMatchToken(ts, ts.SEMI, "msg.no.semi.for"); |
| sourceAdd((char) ts.SEMI); |
| if (ts.peekToken() == ts.SEMI) { |
| // no loop condition |
| cond = nf.createLeaf(ts.VOID); |
| } else { |
| cond = expr(ts, false); |
| } |
| |
| mustMatchToken(ts, ts.SEMI, "msg.no.semi.for.cond"); |
| sourceAdd((char) ts.SEMI); |
| if (ts.peekToken() == ts.GWT) { |
| incr = nf.createLeaf(ts.VOID); |
| } else { |
| incr = expr(ts, false); |
| } |
| } |
| |
| mustMatchToken(ts, ts.GWT, "msg.no.paren.for.ctrl"); |
| sourceAdd((char) ts.GWT); |
| sourceAdd((char) ts.LC); |
| sourceAdd((char) ts.EOL); |
| body = statement(ts); |
| sourceAdd((char) ts.RC); |
| sourceAdd((char) ts.EOL); |
| |
| if (incr == null) { |
| // cond could be null if 'in obj' got eaten by the init node. |
| pn = nf.createForIn(init, cond, body, lineno); |
| } else { |
| pn = nf.createFor(init, cond, incr, body, lineno); |
| } |
| break; |
| } |
| |
| case TokenStream.TRY: { |
| int lineno = ts.getLineno(); |
| |
| Object tryblock; |
| Object catchblocks = null; |
| Object finallyblock = null; |
| |
| skipsemi = true; |
| sourceAdd((char) ts.TRY); |
| sourceAdd((char) ts.LC); |
| sourceAdd((char) ts.EOL); |
| tryblock = statement(ts); |
| sourceAdd((char) ts.RC); |
| sourceAdd((char) ts.EOL); |
| |
| catchblocks = nf.createLeaf(TokenStream.BLOCK); |
| |
| boolean sawDefaultCatch = false; |
| int peek = ts.peekToken(); |
| if (peek == ts.CATCH) { |
| while (ts.matchToken(ts.CATCH)) { |
| if (sawDefaultCatch) { |
| reportError(ts, "msg.catch.unreachable"); |
| } |
| sourceAdd((char) ts.CATCH); |
| mustMatchToken(ts, ts.LP, "msg.no.paren.catch"); |
| sourceAdd((char) ts.LP); |
| |
| mustMatchToken(ts, ts.NAME, "msg.bad.catchcond"); |
| String varName = ts.getString(); |
| sourceAddString(ts.NAME, varName); |
| |
| Object catchCond = null; |
| if (ts.matchToken(ts.IF)) { |
| sourceAdd((char) ts.IF); |
| catchCond = expr(ts, false); |
| } else { |
| sawDefaultCatch = true; |
| } |
| |
| mustMatchToken(ts, ts.GWT, "msg.bad.catchcond"); |
| sourceAdd((char) ts.GWT); |
| mustMatchToken(ts, ts.LC, "msg.no.brace.catchblock"); |
| sourceAdd((char) ts.LC); |
| sourceAdd((char) ts.EOL); |
| |
| nf.addChildToBack(catchblocks, nf.createCatch(varName, catchCond, |
| statements(ts), ts.getLineno())); |
| |
| mustMatchToken(ts, ts.RC, "msg.no.brace.after.body"); |
| sourceAdd((char) ts.RC); |
| sourceAdd((char) ts.EOL); |
| } |
| } else if (peek != ts.FINALLY) { |
| mustMatchToken(ts, ts.FINALLY, "msg.try.no.catchfinally"); |
| } |
| |
| if (ts.matchToken(ts.FINALLY)) { |
| sourceAdd((char) ts.FINALLY); |
| |
| sourceAdd((char) ts.LC); |
| sourceAdd((char) ts.EOL); |
| finallyblock = statement(ts); |
| sourceAdd((char) ts.RC); |
| sourceAdd((char) ts.EOL); |
| } |
| |
| pn = nf.createTryCatchFinally(tryblock, catchblocks, finallyblock, |
| lineno); |
| |
| break; |
| } |
| case TokenStream.THROW: { |
| int lineno = ts.getLineno(); |
| sourceAdd((char) ts.THROW); |
| pn = nf.createThrow(expr(ts, false), lineno); |
| if (lineno == ts.getLineno()) |
| wellTerminated(ts, ts.ERROR); |
| break; |
| } |
| case TokenStream.BREAK: { |
| int lineno = ts.getLineno(); |
| |
| sourceAdd((char) ts.BREAK); |
| |
| // matchLabel only matches if there is one |
| String label = matchLabel(ts); |
| if (label != null) { |
| sourceAddString(ts.NAME, label); |
| } |
| pn = nf.createBreak(label, lineno); |
| break; |
| } |
| case TokenStream.CONTINUE: { |
| int lineno = ts.getLineno(); |
| |
| sourceAdd((char) ts.CONTINUE); |
| |
| // matchLabel only matches if there is one |
| String label = matchLabel(ts); |
| if (label != null) { |
| sourceAddString(ts.NAME, label); |
| } |
| pn = nf.createContinue(label, lineno); |
| break; |
| } |
| case TokenStream.DEBUGGER: { |
| int lineno = ts.getLineno(); |
| |
| sourceAdd((char) ts.DEBUGGER); |
| |
| pn = nf.createDebugger(lineno); |
| break; |
| } |
| case TokenStream.WITH: { |
| // bruce: we don't support this is JSNI code because it's impossible |
| // to identify bindings even passably well |
| // |
| |
| reportError(ts, "msg.jsni.unsupported.with"); |
| |
| skipsemi = true; |
| |
| sourceAdd((char) ts.WITH); |
| int lineno = ts.getLineno(); |
| mustMatchToken(ts, ts.LP, "msg.no.paren.with"); |
| sourceAdd((char) ts.LP); |
| Object obj = expr(ts, false); |
| mustMatchToken(ts, ts.GWT, "msg.no.paren.after.with"); |
| sourceAdd((char) ts.GWT); |
| sourceAdd((char) ts.LC); |
| sourceAdd((char) ts.EOL); |
| |
| Object body = statement(ts); |
| |
| sourceAdd((char) ts.RC); |
| sourceAdd((char) ts.EOL); |
| |
| pn = nf.createWith(obj, body, lineno); |
| break; |
| } |
| case TokenStream.VAR: { |
| int lineno = ts.getLineno(); |
| pn = variables(ts, false); |
| if (ts.getLineno() == lineno) |
| wellTerminated(ts, ts.ERROR); |
| break; |
| } |
| case TokenStream.RETURN: { |
| Object retExpr = null; |
| int lineno = 0; |
| |
| sourceAdd((char) ts.RETURN); |
| |
| // bail if we're not in a (toplevel) function |
| if ((ts.flags & ts.TSF_FUNCTION) == 0) |
| reportError(ts, "msg.bad.return"); |
| |
| /* This is ugly, but we don't want to require a semicolon. */ |
| ts.flags |= ts.TSF_REGEXP; |
| tt = ts.peekTokenSameLine(); |
| ts.flags &= ~ts.TSF_REGEXP; |
| |
| if (tt != ts.EOF && tt != ts.EOL && tt != ts.SEMI && tt != ts.RC) { |
| lineno = ts.getLineno(); |
| retExpr = expr(ts, false); |
| if (ts.getLineno() == lineno) |
| wellTerminated(ts, ts.ERROR); |
| ts.flags |= ts.TSF_RETURN_EXPR; |
| } else { |
| ts.flags |= ts.TSF_RETURN_VOID; |
| } |
| |
| // XXX ASSERT pn |
| pn = nf.createReturn(retExpr, lineno); |
| break; |
| } |
| case TokenStream.LC: |
| skipsemi = true; |
| |
| pn = statements(ts); |
| mustMatchToken(ts, ts.RC, "msg.no.brace.block"); |
| break; |
| |
| case TokenStream.ERROR: |
| // Fall thru, to have a node for error recovery to work on |
| case TokenStream.EOL: |
| case TokenStream.SEMI: |
| pn = nf.createLeaf(ts.VOID); |
| skipsemi = true; |
| break; |
| |
| default: { |
| lastExprType = tt; |
| int tokenno = ts.getTokenno(); |
| ts.ungetToken(tt); |
| int lineno = ts.getLineno(); |
| |
| pn = expr(ts, false); |
| |
| if (ts.peekToken() == ts.COLON) { |
| /* |
| * check that the last thing the tokenizer returned was a NAME and |
| * that only one token was consumed. |
| */ |
| if (lastExprType != ts.NAME || (ts.getTokenno() != tokenno)) |
| reportError(ts, "msg.bad.label"); |
| |
| ts.getToken(); // eat the COLON |
| |
| /* |
| * in the C source, the label is associated with the statement that |
| * follows: nf.addChildToBack(pn, statement(ts)); |
| */ |
| String name = ts.getString(); |
| pn = nf.createLabel(name, lineno); |
| |
| // bruce: added to make it easier to bind labels to the |
| // statements they modify |
| // |
| nf.addChildToBack(pn, statement(ts)); |
| |
| // depend on decompiling lookahead to guess that that |
| // last name was a label. |
| sourceAdd((char) ts.COLON); |
| sourceAdd((char) ts.EOL); |
| return pn; |
| } |
| |
| if (lastExprType == ts.FUNCTION) { |
| if (nf.getLeafType(pn) != ts.FUNCTION) { |
| reportError(ts, "msg.syntax"); |
| } |
| } |
| |
| pn = nf.createExprStatement(pn, lineno); |
| |
| /* |
| * Check explicitly against (multi-line) function statement. |
| * |
| * lastExprEndLine is a hack to fix an automatic semicolon insertion |
| * problem with function expressions; the ts.getLineno() == lineno check |
| * was firing after a function definition even though the next statement |
| * was on a new line, because speculative getToken calls advanced the |
| * line number even when they didn't succeed. |
| */ |
| if (ts.getLineno() == lineno |
| || (lastExprType == ts.FUNCTION && ts.getLineno() == lastExprEndLine)) { |
| wellTerminated(ts, lastExprType); |
| } |
| break; |
| } |
| } |
| ts.matchToken(ts.SEMI); |
| if (!skipsemi) { |
| sourceAdd((char) ts.SEMI); |
| sourceAdd((char) ts.EOL); |
| } |
| |
| return pn; |
| } |
| |
| private Object variables(TokenStream ts, boolean inForInit) |
| throws IOException, JavaScriptException { |
| Object pn = nf.createVariables(ts.getLineno()); |
| boolean first = true; |
| |
| sourceAdd((char) ts.VAR); |
| |
| for (;;) { |
| Object name; |
| Object init; |
| mustMatchToken(ts, ts.NAME, "msg.bad.var"); |
| String s = ts.getString(); |
| |
| if (!first) |
| sourceAdd((char) ts.COMMA); |
| first = false; |
| |
| sourceAddString(ts.NAME, s); |
| name = nf.createName(s); |
| |
| // omitted check for argument hiding |
| |
| if (ts.matchToken(ts.ASSIGN)) { |
| if (ts.getOp() != ts.NOP) |
| reportError(ts, "msg.bad.var.init"); |
| |
| sourceAdd((char) ts.ASSIGN); |
| sourceAdd((char) ts.NOP); |
| |
| init = assignExpr(ts, inForInit); |
| nf.addChildToBack(name, init); |
| } |
| nf.addChildToBack(pn, name); |
| if (!ts.matchToken(ts.COMMA)) |
| break; |
| } |
| return pn; |
| } |
| |
| private Object expr(TokenStream ts, boolean inForInit) throws IOException, |
| JavaScriptException { |
| Object pn = assignExpr(ts, inForInit); |
| while (ts.matchToken(ts.COMMA)) { |
| sourceAdd((char) ts.COMMA); |
| pn = nf.createBinary(ts.COMMA, pn, assignExpr(ts, inForInit)); |
| } |
| return pn; |
| } |
| |
| private Object assignExpr(TokenStream ts, boolean inForInit) |
| throws IOException, JavaScriptException { |
| Object pn = condExpr(ts, inForInit); |
| |
| if (ts.matchToken(ts.ASSIGN)) { |
| // omitted: "invalid assignment left-hand side" check. |
| sourceAdd((char) ts.ASSIGN); |
| sourceAdd((char) ts.getOp()); |
| pn = nf |
| .createBinary(ts.ASSIGN, ts.getOp(), pn, assignExpr(ts, inForInit)); |
| } |
| |
| return pn; |
| } |
| |
| private Object condExpr(TokenStream ts, boolean inForInit) |
| throws IOException, JavaScriptException { |
| Object ifTrue; |
| Object ifFalse; |
| |
| Object pn = orExpr(ts, inForInit); |
| |
| if (ts.matchToken(ts.HOOK)) { |
| sourceAdd((char) ts.HOOK); |
| ifTrue = assignExpr(ts, false); |
| mustMatchToken(ts, ts.COLON, "msg.no.colon.cond"); |
| sourceAdd((char) ts.COLON); |
| ifFalse = assignExpr(ts, inForInit); |
| return nf.createTernary(pn, ifTrue, ifFalse); |
| } |
| |
| return pn; |
| } |
| |
| private Object orExpr(TokenStream ts, boolean inForInit) throws IOException, |
| JavaScriptException { |
| Object pn = andExpr(ts, inForInit); |
| if (ts.matchToken(ts.OR)) { |
| sourceAdd((char) ts.OR); |
| pn = nf.createBinary(ts.OR, pn, orExpr(ts, inForInit)); |
| } |
| |
| return pn; |
| } |
| |
| private Object andExpr(TokenStream ts, boolean inForInit) throws IOException, |
| JavaScriptException { |
| Object pn = bitOrExpr(ts, inForInit); |
| if (ts.matchToken(ts.AND)) { |
| sourceAdd((char) ts.AND); |
| pn = nf.createBinary(ts.AND, pn, andExpr(ts, inForInit)); |
| } |
| |
| return pn; |
| } |
| |
| private Object bitOrExpr(TokenStream ts, boolean inForInit) |
| throws IOException, JavaScriptException { |
| Object pn = bitXorExpr(ts, inForInit); |
| while (ts.matchToken(ts.BITOR)) { |
| sourceAdd((char) ts.BITOR); |
| pn = nf.createBinary(ts.BITOR, pn, bitXorExpr(ts, inForInit)); |
| } |
| return pn; |
| } |
| |
| private Object bitXorExpr(TokenStream ts, boolean inForInit) |
| throws IOException, JavaScriptException { |
| Object pn = bitAndExpr(ts, inForInit); |
| while (ts.matchToken(ts.BITXOR)) { |
| sourceAdd((char) ts.BITXOR); |
| pn = nf.createBinary(ts.BITXOR, pn, bitAndExpr(ts, inForInit)); |
| } |
| return pn; |
| } |
| |
| private Object bitAndExpr(TokenStream ts, boolean inForInit) |
| throws IOException, JavaScriptException { |
| Object pn = eqExpr(ts, inForInit); |
| while (ts.matchToken(ts.BITAND)) { |
| sourceAdd((char) ts.BITAND); |
| pn = nf.createBinary(ts.BITAND, pn, eqExpr(ts, inForInit)); |
| } |
| return pn; |
| } |
| |
| private Object eqExpr(TokenStream ts, boolean inForInit) throws IOException, |
| JavaScriptException { |
| Object pn = relExpr(ts, inForInit); |
| while (ts.matchToken(ts.EQOP)) { |
| sourceAdd((char) ts.EQOP); |
| sourceAdd((char) ts.getOp()); |
| pn = nf.createBinary(ts.EQOP, ts.getOp(), pn, relExpr(ts, inForInit)); |
| } |
| return pn; |
| } |
| |
| private Object relExpr(TokenStream ts, boolean inForInit) throws IOException, |
| JavaScriptException { |
| Object pn = shiftExpr(ts); |
| while (ts.matchToken(ts.RELOP)) { |
| int op = ts.getOp(); |
| if (inForInit && op == ts.IN) { |
| ts.ungetToken(ts.RELOP); |
| break; |
| } |
| sourceAdd((char) ts.RELOP); |
| sourceAdd((char) op); |
| pn = nf.createBinary(ts.RELOP, op, pn, shiftExpr(ts)); |
| } |
| return pn; |
| } |
| |
| private Object shiftExpr(TokenStream ts) throws IOException, |
| JavaScriptException { |
| Object pn = addExpr(ts); |
| while (ts.matchToken(ts.SHOP)) { |
| sourceAdd((char) ts.SHOP); |
| sourceAdd((char) ts.getOp()); |
| pn = nf.createBinary(ts.SHOP, ts.getOp(), pn, addExpr(ts)); |
| } |
| return pn; |
| } |
| |
| private Object addExpr(TokenStream ts) throws IOException, |
| JavaScriptException { |
| int tt; |
| Object pn = mulExpr(ts); |
| |
| while ((tt = ts.getToken()) == ts.ADD || tt == ts.SUB) { |
| sourceAdd((char) tt); |
| // flushNewLines |
| pn = nf.createBinary(tt, pn, mulExpr(ts)); |
| } |
| ts.ungetToken(tt); |
| |
| return pn; |
| } |
| |
| private Object mulExpr(TokenStream ts) throws IOException, |
| JavaScriptException { |
| int tt; |
| |
| Object pn = unaryExpr(ts); |
| |
| while ((tt = ts.peekToken()) == ts.MUL || tt == ts.DIV || tt == ts.MOD) { |
| tt = ts.getToken(); |
| sourceAdd((char) tt); |
| pn = nf.createBinary(tt, pn, unaryExpr(ts)); |
| } |
| |
| return pn; |
| } |
| |
| private Object unaryExpr(TokenStream ts) throws IOException, |
| JavaScriptException { |
| int tt; |
| |
| ts.flags |= ts.TSF_REGEXP; |
| tt = ts.getToken(); |
| ts.flags &= ~ts.TSF_REGEXP; |
| |
| switch (tt) { |
| case TokenStream.UNARYOP: |
| sourceAdd((char) ts.UNARYOP); |
| sourceAdd((char) ts.getOp()); |
| return nf.createUnary(ts.UNARYOP, ts.getOp(), unaryExpr(ts)); |
| |
| case TokenStream.ADD: |
| case TokenStream.SUB: |
| sourceAdd((char) ts.UNARYOP); |
| sourceAdd((char) tt); |
| return nf.createUnary(ts.UNARYOP, tt, unaryExpr(ts)); |
| |
| case TokenStream.INC: |
| case TokenStream.DEC: |
| sourceAdd((char) tt); |
| return nf.createUnary(tt, ts.PRE, memberExpr(ts, true)); |
| |
| case TokenStream.DELPROP: |
| sourceAdd((char) ts.DELPROP); |
| return nf.createUnary(ts.DELPROP, unaryExpr(ts)); |
| |
| case TokenStream.ERROR: |
| break; |
| |
| default: |
| ts.ungetToken(tt); |
| |
| int lineno = ts.getLineno(); |
| |
| Object pn = memberExpr(ts, true); |
| |
| /* |
| * don't look across a newline boundary for a postfix incop. |
| * |
| * the rhino scanner seems to work differently than the js scanner here; |
| * in js, it works to have the line number check precede the peekToken |
| * calls. It'd be better if they had similar behavior... |
| */ |
| int peeked; |
| if (((peeked = ts.peekToken()) == ts.INC || peeked == ts.DEC) |
| && ts.getLineno() == lineno) { |
| int pf = ts.getToken(); |
| sourceAdd((char) pf); |
| return nf.createUnary(pf, ts.POST, pn); |
| } |
| return pn; |
| } |
| return nf.createName("err"); // Only reached on error. Try to continue. |
| |
| } |
| |
| private Object argumentList(TokenStream ts, Object listNode) |
| throws IOException, JavaScriptException { |
| boolean matched; |
| ts.flags |= ts.TSF_REGEXP; |
| matched = ts.matchToken(ts.GWT); |
| ts.flags &= ~ts.TSF_REGEXP; |
| if (!matched) { |
| boolean first = true; |
| do { |
| if (!first) |
| sourceAdd((char) ts.COMMA); |
| first = false; |
| nf.addChildToBack(listNode, assignExpr(ts, false)); |
| } while (ts.matchToken(ts.COMMA)); |
| |
| mustMatchToken(ts, ts.GWT, "msg.no.paren.arg"); |
| } |
| sourceAdd((char) ts.GWT); |
| return listNode; |
| } |
| |
| private Object memberExpr(TokenStream ts, boolean allowCallSyntax) |
| throws IOException, JavaScriptException { |
| int tt; |
| |
| Object pn; |
| |
| /* Check for new expressions. */ |
| ts.flags |= ts.TSF_REGEXP; |
| tt = ts.peekToken(); |
| ts.flags &= ~ts.TSF_REGEXP; |
| if (tt == ts.NEW) { |
| /* Eat the NEW token. */ |
| ts.getToken(); |
| sourceAdd((char) ts.NEW); |
| |
| /* Make a NEW node to append to. */ |
| pn = nf.createLeaf(ts.NEW); |
| nf.addChildToBack(pn, memberExpr(ts, false)); |
| |
| if (ts.matchToken(ts.LP)) { |
| sourceAdd((char) ts.LP); |
| /* Add the arguments to pn, if any are supplied. */ |
| pn = argumentList(ts, pn); |
| } |
| |
| /* |
| * XXX there's a check in the C source against "too many constructor |
| * arguments" - how many do we claim to support? |
| */ |
| |
| /* |
| * Experimental syntax: allow an object literal to follow a new |
| * expression, which will mean a kind of anonymous class built with the |
| * JavaAdapter. the object literal will be passed as an additional |
| * argument to the constructor. |
| */ |
| tt = ts.peekToken(); |
| if (tt == ts.LC) { |
| nf.addChildToBack(pn, primaryExpr(ts)); |
| } |
| } else { |
| pn = primaryExpr(ts); |
| } |
| |
| return memberExprTail(ts, allowCallSyntax, pn); |
| } |
| |
| private Object memberExprTail(TokenStream ts, boolean allowCallSyntax, |
| Object pn) throws IOException, JavaScriptException { |
| lastExprEndLine = ts.getLineno(); |
| int tt; |
| while ((tt = ts.getToken()) > ts.EOF) { |
| if (tt == ts.DOT) { |
| sourceAdd((char) ts.DOT); |
| mustMatchToken(ts, ts.NAME, "msg.no.name.after.dot"); |
| String s = ts.getString(); |
| sourceAddString(ts.NAME, s); |
| pn = nf.createBinary(ts.DOT, pn, nf.createName(ts.getString())); |
| /* |
| * pn = nf.createBinary(ts.DOT, pn, memberExpr(ts)) is the version in |
| * Brendan's IR C version. Not in ECMA... does it reflect the 'new' |
| * operator syntax he mentioned? |
| */ |
| lastExprEndLine = ts.getLineno(); |
| } else if (tt == ts.LB) { |
| sourceAdd((char) ts.LB); |
| pn = nf.createBinary(ts.LB, pn, expr(ts, false)); |
| |
| mustMatchToken(ts, ts.RB, "msg.no.bracket.index"); |
| sourceAdd((char) ts.RB); |
| lastExprEndLine = ts.getLineno(); |
| } else if (allowCallSyntax && tt == ts.LP) { |
| /* make a call node */ |
| |
| pn = nf.createUnary(ts.CALL, pn); |
| sourceAdd((char) ts.LP); |
| |
| /* Add the arguments to pn, if any are supplied. */ |
| pn = argumentList(ts, pn); |
| lastExprEndLine = ts.getLineno(); |
| } else { |
| ts.ungetToken(tt); |
| |
| break; |
| } |
| } |
| return pn; |
| } |
| |
| private Object primaryExpr(TokenStream ts) throws IOException, |
| JavaScriptException { |
| int tt; |
| |
| Object pn; |
| |
| ts.flags |= ts.TSF_REGEXP; |
| tt = ts.getToken(); |
| ts.flags &= ~ts.TSF_REGEXP; |
| |
| switch (tt) { |
| |
| case TokenStream.FUNCTION: |
| return function(ts, true); |
| |
| case TokenStream.LB: { |
| sourceAdd((char) ts.LB); |
| pn = nf.createLeaf(ts.ARRAYLIT); |
| |
| ts.flags |= ts.TSF_REGEXP; |
| boolean matched = ts.matchToken(ts.RB); |
| ts.flags &= ~ts.TSF_REGEXP; |
| |
| if (!matched) { |
| boolean first = true; |
| do { |
| ts.flags |= ts.TSF_REGEXP; |
| tt = ts.peekToken(); |
| ts.flags &= ~ts.TSF_REGEXP; |
| |
| if (!first) |
| sourceAdd((char) ts.COMMA); |
| else |
| first = false; |
| |
| if (tt == ts.RB) { // to fix [,,,].length behavior... |
| break; |
| } |
| |
| if (tt == ts.COMMA) { |
| nf.addChildToBack(pn, nf.createLeaf(ts.PRIMARY, ts.UNDEFINED)); |
| } else { |
| nf.addChildToBack(pn, assignExpr(ts, false)); |
| } |
| |
| } while (ts.matchToken(ts.COMMA)); |
| mustMatchToken(ts, ts.RB, "msg.no.bracket.arg"); |
| } |
| sourceAdd((char) ts.RB); |
| return nf.createArrayLiteral(pn); |
| } |
| |
| case TokenStream.LC: { |
| pn = nf.createLeaf(ts.OBJLIT); |
| |
| sourceAdd((char) ts.LC); |
| if (!ts.matchToken(ts.RC)) { |
| |
| boolean first = true; |
| commaloop : do { |
| Object property; |
| |
| if (!first) |
| sourceAdd((char) ts.COMMA); |
| else |
| first = false; |
| |
| tt = ts.getToken(); |
| switch (tt) { |
| // map NAMEs to STRINGs in object literal context. |
| case TokenStream.NAME: |
| case TokenStream.STRING: |
| String s = ts.getString(); |
| sourceAddString(ts.NAME, s); |
| property = nf.createString(ts.getString()); |
| break; |
| case TokenStream.NUMBER: |
| double n = ts.getNumber(); |
| sourceAddNumber(n); |
| property = nf.createNumber(n); |
| break; |
| case TokenStream.RC: |
| // trailing comma is OK. |
| ts.ungetToken(tt); |
| break commaloop; |
| default: |
| reportError(ts, "msg.bad.prop"); |
| break commaloop; |
| } |
| mustMatchToken(ts, ts.COLON, "msg.no.colon.prop"); |
| |
| // OBJLIT is used as ':' in object literal for |
| // decompilation to solve spacing ambiguity. |
| sourceAdd((char) ts.OBJLIT); |
| nf.addChildToBack(pn, property); |
| nf.addChildToBack(pn, assignExpr(ts, false)); |
| |
| } while (ts.matchToken(ts.COMMA)); |
| |
| mustMatchToken(ts, ts.RC, "msg.no.brace.prop"); |
| } |
| sourceAdd((char) ts.RC); |
| return nf.createObjectLiteral(pn); |
| } |
| |
| case TokenStream.LP: |
| |
| /* |
| * Brendan's IR-jsparse.c makes a new node tagged with TOK_LP here... |
| * I'm not sure I understand why. Isn't the grouping already implicit in |
| * the structure of the parse tree? also TOK_LP is already overloaded (I |
| * think) in the C IR as 'function call.' |
| */ |
| sourceAdd((char) ts.LP); |
| pn = expr(ts, false); |
| sourceAdd((char) ts.GWT); |
| mustMatchToken(ts, ts.GWT, "msg.no.paren"); |
| return pn; |
| |
| case TokenStream.NAME: |
| String name = ts.getString(); |
| sourceAddString(ts.NAME, name); |
| return nf.createName(name); |
| |
| case TokenStream.NUMBER: |
| double n = ts.getNumber(); |
| sourceAddNumber(n); |
| return nf.createNumber(n); |
| |
| case TokenStream.STRING: |
| String s = ts.getString(); |
| sourceAddString(ts.STRING, s); |
| return nf.createString(s); |
| |
| case TokenStream.REGEXP: { |
| String flags = ts.regExpFlags; |
| ts.regExpFlags = null; |
| String re = ts.getString(); |
| sourceAddString(ts.REGEXP, '/' + re + '/' + flags); |
| return nf.createRegExp(re, flags); |
| } |
| |
| case TokenStream.PRIMARY: |
| sourceAdd((char) ts.PRIMARY); |
| sourceAdd((char) ts.getOp()); |
| return nf.createLeaf(ts.PRIMARY, ts.getOp()); |
| |
| case TokenStream.RESERVED: |
| reportError(ts, "msg.reserved.id"); |
| break; |
| |
| case TokenStream.ERROR: |
| /* the scanner or one of its subroutines reported the error. */ |
| break; |
| |
| default: |
| reportError(ts, "msg.syntax"); |
| break; |
| |
| } |
| return null; // should never reach here |
| } |
| |
| /** |
| * The following methods save decompilation information about the source. |
| * Source information is returned from the parser as a String associated with |
| * function nodes and with the toplevel script. When saved in the constant |
| * pool of a class, this string will be UTF-8 encoded, and token values will |
| * occupy a single byte. |
| * |
| * Source is saved (mostly) as token numbers. The tokens saved pretty much |
| * correspond to the token stream of a 'canonical' representation of the input |
| * program, as directed by the parser. (There were a few cases where tokens |
| * could have been left out where decompiler could easily reconstruct them, |
| * but I left them in for clarity). (I also looked adding source collection to |
| * TokenStream instead, where I could have limited the changes to a few lines |
| * in getToken... but this wouldn't have saved any space in the resulting |
| * source representation, and would have meant that I'd have to duplicate |
| * parser logic in the decompiler to disambiguate situations where newlines |
| * are important.) NativeFunction.decompile expands the tokens back into their |
| * string representations, using simple lookahead to correct spacing and |
| * indentation. |
| * |
| * Token types with associated ops (ASSIGN, SHOP, PRIMARY, etc.) are saved as |
| * two-token pairs. Number tokens are stored inline, as a NUMBER token, a |
| * character representing the type, and either 1 or 4 characters representing |
| * the bit-encoding of the number. String types NAME, STRING and OBJECT are |
| * currently stored as a token type, followed by a character giving the length |
| * of the string (assumed to be less than 2^16), followed by the characters of |
| * the string inlined into the source string. Changing this to some reference |
| * to to the string in the compiled class' constant pool would probably save a |
| * lot of space... but would require some method of deriving the final |
| * constant pool entry from information available at parse time. |
| * |
| * Nested functions need a similar mechanism... fortunately the nested |
| * functions for a given function are generated in source order. Nested |
| * functions are encoded as FUNCTION followed by a function number (encoded as |
| * a character), which is enough information to find the proper generated |
| * NativeFunction instance. |
| * |
| */ |
| private void sourceAdd(char c) { |
| if (sourceTop == sourceBuffer.length) { |
| increaseSourceCapacity(sourceTop + 1); |
| } |
| sourceBuffer[sourceTop] = c; |
| ++sourceTop; |
| } |
| |
| private void sourceAddString(int type, String str) { |
| int L = str.length(); |
| // java string length < 2^16? |
| if (Context.check && L > Character.MAX_VALUE) |
| Context.codeBug(); |
| |
| if (sourceTop + L + 2 > sourceBuffer.length) { |
| increaseSourceCapacity(sourceTop + L + 2); |
| } |
| sourceAdd((char) type); |
| sourceAdd((char) L); |
| str.getChars(0, L, sourceBuffer, sourceTop); |
| sourceTop += L; |
| } |
| |
| private void sourceAddNumber(double n) { |
| sourceAdd((char) TokenStream.NUMBER); |
| |
| /* |
| * encode the number in the source stream. Save as NUMBER type (char | char |
| * char char char) where type is 'D' - double, 'S' - short, 'J' - long. |
| * |
| * We need to retain float vs. integer type info to keep the behavior of |
| * liveconnect type-guessing the same after decompilation. (Liveconnect |
| * tries to present 1.0 to Java as a float/double) OPT: This is no longer |
| * true. We could compress the format. |
| * |
| * This may not be the most space-efficient encoding; the chars created |
| * below may take up to 3 bytes in constant pool UTF-8 encoding, so a Double |
| * could take up to 12 bytes. |
| */ |
| |
| long lbits = (long) n; |
| if (lbits != n) { |
| // if it's floating point, save as a Double bit pattern. |
| // (12/15/97 our scanner only returns Double for f.p.) |
| lbits = Double.doubleToLongBits(n); |
| sourceAdd('D'); |
| sourceAdd((char) (lbits >> 48)); |
| sourceAdd((char) (lbits >> 32)); |
| sourceAdd((char) (lbits >> 16)); |
| sourceAdd((char) lbits); |
| } else { |
| // we can ignore negative values, bc they're already prefixed |
| // by UNARYOP SUB |
| if (Context.check && lbits < 0) |
| Context.codeBug(); |
| |
| // will it fit in a char? |
| // this gives a short encoding for integer values up to 2^16. |
| if (lbits <= Character.MAX_VALUE) { |
| sourceAdd('S'); |
| sourceAdd((char) lbits); |
| } else { // Integral, but won't fit in a char. Store as a long. |
| sourceAdd('J'); |
| sourceAdd((char) (lbits >> 48)); |
| sourceAdd((char) (lbits >> 32)); |
| sourceAdd((char) (lbits >> 16)); |
| sourceAdd((char) lbits); |
| } |
| } |
| } |
| |
| private void increaseSourceCapacity(int minimalCapacity) { |
| // Call this only when capacity increase is must |
| if (Context.check && minimalCapacity <= sourceBuffer.length) |
| Context.codeBug(); |
| int newCapacity = sourceBuffer.length * 2; |
| if (newCapacity < minimalCapacity) { |
| newCapacity = minimalCapacity; |
| } |
| char[] tmp = new char[newCapacity]; |
| System.arraycopy(sourceBuffer, 0, tmp, 0, sourceTop); |
| sourceBuffer = tmp; |
| } |
| |
| private String sourceToString(int offset) { |
| if (Context.check && (offset < 0 || sourceTop < offset)) |
| Context.codeBug(); |
| return new String(sourceBuffer, offset, sourceTop - offset); |
| } |
| |
| private int lastExprEndLine; // Hack to handle function expr termination. |
| private IRFactory nf; |
| private ErrorReporter er; |
| private boolean ok; // Did the parse encounter an error? |
| |
| private char[] sourceBuffer = new char[128]; |
| private int sourceTop; |
| private int functionNumber; |
| } |