diff options
Diffstat (limited to 'trunk/infrastructure/yuicompressor/src/yuicompressor/org/mozilla/javascript/Parser.java.orig')
-rw-r--r-- | trunk/infrastructure/yuicompressor/src/yuicompressor/org/mozilla/javascript/Parser.java.orig | 2159 |
1 files changed, 2159 insertions, 0 deletions
diff --git a/trunk/infrastructure/yuicompressor/src/yuicompressor/org/mozilla/javascript/Parser.java.orig b/trunk/infrastructure/yuicompressor/src/yuicompressor/org/mozilla/javascript/Parser.java.orig new file mode 100644 index 0000000..628bb42 --- /dev/null +++ b/trunk/infrastructure/yuicompressor/src/yuicompressor/org/mozilla/javascript/Parser.java.orig @@ -0,0 +1,2159 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0 + * + * The contents of this file are subject to the Mozilla 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/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or 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 the Initial Developer are Copyright (C) 1997-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Mike Ang + * Igor Bukanov + * Yuh-Ruey Chen + * Ethan Hugg + * Bob Jervis + * Terry Lucas + * Mike McCabe + * Milen Nankov + * + * Alternatively, the contents of this file may be used under the terms of + * the GNU General Public License Version 2 or later (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 + * MPL, indicate your decision by deleting the provisions above and replacing + * 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 MPL or the GPL. + * + * ***** END LICENSE BLOCK ***** */ + +package org.mozilla.javascript; + +import java.io.Reader; +import java.io.IOException; +import java.util.Hashtable; + +/** + * 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 +{ + // TokenInformation flags : currentFlaggedToken stores them together + // with token type + final static int + CLEAR_TI_MASK = 0xFFFF, // mask to clear token information bits + TI_AFTER_EOL = 1 << 16, // first token of the source line + TI_CHECK_LABEL = 1 << 17; // indicates to check for label + + CompilerEnvirons compilerEnv; + private ErrorReporter errorReporter; + private String sourceURI; + boolean calledByCompileFunction; + + private TokenStream ts; + private int currentFlaggedToken; + private int syntaxErrorCount; + + private IRFactory nf; + + private int nestingOfFunction; + + private Decompiler decompiler; + private String encodedSource; + +// The following are per function variables and should be saved/restored +// during function parsing. +// XXX Move to separated class? + ScriptOrFnNode currentScriptOrFn; + private int nestingOfWith; + private Hashtable labelSet; // map of label names into nodes + private ObjArray loopSet; + private ObjArray loopAndSwitchSet; + private boolean hasReturnValue; + private int functionEndFlags; +// end of per function variables + + // Exception to unwind + private static class ParserException extends RuntimeException + { + static final long serialVersionUID = 5882582646773765630L; + } + + public Parser(CompilerEnvirons compilerEnv, ErrorReporter errorReporter) + { + this.compilerEnv = compilerEnv; + this.errorReporter = errorReporter; + } + + protected Decompiler createDecompiler(CompilerEnvirons compilerEnv) + { + return new Decompiler(); + } + + void addStrictWarning(String messageId, String messageArg) + { + if (compilerEnv.isStrictMode()) + addWarning(messageId, messageArg); + } + + void addWarning(String messageId, String messageArg) + { + String message = ScriptRuntime.getMessage1(messageId, messageArg); + if (compilerEnv.reportWarningAsError()) { + ++syntaxErrorCount; + errorReporter.error(message, sourceURI, ts.getLineno(), + ts.getLine(), ts.getOffset()); + } else + errorReporter.warning(message, sourceURI, ts.getLineno(), + ts.getLine(), ts.getOffset()); + } + + void addError(String messageId) + { + ++syntaxErrorCount; + String message = ScriptRuntime.getMessage0(messageId); + errorReporter.error(message, sourceURI, ts.getLineno(), + ts.getLine(), ts.getOffset()); + } + + void addError(String messageId, String messageArg) + { + ++syntaxErrorCount; + String message = ScriptRuntime.getMessage1(messageId, messageArg); + errorReporter.error(message, sourceURI, ts.getLineno(), + ts.getLine(), ts.getOffset()); + } + + RuntimeException reportError(String messageId) + { + addError(messageId); + + // Throw a ParserException exception to unwind the recursive descent + // parse. + throw new ParserException(); + } + + private int peekToken() + throws IOException + { + int tt = currentFlaggedToken; + if (tt == Token.EOF) { + tt = ts.getToken(); + if (tt == Token.EOL) { + do { + tt = ts.getToken(); + } while (tt == Token.EOL); + tt |= TI_AFTER_EOL; + } + currentFlaggedToken = tt; + } + return tt & CLEAR_TI_MASK; + } + + private int peekFlaggedToken() + throws IOException + { + peekToken(); + return currentFlaggedToken; + } + + private void consumeToken() + { + currentFlaggedToken = Token.EOF; + } + + private int nextToken() + throws IOException + { + int tt = peekToken(); + consumeToken(); + return tt; + } + + private int nextFlaggedToken() + throws IOException + { + peekToken(); + int ttFlagged = currentFlaggedToken; + consumeToken(); + return ttFlagged; + } + + private boolean matchToken(int toMatch) + throws IOException + { + int tt = peekToken(); + if (tt != toMatch) { + return false; + } + consumeToken(); + return true; + } + + private int peekTokenOrEOL() + throws IOException + { + int tt = peekToken(); + // Check for last peeked token flags + if ((currentFlaggedToken & TI_AFTER_EOL) != 0) { + tt = Token.EOL; + } + return tt; + } + + private void setCheckForLabel() + { + if ((currentFlaggedToken & CLEAR_TI_MASK) != Token.NAME) + throw Kit.codeBug(); + currentFlaggedToken |= TI_CHECK_LABEL; + } + + private void mustMatchToken(int toMatch, String messageId) + throws IOException, ParserException + { + if (!matchToken(toMatch)) { + reportError(messageId); + } + } + + private void mustHaveXML() + { + if (!compilerEnv.isXmlAvailable()) { + reportError("msg.XML.not.available"); + } + } + + public String getEncodedSource() + { + return encodedSource; + } + + public boolean eof() + { + return ts.eof(); + } + + boolean insideFunction() + { + return nestingOfFunction != 0; + } + + private Node enterLoop(Node loopLabel) + { + Node loop = nf.createLoopNode(loopLabel, ts.getLineno()); + if (loopSet == null) { + loopSet = new ObjArray(); + if (loopAndSwitchSet == null) { + loopAndSwitchSet = new ObjArray(); + } + } + loopSet.push(loop); + loopAndSwitchSet.push(loop); + return loop; + } + + private void exitLoop() + { + loopSet.pop(); + loopAndSwitchSet.pop(); + } + + private Node enterSwitch(Node switchSelector, int lineno) + { + Node switchNode = nf.createSwitch(switchSelector, lineno); + if (loopAndSwitchSet == null) { + loopAndSwitchSet = new ObjArray(); + } + loopAndSwitchSet.push(switchNode); + return switchNode; + } + + private void exitSwitch() + { + loopAndSwitchSet.pop(); + } + + /* + * Build a parse tree from the given sourceString. + * + * @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 ErrorReporter from + * CompilerEnvirons.) + */ + public ScriptOrFnNode parse(String sourceString, + String sourceURI, int lineno) + { + this.sourceURI = sourceURI; + this.ts = new TokenStream(this, null, sourceString, lineno); + try { + return parse(); + } catch (IOException ex) { + // Should never happen + throw new IllegalStateException(); + } + } + + /* + * Build a parse tree from the given sourceString. + * + * @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 ErrorReporter from + * CompilerEnvirons.) + */ + public ScriptOrFnNode parse(Reader sourceReader, + String sourceURI, int lineno) + throws IOException + { + this.sourceURI = sourceURI; + this.ts = new TokenStream(this, sourceReader, null, lineno); + return parse(); + } + + private ScriptOrFnNode parse() + throws IOException + { + this.decompiler = createDecompiler(compilerEnv); + this.nf = new IRFactory(this); + currentScriptOrFn = nf.createScript(); + int sourceStartOffset = decompiler.getCurrentOffset(); + this.encodedSource = null; + decompiler.addToken(Token.SCRIPT); + + this.currentFlaggedToken = Token.EOF; + this.syntaxErrorCount = 0; + + int baseLineno = ts.getLineno(); // line number where source starts + + /* so we have something to add nodes to until + * we've collected all the source */ + Node pn = nf.createLeaf(Token.BLOCK); + + try { + for (;;) { + int tt = peekToken(); + + if (tt <= Token.EOF) { + break; + } + + Node n; + if (tt == Token.FUNCTION) { + consumeToken(); + try { + n = function(calledByCompileFunction + ? FunctionNode.FUNCTION_EXPRESSION + : FunctionNode.FUNCTION_STATEMENT); + } catch (ParserException e) { + break; + } + } else { + n = statement(); + } + nf.addChildToBack(pn, n); + } + } catch (StackOverflowError ex) { + String msg = ScriptRuntime.getMessage0( + "msg.too.deep.parser.recursion"); + throw Context.reportRuntimeError(msg, sourceURI, + ts.getLineno(), null, 0); + } + + if (this.syntaxErrorCount != 0) { + String msg = String.valueOf(this.syntaxErrorCount); + msg = ScriptRuntime.getMessage1("msg.got.syntax.errors", msg); + throw errorReporter.runtimeError(msg, sourceURI, baseLineno, + null, 0); + } + + currentScriptOrFn.setSourceName(sourceURI); + currentScriptOrFn.setBaseLineno(baseLineno); + currentScriptOrFn.setEndLineno(ts.getLineno()); + + int sourceEndOffset = decompiler.getCurrentOffset(); + currentScriptOrFn.setEncodedSourceBounds(sourceStartOffset, + sourceEndOffset); + + nf.initScript(currentScriptOrFn, pn); + + if (compilerEnv.isGeneratingSource()) { + encodedSource = decompiler.getEncodedSource(); + } + this.decompiler = null; // It helps GC + + return currentScriptOrFn; + } + + /* + * 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 Node parseFunctionBody() + throws IOException + { + ++nestingOfFunction; + Node pn = nf.createBlock(ts.getLineno()); + try { + bodyLoop: for (;;) { + Node n; + int tt = peekToken(); + switch (tt) { + case Token.ERROR: + case Token.EOF: + case Token.RC: + break bodyLoop; + + case Token.FUNCTION: + consumeToken(); + n = function(FunctionNode.FUNCTION_STATEMENT); + break; + default: + n = statement(); + break; + } + nf.addChildToBack(pn, n); + } + } catch (ParserException e) { + // Ignore it + } finally { + --nestingOfFunction; + } + + return pn; + } + + private Node function(int functionType) + throws IOException, ParserException + { + int syntheticType = functionType; + int baseLineno = ts.getLineno(); // line number where source starts + + int functionSourceStart = decompiler.markFunctionStart(functionType); + String name; + Node memberExprNode = null; + if (matchToken(Token.NAME)) { + name = ts.getString(); + decompiler.addName(name); + if (!matchToken(Token.LP)) { + if (compilerEnv.isAllowMemberExprAsFunctionName()) { + // Extension to ECMA: if 'function <name>' does not follow + // by '(', assume <name> starts memberExpr + Node memberExprHead = nf.createName(name); + name = ""; + memberExprNode = memberExprTail(false, memberExprHead); + } + mustMatchToken(Token.LP, "msg.no.paren.parms"); + } + } else if (matchToken(Token.LP)) { + // Anonymous function + name = ""; + } else { + name = ""; + if (compilerEnv.isAllowMemberExprAsFunctionName()) { + // Note that memberExpr can not start with '(' like + // in function (1+2).toString(), because 'function (' already + // processed as anonymous function + memberExprNode = memberExpr(false); + } + mustMatchToken(Token.LP, "msg.no.paren.parms"); + } + + if (memberExprNode != null) { + syntheticType = FunctionNode.FUNCTION_EXPRESSION; + } + + boolean nested = insideFunction(); + + FunctionNode fnNode = nf.createFunction(name); + if (nested || nestingOfWith > 0) { + // 1. Nested functions are not affected by the dynamic scope flag + // as dynamic scope is already a parent of their scope. + // 2. Functions defined under the with statement also immune to + // this setup, in which case dynamic scope is ignored in favor + // of with object. + fnNode.itsIgnoreDynamicScope = true; + } + + int functionIndex = currentScriptOrFn.addFunction(fnNode); + + int functionSourceEnd; + + ScriptOrFnNode savedScriptOrFn = currentScriptOrFn; + currentScriptOrFn = fnNode; + int savedNestingOfWith = nestingOfWith; + nestingOfWith = 0; + Hashtable savedLabelSet = labelSet; + labelSet = null; + ObjArray savedLoopSet = loopSet; + loopSet = null; + ObjArray savedLoopAndSwitchSet = loopAndSwitchSet; + loopAndSwitchSet = null; + boolean savedHasReturnValue = hasReturnValue; + int savedFunctionEndFlags = functionEndFlags; + + Node body; + try { + decompiler.addToken(Token.LP); + if (!matchToken(Token.RP)) { + boolean first = true; + do { + if (!first) + decompiler.addToken(Token.COMMA); + first = false; + mustMatchToken(Token.NAME, "msg.no.parm"); + String s = ts.getString(); + if (fnNode.hasParamOrVar(s)) { + addWarning("msg.dup.parms", s); + } + fnNode.addParam(s); + decompiler.addName(s); + } while (matchToken(Token.COMMA)); + + mustMatchToken(Token.RP, "msg.no.paren.after.parms"); + } + decompiler.addToken(Token.RP); + + mustMatchToken(Token.LC, "msg.no.brace.body"); + decompiler.addEOL(Token.LC); + body = parseFunctionBody(); + mustMatchToken(Token.RC, "msg.no.brace.after.body"); + + if (compilerEnv.isStrictMode() && !body.hasConsistentReturnUsage()) + { + String msg = name.length() > 0 ? "msg.no.return.value" + : "msg.anon.no.return.value"; + addStrictWarning(msg, name); + } + + decompiler.addToken(Token.RC); + functionSourceEnd = decompiler.markFunctionEnd(functionSourceStart); + if (functionType != FunctionNode.FUNCTION_EXPRESSION) { + // Add EOL only if function is not part of expression + // since it gets SEMI + EOL from Statement in that case + decompiler.addToken(Token.EOL); + } + } + finally { + hasReturnValue = savedHasReturnValue; + functionEndFlags = savedFunctionEndFlags; + loopAndSwitchSet = savedLoopAndSwitchSet; + loopSet = savedLoopSet; + labelSet = savedLabelSet; + nestingOfWith = savedNestingOfWith; + currentScriptOrFn = savedScriptOrFn; + } + + fnNode.setEncodedSourceBounds(functionSourceStart, functionSourceEnd); + fnNode.setSourceName(sourceURI); + fnNode.setBaseLineno(baseLineno); + fnNode.setEndLineno(ts.getLineno()); + + if (name != null) { + int index = currentScriptOrFn.getParamOrVarIndex(name); + if (index >= 0 && index < currentScriptOrFn.getParamCount()) + addStrictWarning("msg.var.hides.arg", name); + } + + Node pn = nf.initFunction(fnNode, functionIndex, body, syntheticType); + if (memberExprNode != null) { + pn = nf.createAssignment(Token.ASSIGN, memberExprNode, pn); + if (functionType != FunctionNode.FUNCTION_EXPRESSION) { + // XXX check JScript behavior: should it be createExprStatement? + pn = nf.createExprStatementNoReturn(pn, baseLineno); + } + } + return pn; + } + + private Node statements() + throws IOException + { + Node pn = nf.createBlock(ts.getLineno()); + + int tt; + while((tt = peekToken()) > Token.EOF && tt != Token.RC) { + nf.addChildToBack(pn, statement()); + } + + return pn; + } + + private Node condition() + throws IOException, ParserException + { + mustMatchToken(Token.LP, "msg.no.paren.cond"); + decompiler.addToken(Token.LP); + Node pn = expr(false); + mustMatchToken(Token.RP, "msg.no.paren.after.cond"); + decompiler.addToken(Token.RP); + + // Report strict warning on code like "if (a = 7) ...". Suppress the + // warning if the condition is parenthesized, like "if ((a = 7)) ...". + if (pn.getProp(Node.PARENTHESIZED_PROP) == null && + (pn.getType() == Token.SETNAME || pn.getType() == Token.SETPROP || + pn.getType() == Token.SETELEM)) + { + addStrictWarning("msg.equal.as.assign", ""); + } + return pn; + } + + // match a NAME; return null if no match. + private Node matchJumpLabelName() + throws IOException, ParserException + { + Node label = null; + + int tt = peekTokenOrEOL(); + if (tt == Token.NAME) { + consumeToken(); + String name = ts.getString(); + decompiler.addName(name); + if (labelSet != null) { + label = (Node)labelSet.get(name); + } + if (label == null) { + reportError("msg.undef.label"); + } + } + + return label; + } + + private Node statement() + throws IOException + { + try { + Node pn = statementHelper(null); + if (pn != null) { + if (compilerEnv.isStrictMode() && !pn.hasSideEffects()) + addStrictWarning("msg.no.side.effects", ""); + return pn; + } + } catch (ParserException e) { } + + // skip to end of statement + int lineno = ts.getLineno(); + guessingStatementEnd: for (;;) { + int tt = peekTokenOrEOL(); + consumeToken(); + switch (tt) { + case Token.ERROR: + case Token.EOF: + case Token.EOL: + case Token.SEMI: + break guessingStatementEnd; + } + } + return nf.createExprStatement(nf.createName("error"), lineno); + } + + /** + * Whether the "catch (e: e instanceof Exception) { ... }" syntax + * is implemented. + */ + + private Node statementHelper(Node statementLabel) + throws IOException, ParserException + { + Node pn = null; + + int tt; + + tt = peekToken(); + + switch(tt) { + case Token.IF: { + consumeToken(); + + decompiler.addToken(Token.IF); + int lineno = ts.getLineno(); + Node cond = condition(); + decompiler.addEOL(Token.LC); + Node ifTrue = statement(); + Node ifFalse = null; + if (matchToken(Token.ELSE)) { + decompiler.addToken(Token.RC); + decompiler.addToken(Token.ELSE); + decompiler.addEOL(Token.LC); + ifFalse = statement(); + } + decompiler.addEOL(Token.RC); + pn = nf.createIf(cond, ifTrue, ifFalse, lineno); + return pn; + } + + case Token.SWITCH: { + consumeToken(); + + decompiler.addToken(Token.SWITCH); + int lineno = ts.getLineno(); + mustMatchToken(Token.LP, "msg.no.paren.switch"); + decompiler.addToken(Token.LP); + pn = enterSwitch(expr(false), lineno); + try { + mustMatchToken(Token.RP, "msg.no.paren.after.switch"); + decompiler.addToken(Token.RP); + mustMatchToken(Token.LC, "msg.no.brace.switch"); + decompiler.addEOL(Token.LC); + + boolean hasDefault = false; + switchLoop: for (;;) { + tt = nextToken(); + Node caseExpression; + switch (tt) { + case Token.RC: + break switchLoop; + + case Token.CASE: + decompiler.addToken(Token.CASE); + caseExpression = expr(false); + mustMatchToken(Token.COLON, "msg.no.colon.case"); + decompiler.addEOL(Token.COLON); + break; + + case Token.DEFAULT: + if (hasDefault) { + reportError("msg.double.switch.default"); + } + decompiler.addToken(Token.DEFAULT); + hasDefault = true; + caseExpression = null; + mustMatchToken(Token.COLON, "msg.no.colon.case"); + decompiler.addEOL(Token.COLON); + break; + + default: + reportError("msg.bad.switch"); + break switchLoop; + } + + Node block = nf.createLeaf(Token.BLOCK); + while ((tt = peekToken()) != Token.RC + && tt != Token.CASE + && tt != Token.DEFAULT + && tt != Token.EOF) + { + nf.addChildToBack(block, statement()); + } + + // caseExpression == null => add default lable + nf.addSwitchCase(pn, caseExpression, block); + } + decompiler.addEOL(Token.RC); + nf.closeSwitch(pn); + } finally { + exitSwitch(); + } + return pn; + } + + case Token.WHILE: { + consumeToken(); + decompiler.addToken(Token.WHILE); + + Node loop = enterLoop(statementLabel); + try { + Node cond = condition(); + decompiler.addEOL(Token.LC); + Node body = statement(); + decompiler.addEOL(Token.RC); + pn = nf.createWhile(loop, cond, body); + } finally { + exitLoop(); + } + return pn; + } + + case Token.DO: { + consumeToken(); + decompiler.addToken(Token.DO); + decompiler.addEOL(Token.LC); + + Node loop = enterLoop(statementLabel); + try { + Node body = statement(); + decompiler.addToken(Token.RC); + mustMatchToken(Token.WHILE, "msg.no.while.do"); + decompiler.addToken(Token.WHILE); + Node cond = condition(); + pn = nf.createDoWhile(loop, body, cond); + } finally { + exitLoop(); + } + // Always auto-insert semicon to follow SpiderMonkey: + // It is required by EMAScript but is ignored by the rest of + // world, see bug 238945 + matchToken(Token.SEMI); + decompiler.addEOL(Token.SEMI); + return pn; + } + + case Token.FOR: { + consumeToken(); + boolean isForEach = false; + decompiler.addToken(Token.FOR); + + Node loop = enterLoop(statementLabel); + try { + + Node init; // Node init is also foo in 'foo in Object' + Node cond; // Node cond is also object in 'foo in Object' + Node incr = null; // to kill warning + Node body; + + // See if this is a for each () instead of just a for () + if (matchToken(Token.NAME)) { + decompiler.addName(ts.getString()); + if (ts.getString().equals("each")) { + isForEach = true; + } else { + reportError("msg.no.paren.for"); + } + } + + mustMatchToken(Token.LP, "msg.no.paren.for"); + decompiler.addToken(Token.LP); + tt = peekToken(); + if (tt == Token.SEMI) { + init = nf.createLeaf(Token.EMPTY); + } else { + if (tt == Token.VAR) { + // set init to a var list or initial + consumeToken(); // consume the 'var' token + init = variables(Token.FOR); + } + else { + init = expr(true); + } + } + + if (matchToken(Token.IN)) { + decompiler.addToken(Token.IN); + // 'cond' is the object over which we're iterating + cond = expr(false); + } else { // ordinary for loop + mustMatchToken(Token.SEMI, "msg.no.semi.for"); + decompiler.addToken(Token.SEMI); + if (peekToken() == Token.SEMI) { + // no loop condition + cond = nf.createLeaf(Token.EMPTY); + } else { + cond = expr(false); + } + + mustMatchToken(Token.SEMI, "msg.no.semi.for.cond"); + decompiler.addToken(Token.SEMI); + if (peekToken() == Token.RP) { + incr = nf.createLeaf(Token.EMPTY); + } else { + incr = expr(false); + } + } + + mustMatchToken(Token.RP, "msg.no.paren.for.ctrl"); + decompiler.addToken(Token.RP); + decompiler.addEOL(Token.LC); + body = statement(); + decompiler.addEOL(Token.RC); + + if (incr == null) { + // cond could be null if 'in obj' got eaten + // by the init node. + pn = nf.createForIn(loop, init, cond, body, isForEach); + } else { + pn = nf.createFor(loop, init, cond, incr, body); + } + } finally { + exitLoop(); + } + return pn; + } + + case Token.TRY: { + consumeToken(); + int lineno = ts.getLineno(); + + Node tryblock; + Node catchblocks = null; + Node finallyblock = null; + + decompiler.addToken(Token.TRY); + decompiler.addEOL(Token.LC); + tryblock = statement(); + decompiler.addEOL(Token.RC); + + catchblocks = nf.createLeaf(Token.BLOCK); + + boolean sawDefaultCatch = false; + int peek = peekToken(); + if (peek == Token.CATCH) { + while (matchToken(Token.CATCH)) { + if (sawDefaultCatch) { + reportError("msg.catch.unreachable"); + } + decompiler.addToken(Token.CATCH); + mustMatchToken(Token.LP, "msg.no.paren.catch"); + decompiler.addToken(Token.LP); + + mustMatchToken(Token.NAME, "msg.bad.catchcond"); + String varName = ts.getString(); + decompiler.addName(varName); + + Node catchCond = null; + if (matchToken(Token.IF)) { + decompiler.addToken(Token.IF); + catchCond = expr(false); + } else { + sawDefaultCatch = true; + } + + mustMatchToken(Token.RP, "msg.bad.catchcond"); + decompiler.addToken(Token.RP); + mustMatchToken(Token.LC, "msg.no.brace.catchblock"); + decompiler.addEOL(Token.LC); + + nf.addChildToBack(catchblocks, + nf.createCatch(varName, catchCond, + statements(), + ts.getLineno())); + + mustMatchToken(Token.RC, "msg.no.brace.after.body"); + decompiler.addEOL(Token.RC); + } + } else if (peek != Token.FINALLY) { + mustMatchToken(Token.FINALLY, "msg.try.no.catchfinally"); + } + + if (matchToken(Token.FINALLY)) { + decompiler.addToken(Token.FINALLY); + decompiler.addEOL(Token.LC); + finallyblock = statement(); + decompiler.addEOL(Token.RC); + } + + pn = nf.createTryCatchFinally(tryblock, catchblocks, + finallyblock, lineno); + + return pn; + } + + case Token.THROW: { + consumeToken(); + if (peekTokenOrEOL() == Token.EOL) { + // ECMAScript does not allow new lines before throw expression, + // see bug 256617 + reportError("msg.bad.throw.eol"); + } + + int lineno = ts.getLineno(); + decompiler.addToken(Token.THROW); + pn = nf.createThrow(expr(false), lineno); + break; + } + + case Token.BREAK: { + consumeToken(); + int lineno = ts.getLineno(); + + decompiler.addToken(Token.BREAK); + + // matchJumpLabelName only matches if there is one + Node breakStatement = matchJumpLabelName(); + if (breakStatement == null) { + if (loopAndSwitchSet == null || loopAndSwitchSet.size() == 0) { + reportError("msg.bad.break"); + return null; + } + breakStatement = (Node)loopAndSwitchSet.peek(); + } + pn = nf.createBreak(breakStatement, lineno); + break; + } + + case Token.CONTINUE: { + consumeToken(); + int lineno = ts.getLineno(); + + decompiler.addToken(Token.CONTINUE); + + Node loop; + // matchJumpLabelName only matches if there is one + Node label = matchJumpLabelName(); + if (label == null) { + if (loopSet == null || loopSet.size() == 0) { + reportError("msg.continue.outside"); + return null; + } + loop = (Node)loopSet.peek(); + } else { + loop = nf.getLabelLoop(label); + if (loop == null) { + reportError("msg.continue.nonloop"); + return null; + } + } + pn = nf.createContinue(loop, lineno); + break; + } + + case Token.WITH: { + consumeToken(); + + decompiler.addToken(Token.WITH); + int lineno = ts.getLineno(); + mustMatchToken(Token.LP, "msg.no.paren.with"); + decompiler.addToken(Token.LP); + Node obj = expr(false); + mustMatchToken(Token.RP, "msg.no.paren.after.with"); + decompiler.addToken(Token.RP); + decompiler.addEOL(Token.LC); + + ++nestingOfWith; + Node body; + try { + body = statement(); + } finally { + --nestingOfWith; + } + + decompiler.addEOL(Token.RC); + + pn = nf.createWith(obj, body, lineno); + return pn; + } + + case Token.CONST: + case Token.VAR: { + consumeToken(); + pn = variables(tt); + break; + } + + case Token.RETURN: { + if (!insideFunction()) { + reportError("msg.bad.return"); + } + consumeToken(); + decompiler.addToken(Token.RETURN); + int lineno = ts.getLineno(); + + Node retExpr; + /* This is ugly, but we don't want to require a semicolon. */ + tt = peekTokenOrEOL(); + switch (tt) { + case Token.SEMI: + case Token.RC: + case Token.EOF: + case Token.EOL: + case Token.ERROR: + retExpr = null; + break; + default: + retExpr = expr(false); + hasReturnValue = true; + } + pn = nf.createReturn(retExpr, lineno); + + // see if we need a strict mode warning + if (retExpr == null) { + if (functionEndFlags == Node.END_RETURNS_VALUE) + addStrictWarning("msg.return.inconsistent", ""); + + functionEndFlags |= Node.END_RETURNS; + } else { + if (functionEndFlags == Node.END_RETURNS) + addStrictWarning("msg.return.inconsistent", ""); + + functionEndFlags |= Node.END_RETURNS_VALUE; + } + + break; + } + + case Token.LC: + consumeToken(); + if (statementLabel != null) { + decompiler.addToken(Token.LC); + } + pn = statements(); + mustMatchToken(Token.RC, "msg.no.brace.block"); + if (statementLabel != null) { + decompiler.addEOL(Token.RC); + } + return pn; + + case Token.ERROR: + // Fall thru, to have a node for error recovery to work on + case Token.SEMI: + consumeToken(); + pn = nf.createLeaf(Token.EMPTY); + return pn; + + case Token.FUNCTION: { + consumeToken(); + pn = function(FunctionNode.FUNCTION_EXPRESSION_STATEMENT); + return pn; + } + + case Token.DEFAULT : + consumeToken(); + mustHaveXML(); + + decompiler.addToken(Token.DEFAULT); + int nsLine = ts.getLineno(); + + if (!(matchToken(Token.NAME) + && ts.getString().equals("xml"))) + { + reportError("msg.bad.namespace"); + } + decompiler.addName(" xml"); + + if (!(matchToken(Token.NAME) + && ts.getString().equals("namespace"))) + { + reportError("msg.bad.namespace"); + } + decompiler.addName(" namespace"); + + if (!matchToken(Token.ASSIGN)) { + reportError("msg.bad.namespace"); + } + decompiler.addToken(Token.ASSIGN); + + Node expr = expr(false); + pn = nf.createDefaultNamespace(expr, nsLine); + break; + + case Token.NAME: { + int lineno = ts.getLineno(); + String name = ts.getString(); + setCheckForLabel(); + pn = expr(false); + if (pn.getType() != Token.LABEL) { + pn = nf.createExprStatement(pn, lineno); + } else { + // Parsed the label: push back token should be + // colon that primaryExpr left untouched. + if (peekToken() != Token.COLON) Kit.codeBug(); + consumeToken(); + // depend on decompiling lookahead to guess that that + // last name was a label. + decompiler.addName(name); + decompiler.addEOL(Token.COLON); + + if (labelSet == null) { + labelSet = new Hashtable(); + } else if (labelSet.containsKey(name)) { + reportError("msg.dup.label"); + } + + boolean firstLabel; + if (statementLabel == null) { + firstLabel = true; + statementLabel = pn; + } else { + // Discard multiple label nodes and use only + // the first: it allows to simplify IRFactory + firstLabel = false; + } + labelSet.put(name, statementLabel); + try { + pn = statementHelper(statementLabel); + } finally { + labelSet.remove(name); + } + if (firstLabel) { + pn = nf.createLabeledStatement(statementLabel, pn); + } + return pn; + } + break; + } + + default: { + int lineno = ts.getLineno(); + pn = expr(false); + pn = nf.createExprStatement(pn, lineno); + break; + } + } + + int ttFlagged = peekFlaggedToken(); + switch (ttFlagged & CLEAR_TI_MASK) { + case Token.SEMI: + // Consume ';' as a part of expression + consumeToken(); + break; + case Token.ERROR: + case Token.EOF: + case Token.RC: + // Autoinsert ; + break; + default: + if ((ttFlagged & TI_AFTER_EOL) == 0) { + // Report error if no EOL or autoinsert ; otherwise + reportError("msg.no.semi.stmt"); + } + break; + } + decompiler.addEOL(Token.SEMI); + + return pn; + } + + /** + * Parse a 'var' or 'const' statement, or a 'var' init list in a for + * statement. + * @param context A token value: either VAR, CONST or FOR depending on + * context. + * @return The parsed statement + * @throws IOException + * @throws ParserException + */ + private Node variables(int context) + throws IOException, ParserException + { + Node pn; + boolean first = true; + + if (context == Token.CONST){ + pn = nf.createVariables(Token.CONST, ts.getLineno()); + decompiler.addToken(Token.CONST); + } else { + pn = nf.createVariables(Token.VAR, ts.getLineno()); + decompiler.addToken(Token.VAR); + } + + for (;;) { + Node name; + Node init; + mustMatchToken(Token.NAME, "msg.bad.var"); + String s = ts.getString(); + + if (!first) + decompiler.addToken(Token.COMMA); + first = false; + + decompiler.addName(s); + + if (context == Token.CONST) { + if (!currentScriptOrFn.addConst(s)) { + // We know it's already defined, since addConst passes if + // it's not defined at all. The addVar call just confirms + // what it is. + if (currentScriptOrFn.addVar(s) != ScriptOrFnNode.DUPLICATE_CONST) + addError("msg.var.redecl", s); + else + addError("msg.const.redecl", s); + } + } else { + int dupState = currentScriptOrFn.addVar(s); + if (dupState == ScriptOrFnNode.DUPLICATE_CONST) + addError("msg.const.redecl", s); + else if (dupState == ScriptOrFnNode.DUPLICATE_PARAMETER) + addStrictWarning("msg.var.hides.arg", s); + else if (dupState == ScriptOrFnNode.DUPLICATE_VAR) + addStrictWarning("msg.var.redecl", s); + } + name = nf.createName(s); + + // omitted check for argument hiding + + if (matchToken(Token.ASSIGN)) { + decompiler.addToken(Token.ASSIGN); + + init = assignExpr(context == Token.FOR); + nf.addChildToBack(name, init); + } + nf.addChildToBack(pn, name); + if (!matchToken(Token.COMMA)) + break; + } + return pn; + } + + private Node expr(boolean inForInit) + throws IOException, ParserException + { + Node pn = assignExpr(inForInit); + while (matchToken(Token.COMMA)) { + decompiler.addToken(Token.COMMA); + if (compilerEnv.isStrictMode() && !pn.hasSideEffects()) + addStrictWarning("msg.no.side.effects", ""); + pn = nf.createBinary(Token.COMMA, pn, assignExpr(inForInit)); + } + return pn; + } + + private Node assignExpr(boolean inForInit) + throws IOException, ParserException + { + Node pn = condExpr(inForInit); + + int tt = peekToken(); + if (Token.FIRST_ASSIGN <= tt && tt <= Token.LAST_ASSIGN) { + consumeToken(); + decompiler.addToken(tt); + pn = nf.createAssignment(tt, pn, assignExpr(inForInit)); + } + + return pn; + } + + private Node condExpr(boolean inForInit) + throws IOException, ParserException + { + Node pn = orExpr(inForInit); + + if (matchToken(Token.HOOK)) { + decompiler.addToken(Token.HOOK); + Node ifTrue = assignExpr(false); + mustMatchToken(Token.COLON, "msg.no.colon.cond"); + decompiler.addToken(Token.COLON); + Node ifFalse = assignExpr(inForInit); + return nf.createCondExpr(pn, ifTrue, ifFalse); + } + + return pn; + } + + private Node orExpr(boolean inForInit) + throws IOException, ParserException + { + Node pn = andExpr(inForInit); + if (matchToken(Token.OR)) { + decompiler.addToken(Token.OR); + pn = nf.createBinary(Token.OR, pn, orExpr(inForInit)); + } + + return pn; + } + + private Node andExpr(boolean inForInit) + throws IOException, ParserException + { + Node pn = bitOrExpr(inForInit); + if (matchToken(Token.AND)) { + decompiler.addToken(Token.AND); + pn = nf.createBinary(Token.AND, pn, andExpr(inForInit)); + } + + return pn; + } + + private Node bitOrExpr(boolean inForInit) + throws IOException, ParserException + { + Node pn = bitXorExpr(inForInit); + while (matchToken(Token.BITOR)) { + decompiler.addToken(Token.BITOR); + pn = nf.createBinary(Token.BITOR, pn, bitXorExpr(inForInit)); + } + return pn; + } + + private Node bitXorExpr(boolean inForInit) + throws IOException, ParserException + { + Node pn = bitAndExpr(inForInit); + while (matchToken(Token.BITXOR)) { + decompiler.addToken(Token.BITXOR); + pn = nf.createBinary(Token.BITXOR, pn, bitAndExpr(inForInit)); + } + return pn; + } + + private Node bitAndExpr(boolean inForInit) + throws IOException, ParserException + { + Node pn = eqExpr(inForInit); + while (matchToken(Token.BITAND)) { + decompiler.addToken(Token.BITAND); + pn = nf.createBinary(Token.BITAND, pn, eqExpr(inForInit)); + } + return pn; + } + + private Node eqExpr(boolean inForInit) + throws IOException, ParserException + { + Node pn = relExpr(inForInit); + for (;;) { + int tt = peekToken(); + switch (tt) { + case Token.EQ: + case Token.NE: + case Token.SHEQ: + case Token.SHNE: + consumeToken(); + int decompilerToken = tt; + int parseToken = tt; + if (compilerEnv.getLanguageVersion() == Context.VERSION_1_2) { + // JavaScript 1.2 uses shallow equality for == and != . + // In addition, convert === and !== for decompiler into + // == and != since the decompiler is supposed to show + // canonical source and in 1.2 ===, !== are allowed + // only as an alias to ==, !=. + switch (tt) { + case Token.EQ: + parseToken = Token.SHEQ; + break; + case Token.NE: + parseToken = Token.SHNE; + break; + case Token.SHEQ: + decompilerToken = Token.EQ; + break; + case Token.SHNE: + decompilerToken = Token.NE; + break; + } + } + decompiler.addToken(decompilerToken); + pn = nf.createBinary(parseToken, pn, relExpr(inForInit)); + continue; + } + break; + } + return pn; + } + + private Node relExpr(boolean inForInit) + throws IOException, ParserException + { + Node pn = shiftExpr(); + for (;;) { + int tt = peekToken(); + switch (tt) { + case Token.IN: + if (inForInit) + break; + // fall through + case Token.INSTANCEOF: + case Token.LE: + case Token.LT: + case Token.GE: + case Token.GT: + consumeToken(); + decompiler.addToken(tt); + pn = nf.createBinary(tt, pn, shiftExpr()); + continue; + } + break; + } + return pn; + } + + private Node shiftExpr() + throws IOException, ParserException + { + Node pn = addExpr(); + for (;;) { + int tt = peekToken(); + switch (tt) { + case Token.LSH: + case Token.URSH: + case Token.RSH: + consumeToken(); + decompiler.addToken(tt); + pn = nf.createBinary(tt, pn, addExpr()); + continue; + } + break; + } + return pn; + } + + private Node addExpr() + throws IOException, ParserException + { + Node pn = mulExpr(); + for (;;) { + int tt = peekToken(); + if (tt == Token.ADD || tt == Token.SUB) { + consumeToken(); + decompiler.addToken(tt); + // flushNewLines + pn = nf.createBinary(tt, pn, mulExpr()); + continue; + } + break; + } + + return pn; + } + + private Node mulExpr() + throws IOException, ParserException + { + Node pn = unaryExpr(); + for (;;) { + int tt = peekToken(); + switch (tt) { + case Token.MUL: + case Token.DIV: + case Token.MOD: + consumeToken(); + decompiler.addToken(tt); + pn = nf.createBinary(tt, pn, unaryExpr()); + continue; + } + break; + } + + return pn; + } + + private Node unaryExpr() + throws IOException, ParserException + { + int tt; + + tt = peekToken(); + + switch(tt) { + case Token.VOID: + case Token.NOT: + case Token.BITNOT: + case Token.TYPEOF: + consumeToken(); + decompiler.addToken(tt); + return nf.createUnary(tt, unaryExpr()); + + case Token.ADD: + consumeToken(); + // Convert to special POS token in decompiler and parse tree + decompiler.addToken(Token.POS); + return nf.createUnary(Token.POS, unaryExpr()); + + case Token.SUB: + consumeToken(); + // Convert to special NEG token in decompiler and parse tree + decompiler.addToken(Token.NEG); + return nf.createUnary(Token.NEG, unaryExpr()); + + case Token.INC: + case Token.DEC: + consumeToken(); + decompiler.addToken(tt); + return nf.createIncDec(tt, false, memberExpr(true)); + + case Token.DELPROP: + consumeToken(); + decompiler.addToken(Token.DELPROP); + return nf.createUnary(Token.DELPROP, unaryExpr()); + + case Token.ERROR: + consumeToken(); + break; + + // XML stream encountered in expression. + case Token.LT: + if (compilerEnv.isXmlAvailable()) { + consumeToken(); + Node pn = xmlInitializer(); + return memberExprTail(true, pn); + } + // Fall thru to the default handling of RELOP + + default: + Node pn = memberExpr(true); + + // Don't look across a newline boundary for a postfix incop. + tt = peekTokenOrEOL(); + if (tt == Token.INC || tt == Token.DEC) { + consumeToken(); + decompiler.addToken(tt); + return nf.createIncDec(tt, true, pn); + } + return pn; + } + return nf.createName("err"); // Only reached on error. Try to continue. + + } + + private Node xmlInitializer() throws IOException + { + int tt = ts.getFirstXMLToken(); + if (tt != Token.XML && tt != Token.XMLEND) { + reportError("msg.syntax"); + return null; + } + + /* Make a NEW node to append to. */ + Node pnXML = nf.createLeaf(Token.NEW); + + String xml = ts.getString(); + boolean fAnonymous = xml.trim().startsWith("<>"); + + Node pn = nf.createName(fAnonymous ? "XMLList" : "XML"); + nf.addChildToBack(pnXML, pn); + + pn = null; + Node expr; + for (;;tt = ts.getNextXMLToken()) { + switch (tt) { + case Token.XML: + xml = ts.getString(); + decompiler.addName(xml); + mustMatchToken(Token.LC, "msg.syntax"); + decompiler.addToken(Token.LC); + expr = (peekToken() == Token.RC) + ? nf.createString("") + : expr(false); + mustMatchToken(Token.RC, "msg.syntax"); + decompiler.addToken(Token.RC); + if (pn == null) { + pn = nf.createString(xml); + } else { + pn = nf.createBinary(Token.ADD, pn, nf.createString(xml)); + } + if (ts.isXMLAttribute()) { + /* Need to put the result in double quotes */ + expr = nf.createUnary(Token.ESCXMLATTR, expr); + Node prepend = nf.createBinary(Token.ADD, + nf.createString("\""), + expr); + expr = nf.createBinary(Token.ADD, + prepend, + nf.createString("\"")); + } else { + expr = nf.createUnary(Token.ESCXMLTEXT, expr); + } + pn = nf.createBinary(Token.ADD, pn, expr); + break; + case Token.XMLEND: + xml = ts.getString(); + decompiler.addName(xml); + if (pn == null) { + pn = nf.createString(xml); + } else { + pn = nf.createBinary(Token.ADD, pn, nf.createString(xml)); + } + + nf.addChildToBack(pnXML, pn); + return pnXML; + default: + reportError("msg.syntax"); + return null; + } + } + } + + private void argumentList(Node listNode) + throws IOException, ParserException + { + boolean matched; + matched = matchToken(Token.RP); + if (!matched) { + boolean first = true; + do { + if (!first) + decompiler.addToken(Token.COMMA); + first = false; + nf.addChildToBack(listNode, assignExpr(false)); + } while (matchToken(Token.COMMA)); + + mustMatchToken(Token.RP, "msg.no.paren.arg"); + } + decompiler.addToken(Token.RP); + } + + private Node memberExpr(boolean allowCallSyntax) + throws IOException, ParserException + { + int tt; + + Node pn; + + /* Check for new expressions. */ + tt = peekToken(); + if (tt == Token.NEW) { + /* Eat the NEW token. */ + consumeToken(); + decompiler.addToken(Token.NEW); + + /* Make a NEW node to append to. */ + pn = nf.createCallOrNew(Token.NEW, memberExpr(false)); + + if (matchToken(Token.LP)) { + decompiler.addToken(Token.LP); + /* Add the arguments to pn, if any are supplied. */ + argumentList(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 = peekToken(); + if (tt == Token.LC) { + nf.addChildToBack(pn, primaryExpr()); + } + } else { + pn = primaryExpr(); + } + + return memberExprTail(allowCallSyntax, pn); + } + + private Node memberExprTail(boolean allowCallSyntax, Node pn) + throws IOException, ParserException + { + tailLoop: + for (;;) { + int tt = peekToken(); + switch (tt) { + + case Token.DOT: + case Token.DOTDOT: + { + int memberTypeFlags; + String s; + + consumeToken(); + decompiler.addToken(tt); + memberTypeFlags = 0; + if (tt == Token.DOTDOT) { + mustHaveXML(); + memberTypeFlags = Node.DESCENDANTS_FLAG; + } + if (!compilerEnv.isXmlAvailable()) { + mustMatchToken(Token.NAME, "msg.no.name.after.dot"); + s = ts.getString(); + decompiler.addName(s); + pn = nf.createPropertyGet(pn, null, s, memberTypeFlags); + break; + } + + tt = nextToken(); + switch (tt) { + // handles: name, ns::name, ns::*, ns::[expr] + case Token.NAME: + s = ts.getString(); + decompiler.addName(s); + pn = propertyName(pn, s, memberTypeFlags); + break; + + // handles: *, *::name, *::*, *::[expr] + case Token.MUL: + decompiler.addName("*"); + pn = propertyName(pn, "*", memberTypeFlags); + break; + + // handles: '@attr', '@ns::attr', '@ns::*', '@ns::*', + // '@::attr', '@::*', '@*', '@*::attr', '@*::*' + case Token.XMLATTR: + decompiler.addToken(Token.XMLATTR); + pn = attributeAccess(pn, memberTypeFlags); + break; + + default: + reportError("msg.no.name.after.dot"); + } + } + break; + + case Token.DOTQUERY: + consumeToken(); + mustHaveXML(); + decompiler.addToken(Token.DOTQUERY); + pn = nf.createDotQuery(pn, expr(false), ts.getLineno()); + mustMatchToken(Token.RP, "msg.no.paren"); + decompiler.addToken(Token.RP); + break; + + case Token.LB: + consumeToken(); + decompiler.addToken(Token.LB); + pn = nf.createElementGet(pn, null, expr(false), 0); + mustMatchToken(Token.RB, "msg.no.bracket.index"); + decompiler.addToken(Token.RB); + break; + + case Token.LP: + if (!allowCallSyntax) { + break tailLoop; + } + consumeToken(); + decompiler.addToken(Token.LP); + pn = nf.createCallOrNew(Token.CALL, pn); + /* Add the arguments to pn, if any are supplied. */ + argumentList(pn); + break; + + default: + break tailLoop; + } + } + return pn; + } + + /* + * Xml attribute expression: + * '@attr', '@ns::attr', '@ns::*', '@ns::*', '@*', '@*::attr', '@*::*' + */ + private Node attributeAccess(Node pn, int memberTypeFlags) + throws IOException + { + memberTypeFlags |= Node.ATTRIBUTE_FLAG; + int tt = nextToken(); + + switch (tt) { + // handles: @name, @ns::name, @ns::*, @ns::[expr] + case Token.NAME: + { + String s = ts.getString(); + decompiler.addName(s); + pn = propertyName(pn, s, memberTypeFlags); + } + break; + + // handles: @*, @*::name, @*::*, @*::[expr] + case Token.MUL: + decompiler.addName("*"); + pn = propertyName(pn, "*", memberTypeFlags); + break; + + // handles @[expr] + case Token.LB: + decompiler.addToken(Token.LB); + pn = nf.createElementGet(pn, null, expr(false), memberTypeFlags); + mustMatchToken(Token.RB, "msg.no.bracket.index"); + decompiler.addToken(Token.RB); + break; + + default: + reportError("msg.no.name.after.xmlAttr"); + pn = nf.createPropertyGet(pn, null, "?", memberTypeFlags); + break; + } + + return pn; + } + + /** + * Check if :: follows name in which case it becomes qualified name + */ + private Node propertyName(Node pn, String name, int memberTypeFlags) + throws IOException, ParserException + { + String namespace = null; + if (matchToken(Token.COLONCOLON)) { + decompiler.addToken(Token.COLONCOLON); + namespace = name; + + int tt = nextToken(); + switch (tt) { + // handles name::name + case Token.NAME: + name = ts.getString(); + decompiler.addName(name); + break; + + // handles name::* + case Token.MUL: + decompiler.addName("*"); + name = "*"; + break; + + // handles name::[expr] + case Token.LB: + decompiler.addToken(Token.LB); + pn = nf.createElementGet(pn, namespace, expr(false), + memberTypeFlags); + mustMatchToken(Token.RB, "msg.no.bracket.index"); + decompiler.addToken(Token.RB); + return pn; + + default: + reportError("msg.no.name.after.coloncolon"); + name = "?"; + } + } + + pn = nf.createPropertyGet(pn, namespace, name, memberTypeFlags); + return pn; + } + + private Node primaryExpr() + throws IOException, ParserException + { + Node pn; + + int ttFlagged = nextFlaggedToken(); + int tt = ttFlagged & CLEAR_TI_MASK; + + switch(tt) { + + case Token.FUNCTION: + return function(FunctionNode.FUNCTION_EXPRESSION); + + case Token.LB: { + ObjArray elems = new ObjArray(); + int skipCount = 0; + decompiler.addToken(Token.LB); + boolean after_lb_or_comma = true; + for (;;) { + tt = peekToken(); + + if (tt == Token.COMMA) { + consumeToken(); + decompiler.addToken(Token.COMMA); + if (!after_lb_or_comma) { + after_lb_or_comma = true; + } else { + elems.add(null); + ++skipCount; + } + } else if (tt == Token.RB) { + consumeToken(); + decompiler.addToken(Token.RB); + break; + } else { + if (!after_lb_or_comma) { + reportError("msg.no.bracket.arg"); + } + elems.add(assignExpr(false)); + after_lb_or_comma = false; + } + } + return nf.createArrayLiteral(elems, skipCount); + } + + case Token.LC: { + ObjArray elems = new ObjArray(); + decompiler.addToken(Token.LC); + if (!matchToken(Token.RC)) { + + boolean first = true; + commaloop: + do { + Object property; + + if (!first) + decompiler.addToken(Token.COMMA); + else + first = false; + + tt = peekToken(); + switch(tt) { + case Token.NAME: + case Token.STRING: + consumeToken(); + // map NAMEs to STRINGs in object literal context + // but tell the decompiler the proper type + String s = ts.getString(); + if (tt == Token.NAME) { + if (s.equals("get") && + peekToken() == Token.NAME) { + decompiler.addToken(Token.GET); + consumeToken(); + s = ts.getString(); + decompiler.addName(s); + property = ScriptRuntime.getIndexObject(s); + if (!getterSetterProperty(elems, property, + true)) + break commaloop; + break; + } else if (s.equals("set") && + peekToken() == Token.NAME) { + decompiler.addToken(Token.SET); + consumeToken(); + s = ts.getString(); + decompiler.addName(s); + property = ScriptRuntime.getIndexObject(s); + if (!getterSetterProperty(elems, property, + false)) + break commaloop; + break; + } + decompiler.addName(s); + } else { + decompiler.addString(s); + } + property = ScriptRuntime.getIndexObject(s); + plainProperty(elems, property); + break; + + case Token.NUMBER: + consumeToken(); + double n = ts.getNumber(); + decompiler.addNumber(n); + property = ScriptRuntime.getIndexObject(n); + plainProperty(elems, property); + break; + + case Token.RC: + // trailing comma is OK. + break commaloop; + default: + reportError("msg.bad.prop"); + break commaloop; + } + } while (matchToken(Token.COMMA)); + + mustMatchToken(Token.RC, "msg.no.brace.prop"); + } + decompiler.addToken(Token.RC); + return nf.createObjectLiteral(elems); + } + + case Token.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.' */ + decompiler.addToken(Token.LP); + pn = expr(false); + pn.putProp(Node.PARENTHESIZED_PROP, Boolean.TRUE); + decompiler.addToken(Token.RP); + mustMatchToken(Token.RP, "msg.no.paren"); + return pn; + + case Token.XMLATTR: + mustHaveXML(); + decompiler.addToken(Token.XMLATTR); + pn = attributeAccess(null, 0); + return pn; + + case Token.NAME: { + String name = ts.getString(); + if ((ttFlagged & TI_CHECK_LABEL) != 0) { + if (peekToken() == Token.COLON) { + // Do not consume colon, it is used as unwind indicator + // to return to statementHelper. + // XXX Better way? + return nf.createLabel(ts.getLineno()); + } + } + + decompiler.addName(name); + if (compilerEnv.isXmlAvailable()) { + pn = propertyName(null, name, 0); + } else { + pn = nf.createName(name); + } + return pn; + } + + case Token.NUMBER: { + double n = ts.getNumber(); + decompiler.addNumber(n); + return nf.createNumber(n); + } + + case Token.STRING: { + String s = ts.getString(); + decompiler.addString(s); + return nf.createString(s); + } + + case Token.DIV: + case Token.ASSIGN_DIV: { + // Got / or /= which should be treated as regexp in fact + ts.readRegExp(tt); + String flags = ts.regExpFlags; + ts.regExpFlags = null; + String re = ts.getString(); + decompiler.addRegexp(re, flags); + int index = currentScriptOrFn.addRegexp(re, flags); + return nf.createRegExp(index); + } + + case Token.NULL: + case Token.THIS: + case Token.FALSE: + case Token.TRUE: + decompiler.addToken(tt); + return nf.createLeaf(tt); + + case Token.RESERVED: + reportError("msg.reserved.id"); + break; + + case Token.ERROR: + /* the scanner or one of its subroutines reported the error. */ + break; + + case Token.EOF: + reportError("msg.unexpected.eof"); + break; + + default: + reportError("msg.syntax"); + break; + } + return null; // should never reach here + } + + private void plainProperty(ObjArray elems, Object property) + throws IOException { + mustMatchToken(Token.COLON, "msg.no.colon.prop"); + + // OBJLIT is used as ':' in object literal for + // decompilation to solve spacing ambiguity. + decompiler.addToken(Token.OBJECTLIT); + elems.add(property); + elems.add(assignExpr(false)); + } + + private boolean getterSetterProperty(ObjArray elems, Object property, + boolean isGetter) throws IOException { + Node f = function(FunctionNode.FUNCTION_EXPRESSION); + if (f.getType() != Token.FUNCTION) { + reportError("msg.bad.prop"); + return false; + } + int fnIndex = f.getExistingIntProp(Node.FUNCTION_PROP); + FunctionNode fn = currentScriptOrFn.getFunctionNode(fnIndex); + if (fn.getFunctionName().length() != 0) { + reportError("msg.bad.prop"); + return false; + } + elems.add(property); + if (isGetter) { + elems.add(nf.createUnary(Token.GET, f)); + } else { + elems.add(nf.createUnary(Token.SET, f)); + } + return true; + } +} |