/* -*- 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.IOException; import java.io.Reader; 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. // XX 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) { while ((tt = ts.getToken()) == Token.CONDCOMMENT || tt == Token.KEEPCOMMENT) { if (tt == Token.CONDCOMMENT) { /* Support for JScript conditional comments */ decompiler.addJScriptConditionalComment(ts.getString()); } else { /* Support for preserved comments */ decompiler.addPreservedComment(ts.getString()); } } if (tt == Token.EOL) { do { tt = ts.getToken(); if (tt == Token.CONDCOMMENT) { /* Support for JScript conditional comments */ decompiler.addJScriptConditionalComment(ts.getString()); } else if (tt == Token.KEEPCOMMENT) { /* Support for preserved comments */ decompiler.addPreservedComment(ts.getString()); } } while (tt == Token.EOL || tt == Token.CONDCOMMENT || tt == Token.KEEPCOMMENT); 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) { // XX 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); } /* XX 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. // XX 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; } }