Categories:
Audio (13)
Biotech (29)
Bytecode (36)
Database (77)
Framework (7)
Game (7)
General (507)
Graphics (53)
I/O (35)
IDE (2)
JAR Tools (101)
JavaBeans (21)
JDBC (121)
JDK (426)
JSP (20)
Logging (108)
Mail (58)
Messaging (8)
Network (84)
PDF (97)
Report (7)
Scripting (84)
Security (32)
Server (121)
Servlet (26)
SOAP (24)
Testing (54)
Web (15)
XML (309)
Collections:
Other Resources:
Rhino JavaScript Java Library Source Code
Rhino JavaScript Java Library is an open-source implementation of JavaScript written entirely in Java.
Rhino JavaScript Java Library Source Code files are provided in binary package (rhino-1.7.14.zip).
You can also browse the source code below:
✍: FYIcenter.com
⏎ org/mozilla/javascript/Parser.java
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.mozilla.javascript; import java.io.IOException; import java.io.Reader; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.mozilla.javascript.ast.ArrayComprehension; import org.mozilla.javascript.ast.ArrayComprehensionLoop; import org.mozilla.javascript.ast.ArrayLiteral; import org.mozilla.javascript.ast.Assignment; import org.mozilla.javascript.ast.AstNode; import org.mozilla.javascript.ast.AstRoot; import org.mozilla.javascript.ast.BigIntLiteral; import org.mozilla.javascript.ast.Block; import org.mozilla.javascript.ast.BreakStatement; import org.mozilla.javascript.ast.CatchClause; import org.mozilla.javascript.ast.Comment; import org.mozilla.javascript.ast.ConditionalExpression; import org.mozilla.javascript.ast.ContinueStatement; import org.mozilla.javascript.ast.DestructuringForm; import org.mozilla.javascript.ast.DoLoop; import org.mozilla.javascript.ast.ElementGet; import org.mozilla.javascript.ast.EmptyExpression; import org.mozilla.javascript.ast.EmptyStatement; import org.mozilla.javascript.ast.ErrorNode; import org.mozilla.javascript.ast.ExpressionStatement; import org.mozilla.javascript.ast.ForInLoop; import org.mozilla.javascript.ast.ForLoop; import org.mozilla.javascript.ast.FunctionCall; import org.mozilla.javascript.ast.FunctionNode; import org.mozilla.javascript.ast.GeneratorExpression; import org.mozilla.javascript.ast.GeneratorExpressionLoop; import org.mozilla.javascript.ast.IdeErrorReporter; import org.mozilla.javascript.ast.IfStatement; import org.mozilla.javascript.ast.InfixExpression; import org.mozilla.javascript.ast.Jump; import org.mozilla.javascript.ast.KeywordLiteral; import org.mozilla.javascript.ast.Label; import org.mozilla.javascript.ast.LabeledStatement; import org.mozilla.javascript.ast.LetNode; import org.mozilla.javascript.ast.Loop; import org.mozilla.javascript.ast.Name; import org.mozilla.javascript.ast.NewExpression; import org.mozilla.javascript.ast.NumberLiteral; import org.mozilla.javascript.ast.ObjectLiteral; import org.mozilla.javascript.ast.ObjectProperty; import org.mozilla.javascript.ast.ParenthesizedExpression; import org.mozilla.javascript.ast.PropertyGet; import org.mozilla.javascript.ast.RegExpLiteral; import org.mozilla.javascript.ast.ReturnStatement; import org.mozilla.javascript.ast.Scope; import org.mozilla.javascript.ast.ScriptNode; import org.mozilla.javascript.ast.StringLiteral; import org.mozilla.javascript.ast.SwitchCase; import org.mozilla.javascript.ast.SwitchStatement; import org.mozilla.javascript.ast.Symbol; import org.mozilla.javascript.ast.TaggedTemplateLiteral; import org.mozilla.javascript.ast.TemplateCharacters; import org.mozilla.javascript.ast.TemplateLiteral; import org.mozilla.javascript.ast.ThrowStatement; import org.mozilla.javascript.ast.TryStatement; import org.mozilla.javascript.ast.UnaryExpression; import org.mozilla.javascript.ast.UpdateExpression; import org.mozilla.javascript.ast.VariableDeclaration; import org.mozilla.javascript.ast.VariableInitializer; import org.mozilla.javascript.ast.WhileLoop; import org.mozilla.javascript.ast.WithStatement; import org.mozilla.javascript.ast.XmlDotQuery; import org.mozilla.javascript.ast.XmlElemRef; import org.mozilla.javascript.ast.XmlExpression; import org.mozilla.javascript.ast.XmlLiteral; import org.mozilla.javascript.ast.XmlMemberGet; import org.mozilla.javascript.ast.XmlPropRef; import org.mozilla.javascript.ast.XmlRef; import org.mozilla.javascript.ast.XmlString; import org.mozilla.javascript.ast.Yield; /** * This class implements the JavaScript parser. * * <p>It is based on the SpiderMonkey C source files jsparse.c and jsparse.h in the jsref package. * * <p>The parser generates an {@link AstRoot} parse tree representing the source code. No tree * rewriting is permitted at this stage, so that the parse tree is a faithful representation of the * source for frontend processing tools and IDEs. * * <p>This parser implementation is not intended to be reused after a parse finishes, and will throw * an IllegalStateException() if invoked again. * * <p> * * @see TokenStream * @author Mike McCabe * @author Brendan Eich */ public class Parser { /** Maximum number of allowed function or constructor arguments, to follow SpiderMonkey. */ public static final int ARGC_LIMIT = 1 << 16; // TokenInformation flags : currentFlaggedToken stores them together // with token type static final 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 IdeErrorReporter errorCollector; private String sourceURI; private char[] sourceChars; boolean calledByCompileFunction; // ugly - set directly by Context private boolean parseFinished; // set when finished to prevent reuse private TokenStream ts; private int currentFlaggedToken = Token.EOF; private int currentToken; private int syntaxErrorCount; private List<Comment> scannedComments; private Comment currentJsDocComment; protected int nestingOfFunction; private LabeledStatement currentLabel; private boolean inDestructuringAssignment; protected boolean inUseStrictDirective; // The following are per function variables and should be saved/restored // during function parsing. See PerFunctionVariables class below. ScriptNode currentScriptOrFn; Scope currentScope; private int endFlags; private boolean inForInit; // bound temporarily during forStatement() private Map<String, LabeledStatement> labelSet; private List<Loop> loopSet; private List<Jump> loopAndSwitchSet; // end of per function variables // Lacking 2-token lookahead, labels become a problem. // These vars store the token info of the last matched name, // iff it wasn't the last matched token. private int prevNameTokenStart; private String prevNameTokenString = ""; private int prevNameTokenLineno; private boolean defaultUseStrictDirective; // Exception to unwind private static class ParserException extends RuntimeException { private static final long serialVersionUID = 5882582646773765630L; } public Parser() { this(new CompilerEnvirons()); } public Parser(CompilerEnvirons compilerEnv) { this(compilerEnv, compilerEnv.getErrorReporter()); } public Parser(CompilerEnvirons compilerEnv, ErrorReporter errorReporter) { this.compilerEnv = compilerEnv; this.errorReporter = errorReporter; if (errorReporter instanceof IdeErrorReporter) { errorCollector = (IdeErrorReporter) errorReporter; } } // Add a strict warning on the last matched token. void addStrictWarning(String messageId, String messageArg) { int beg = -1, end = -1; if (ts != null) { beg = ts.tokenBeg; end = ts.tokenEnd - ts.tokenBeg; } addStrictWarning(messageId, messageArg, beg, end); } void addStrictWarning(String messageId, String messageArg, int position, int length) { if (compilerEnv.isStrictMode()) addWarning(messageId, messageArg, position, length); } void addWarning(String messageId, String messageArg) { int beg = -1, end = -1; if (ts != null) { beg = ts.tokenBeg; end = ts.tokenEnd - ts.tokenBeg; } addWarning(messageId, messageArg, beg, end); } void addWarning(String messageId, int position, int length) { addWarning(messageId, null, position, length); } void addWarning(String messageId, String messageArg, int position, int length) { String message = lookupMessage(messageId, messageArg); if (compilerEnv.reportWarningAsError()) { addError(messageId, messageArg, position, length); } else if (errorCollector != null) { errorCollector.warning(message, sourceURI, position, length); } else if (ts != null) { errorReporter.warning(message, sourceURI, ts.getLineno(), ts.getLine(), ts.getOffset()); } else { errorReporter.warning(message, sourceURI, 1, "", 1); } } void addError(String messageId) { if (ts == null) { addError(messageId, 0, 0); } else { addError(messageId, ts.tokenBeg, ts.tokenEnd - ts.tokenBeg); } } void addError(String messageId, int position, int length) { addError(messageId, null, position, length); } void addError(String messageId, String messageArg) { if (ts == null) { addError(messageId, messageArg, 0, 0); } else { addError(messageId, messageArg, ts.tokenBeg, ts.tokenEnd - ts.tokenBeg); } } void addError(String messageId, int c) { String messageArg = Character.toString((char) c); addError(messageId, messageArg); } void addError(String messageId, String messageArg, int position, int length) { ++syntaxErrorCount; String message = lookupMessage(messageId, messageArg); if (errorCollector != null) { errorCollector.error(message, sourceURI, position, length); } else { int lineno = 1, offset = 1; String line = ""; if (ts != null) { // happens in some regression tests lineno = ts.getLineno(); line = ts.getLine(); offset = ts.getOffset(); } errorReporter.error(message, sourceURI, lineno, line, offset); } } private void addStrictWarning( String messageId, String messageArg, int position, int length, int line, String lineSource, int lineOffset) { if (compilerEnv.isStrictMode()) { addWarning(messageId, messageArg, position, length, line, lineSource, lineOffset); } } private void addWarning( String messageId, String messageArg, int position, int length, int line, String lineSource, int lineOffset) { String message = lookupMessage(messageId, messageArg); if (compilerEnv.reportWarningAsError()) { addError(messageId, messageArg, position, length, line, lineSource, lineOffset); } else if (errorCollector != null) { errorCollector.warning(message, sourceURI, position, length); } else { errorReporter.warning(message, sourceURI, line, lineSource, lineOffset); } } private void addError( String messageId, String messageArg, int position, int length, int line, String lineSource, int lineOffset) { ++syntaxErrorCount; String message = lookupMessage(messageId, messageArg); if (errorCollector != null) { errorCollector.error(message, sourceURI, position, length); } else { errorReporter.error(message, sourceURI, line, lineSource, lineOffset); } } String lookupMessage(String messageId) { return lookupMessage(messageId, null); } String lookupMessage(String messageId, String messageArg) { return messageArg == null ? ScriptRuntime.getMessageById(messageId) : ScriptRuntime.getMessageById(messageId, messageArg); } void reportError(String messageId) { reportError(messageId, null); } void reportError(String messageId, String messageArg) { if (ts == null) { // happens in some regression tests reportError(messageId, messageArg, 1, 1); } else { reportError(messageId, messageArg, ts.tokenBeg, ts.tokenEnd - ts.tokenBeg); } } void reportError(String messageId, int position, int length) { reportError(messageId, null, position, length); } void reportError(String messageId, String messageArg, int position, int length) { addError(messageId, messageArg, position, length); if (!compilerEnv.recoverFromErrors()) { throw new ParserException(); } } // Computes the absolute end offset of node N. // Use with caution! Assumes n.getPosition() is -absolute-, which // is only true before the node is added to its parent. private static int getNodeEnd(AstNode n) { return n.getPosition() + n.getLength(); } private void recordComment(int lineno, String comment) { if (scannedComments == null) { scannedComments = new ArrayList<Comment>(); } Comment commentNode = new Comment(ts.tokenBeg, ts.getTokenLength(), ts.commentType, comment); if (ts.commentType == Token.CommentType.JSDOC && compilerEnv.isRecordingLocalJsDocComments()) { Comment jsDocCommentNode = new Comment(ts.tokenBeg, ts.getTokenLength(), ts.commentType, comment); currentJsDocComment = jsDocCommentNode; currentJsDocComment.setLineno(lineno); } commentNode.setLineno(lineno); scannedComments.add(commentNode); } private Comment getAndResetJsDoc() { Comment saved = currentJsDocComment; currentJsDocComment = null; return saved; } // Returns the next token without consuming it. // If previous token was consumed, calls scanner to get new token. // If previous token was -not- consumed, returns it (idempotent). // // This function will not return a newline (Token.EOL - instead, it // gobbles newlines until it finds a non-newline token, and flags // that token as appearing just after a newline. // // This function will also not return a Token.COMMENT. Instead, it // records comments in the scannedComments list. If the token // returned by this function immediately follows a jsdoc comment, // the token is flagged as such. // // Note that this function always returned the un-flagged token! // The flags, if any, are saved in currentFlaggedToken. private int peekToken() throws IOException { // By far the most common case: last token hasn't been consumed, // so return already-peeked token. if (currentFlaggedToken != Token.EOF) { return currentToken; } int lineno = ts.getLineno(); int tt = ts.getToken(); boolean sawEOL = false; // process comments and whitespace while (tt == Token.EOL || tt == Token.COMMENT) { if (tt == Token.EOL) { lineno++; sawEOL = true; tt = ts.getToken(); } else { if (compilerEnv.isRecordingComments()) { String comment = ts.getAndResetCurrentComment(); recordComment(lineno, comment); break; } tt = ts.getToken(); } } currentToken = tt; currentFlaggedToken = tt | (sawEOL ? TI_AFTER_EOL : 0); return currentToken; // return unflagged token } 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 boolean matchToken(int toMatch, boolean ignoreComment) throws IOException { int tt = peekToken(); while (tt == Token.COMMENT && ignoreComment) { consumeToken(); tt = peekToken(); } if (tt != toMatch) { return false; } consumeToken(); return true; } // Returns Token.EOL if the current token follows a newline, else returns // the current token. Used in situations where we don't consider certain // token types valid if they are preceded by a newline. One example is the // postfix ++ or -- operator, which has to be on the same line as its // operand. 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 boolean mustMatchToken(int toMatch, String messageId, boolean ignoreComment) throws IOException { return mustMatchToken( toMatch, messageId, ts.tokenBeg, ts.tokenEnd - ts.tokenBeg, ignoreComment); } private boolean mustMatchToken( int toMatch, String msgId, int pos, int len, boolean ignoreComment) throws IOException { if (matchToken(toMatch, ignoreComment)) { return true; } reportError(msgId, pos, len); return false; } private void mustHaveXML() { if (!compilerEnv.isXmlAvailable()) { reportError("msg.XML.not.available"); } } public boolean eof() { return ts.eof(); } boolean insideFunction() { return nestingOfFunction != 0; } void pushScope(Scope scope) { Scope parent = scope.getParentScope(); // During codegen, parent scope chain may already be initialized, // in which case we just need to set currentScope variable. if (parent != null) { if (parent != currentScope) codeBug(); } else { currentScope.addChildScope(scope); } currentScope = scope; } void popScope() { currentScope = currentScope.getParentScope(); } private void enterLoop(Loop loop) { if (loopSet == null) loopSet = new ArrayList<Loop>(); loopSet.add(loop); if (loopAndSwitchSet == null) loopAndSwitchSet = new ArrayList<Jump>(); loopAndSwitchSet.add(loop); pushScope(loop); if (currentLabel != null) { currentLabel.setStatement(loop); currentLabel.getFirstLabel().setLoop(loop); // This is the only time during parsing that we set a node's parent // before parsing the children. In order for the child node offsets // to be correct, we adjust the loop's reported position back to an // absolute source offset, and restore it when we call // restoreRelativeLoopPosition() (invoked just before setBody() is // called on the loop). loop.setRelative(-currentLabel.getPosition()); } } private void exitLoop() { loopSet.remove(loopSet.size() - 1); loopAndSwitchSet.remove(loopAndSwitchSet.size() - 1); popScope(); } private void restoreRelativeLoopPosition(Loop loop) { if (loop.getParent() != null) { // see comment in enterLoop loop.setRelative(loop.getParent().getPosition()); } } private void enterSwitch(SwitchStatement node) { if (loopAndSwitchSet == null) loopAndSwitchSet = new ArrayList<Jump>(); loopAndSwitchSet.add(node); } private void exitSwitch() { loopAndSwitchSet.remove(loopAndSwitchSet.size() - 1); } /** * Builds a parse tree from the given source string. * * @return an {@link AstRoot} object representing the parsed program. If the parse fails, {@code * null} will be returned. (The parse failure will result in a call to the {@link * ErrorReporter} from {@link CompilerEnvirons}.) */ public AstRoot parse(String sourceString, String sourceURI, int lineno) { if (parseFinished) throw new IllegalStateException("parser reused"); this.sourceURI = sourceURI; if (compilerEnv.isIdeMode()) { this.sourceChars = sourceString.toCharArray(); } this.ts = new TokenStream(this, null, sourceString, lineno); try { return parse(); } catch (IOException iox) { // Should never happen throw new IllegalStateException(); } finally { parseFinished = true; } } /** * Builds a parse tree from the given sourcereader. * * @see #parse(String,String,int) * @throws IOException if the {@link Reader} encounters an error * @deprecated use parse(String, String, int) instead */ @Deprecated public AstRoot parse(Reader sourceReader, String sourceURI, int lineno) throws IOException { if (parseFinished) throw new IllegalStateException("parser reused"); if (compilerEnv.isIdeMode()) { return parse(Kit.readReader(sourceReader), sourceURI, lineno); } try { this.sourceURI = sourceURI; ts = new TokenStream(this, sourceReader, null, lineno); return parse(); } finally { parseFinished = true; } } private AstRoot parse() throws IOException { int pos = 0; AstRoot root = new AstRoot(pos); currentScope = currentScriptOrFn = root; int baseLineno = ts.lineno; // line number where source starts int end = pos; // in case source is empty boolean inDirectivePrologue = true; boolean savedStrictMode = inUseStrictDirective; inUseStrictDirective = defaultUseStrictDirective; if (inUseStrictDirective) { root.setInStrictMode(true); } try { for (; ; ) { int tt = peekToken(); if (tt <= Token.EOF) { break; } AstNode n; if (tt == Token.FUNCTION) { consumeToken(); try { n = function( calledByCompileFunction ? FunctionNode.FUNCTION_EXPRESSION : FunctionNode.FUNCTION_STATEMENT); } catch (ParserException e) { break; } } else if (tt == Token.COMMENT) { n = scannedComments.get(scannedComments.size() - 1); consumeToken(); } else { n = statement(); if (inDirectivePrologue) { String directive = getDirective(n); if (directive == null) { inDirectivePrologue = false; } else if (directive.equals("use strict")) { inUseStrictDirective = true; root.setInStrictMode(true); } } } end = getNodeEnd(n); root.addChildToBack(n); n.setParent(root); } } catch (StackOverflowError ex) { String msg = lookupMessage("msg.too.deep.parser.recursion"); if (!compilerEnv.isIdeMode()) throw Context.reportRuntimeError(msg, sourceURI, ts.lineno, null, 0); } finally { inUseStrictDirective = savedStrictMode; } if (this.syntaxErrorCount != 0) { String msg = String.valueOf(this.syntaxErrorCount); msg = lookupMessage("msg.got.syntax.errors", msg); if (!compilerEnv.isIdeMode()) throw errorReporter.runtimeError(msg, sourceURI, baseLineno, null, 0); } // add comments to root in lexical order if (scannedComments != null) { // If we find a comment beyond end of our last statement or // function, extend the root bounds to the end of that comment. int last = scannedComments.size() - 1; end = Math.max(end, getNodeEnd(scannedComments.get(last))); for (Comment c : scannedComments) { root.addComment(c); } } root.setLength(end - pos); root.setSourceName(sourceURI); root.setBaseLineno(baseLineno); root.setEndLineno(ts.lineno); return root; } private AstNode parseFunctionBody(int type, FunctionNode fnNode) throws IOException { boolean isExpressionClosure = false; if (!matchToken(Token.LC, true)) { if (compilerEnv.getLanguageVersion() < Context.VERSION_1_8 && type != FunctionNode.ARROW_FUNCTION) { reportError("msg.no.brace.body"); } else { isExpressionClosure = true; } } boolean isArrow = type == FunctionNode.ARROW_FUNCTION; ++nestingOfFunction; int pos = ts.tokenBeg; Block pn = new Block(pos); // starts at LC position // Function code that is supplied as the arguments to the built-in // Function, Generator, and AsyncFunction constructors is strict mode code // if the last argument is a String that when processed is a FunctionBody // that begins with a Directive Prologue that contains a Use Strict Directive. boolean inDirectivePrologue = true; boolean savedStrictMode = inUseStrictDirective; inUseStrictDirective = false; pn.setLineno(ts.lineno); try { if (isExpressionClosure) { AstNode returnValue = assignExpr(); ReturnStatement n = new ReturnStatement( returnValue.getPosition(), returnValue.getLength(), returnValue); // expression closure flag is required on both nodes n.putProp(Node.EXPRESSION_CLOSURE_PROP, Boolean.TRUE); pn.putProp(Node.EXPRESSION_CLOSURE_PROP, Boolean.TRUE); if (isArrow) { n.putProp(Node.ARROW_FUNCTION_PROP, Boolean.TRUE); } pn.addStatement(n); } else { bodyLoop: for (; ; ) { AstNode n; int tt = peekToken(); switch (tt) { case Token.ERROR: case Token.EOF: case Token.RC: break bodyLoop; case Token.COMMENT: consumeToken(); n = scannedComments.get(scannedComments.size() - 1); break; case Token.FUNCTION: consumeToken(); n = function(FunctionNode.FUNCTION_STATEMENT); break; default: n = statement(); if (inDirectivePrologue) { String directive = getDirective(n); if (directive == null) { inDirectivePrologue = false; } else if (directive.equals("use strict")) { inUseStrictDirective = true; fnNode.setInStrictMode(true); if (!savedStrictMode) { setRequiresActivation(); } } } break; } pn.addStatement(n); } } } catch (ParserException e) { // Ignore it } finally { --nestingOfFunction; inUseStrictDirective = savedStrictMode; } int end = ts.tokenEnd; getAndResetJsDoc(); if (!isExpressionClosure && mustMatchToken(Token.RC, "msg.no.brace.after.body", true)) end = ts.tokenEnd; pn.setLength(end - pos); return pn; } private static String getDirective(AstNode n) { if (n instanceof ExpressionStatement) { AstNode e = ((ExpressionStatement) n).getExpression(); if (e instanceof StringLiteral) { return ((StringLiteral) e).getValue(); } } return null; } private void parseFunctionParams(FunctionNode fnNode) throws IOException { if (matchToken(Token.RP, true)) { fnNode.setRp(ts.tokenBeg - fnNode.getPosition()); return; } // Would prefer not to call createDestructuringAssignment until codegen, // but the symbol definitions have to happen now, before body is parsed. Map<String, Node> destructuring = null; Set<String> paramNames = new HashSet<String>(); do { int tt = peekToken(); if (tt == Token.LB || tt == Token.LC) { AstNode expr = destructuringPrimaryExpr(); markDestructuring(expr); fnNode.addParam(expr); // Destructuring assignment for parameters: add a dummy // parameter name, and add a statement to the body to initialize // variables from the destructuring assignment if (destructuring == null) { destructuring = new HashMap<String, Node>(); } String pname = currentScriptOrFn.getNextTempName(); defineSymbol(Token.LP, pname, false); destructuring.put(pname, expr); } else { if (mustMatchToken(Token.NAME, "msg.no.parm", true)) { Name paramNameNode = createNameNode(); Comment jsdocNodeForName = getAndResetJsDoc(); if (jsdocNodeForName != null) { paramNameNode.setJsDocNode(jsdocNodeForName); } fnNode.addParam(paramNameNode); String paramName = ts.getString(); defineSymbol(Token.LP, paramName); if (this.inUseStrictDirective) { if ("eval".equals(paramName) || "arguments".equals(paramName)) { reportError("msg.bad.id.strict", paramName); } if (paramNames.contains(paramName)) addError("msg.dup.param.strict", paramName); paramNames.add(paramName); } } else { fnNode.addParam(makeErrorNode()); } } } while (matchToken(Token.COMMA, true)); if (destructuring != null) { Node destructuringNode = new Node(Token.COMMA); // Add assignment helper for each destructuring parameter for (Map.Entry<String, Node> param : destructuring.entrySet()) { Node assign = createDestructuringAssignment( Token.VAR, param.getValue(), createName(param.getKey())); destructuringNode.addChildToBack(assign); } fnNode.putProp(Node.DESTRUCTURING_PARAMS, destructuringNode); } if (mustMatchToken(Token.RP, "msg.no.paren.after.parms", true)) { fnNode.setRp(ts.tokenBeg - fnNode.getPosition()); } } private FunctionNode function(int type) throws IOException { return function(type, false); } private FunctionNode function(int type, boolean isGenerator) throws IOException { int syntheticType = type; int baseLineno = ts.lineno; // line number where source starts int functionSourceStart = ts.tokenBeg; // start of "function" kwd Name name = null; AstNode memberExprNode = null; if (matchToken(Token.NAME, true)) { name = createNameNode(true, Token.NAME); if (inUseStrictDirective) { String id = name.getIdentifier(); if ("eval".equals(id) || "arguments".equals(id)) { reportError("msg.bad.id.strict", id); } } if (!matchToken(Token.LP, true)) { if (compilerEnv.isAllowMemberExprAsFunctionName()) { AstNode memberExprHead = name; name = null; memberExprNode = memberExprTail(false, memberExprHead); } mustMatchToken(Token.LP, "msg.no.paren.parms", true); } } else if (matchToken(Token.LP, true)) { // Anonymous function: leave name as null } else if (matchToken(Token.MUL, true) && (compilerEnv.getLanguageVersion() >= Context.VERSION_ES6)) { // ES6 generator function return function(type, true); } else { 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", true); } int lpPos = currentToken == Token.LP ? ts.tokenBeg : -1; if (memberExprNode != null) { syntheticType = FunctionNode.FUNCTION_EXPRESSION; } if (syntheticType != FunctionNode.FUNCTION_EXPRESSION && name != null && name.length() > 0) { // Function statements define a symbol in the enclosing scope defineSymbol(Token.FUNCTION, name.getIdentifier()); } FunctionNode fnNode = new FunctionNode(functionSourceStart, name); fnNode.setFunctionType(type); if (isGenerator) { fnNode.setIsES6Generator(); } if (lpPos != -1) fnNode.setLp(lpPos - functionSourceStart); fnNode.setJsDocNode(getAndResetJsDoc()); PerFunctionVariables savedVars = new PerFunctionVariables(fnNode); try { parseFunctionParams(fnNode); fnNode.setBody(parseFunctionBody(type, fnNode)); fnNode.setEncodedSourceBounds(functionSourceStart, ts.tokenEnd); fnNode.setLength(ts.tokenEnd - functionSourceStart); if (compilerEnv.isStrictMode() && !fnNode.getBody().hasConsistentReturnUsage()) { String msg = (name != null && name.length() > 0) ? "msg.no.return.value" : "msg.anon.no.return.value"; addStrictWarning(msg, name == null ? "" : name.getIdentifier()); } } finally { savedVars.restore(); } if (memberExprNode != null) { // TODO(stevey): fix missing functionality Kit.codeBug(); fnNode.setMemberExprNode(memberExprNode); // rewrite later /* old code: 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); } } */ } fnNode.setSourceName(sourceURI); fnNode.setBaseLineno(baseLineno); fnNode.setEndLineno(ts.lineno); // Set the parent scope. Needed for finding undeclared vars. // Have to wait until after parsing the function to set its parent // scope, since defineSymbol needs the defining-scope check to stop // at the function boundary when checking for redeclarations. if (compilerEnv.isIdeMode()) { fnNode.setParentScope(currentScope); } return fnNode; } private AstNode arrowFunction(AstNode params) throws IOException { int baseLineno = ts.lineno; // line number where source starts int functionSourceStart = params != null ? params.getPosition() : -1; // start of "function" kwd FunctionNode fnNode = new FunctionNode(functionSourceStart); fnNode.setFunctionType(FunctionNode.ARROW_FUNCTION); fnNode.setJsDocNode(getAndResetJsDoc()); // Would prefer not to call createDestructuringAssignment until codegen, // but the symbol definitions have to happen now, before body is parsed. Map<String, Node> destructuring = new HashMap<String, Node>(); Set<String> paramNames = new HashSet<String>(); PerFunctionVariables savedVars = new PerFunctionVariables(fnNode); try { if (params instanceof ParenthesizedExpression) { fnNode.setParens(0, params.getLength()); AstNode p = ((ParenthesizedExpression) params).getExpression(); if (!(p instanceof EmptyExpression)) { arrowFunctionParams(fnNode, p, destructuring, paramNames); } } else { arrowFunctionParams(fnNode, params, destructuring, paramNames); } if (!destructuring.isEmpty()) { Node destructuringNode = new Node(Token.COMMA); // Add assignment helper for each destructuring parameter for (Map.Entry<String, Node> param : destructuring.entrySet()) { Node assign = createDestructuringAssignment( Token.VAR, param.getValue(), createName(param.getKey())); destructuringNode.addChildToBack(assign); } fnNode.putProp(Node.DESTRUCTURING_PARAMS, destructuringNode); } fnNode.setBody(parseFunctionBody(FunctionNode.ARROW_FUNCTION, fnNode)); fnNode.setEncodedSourceBounds(functionSourceStart, ts.tokenEnd); fnNode.setLength(ts.tokenEnd - functionSourceStart); } finally { savedVars.restore(); } if (fnNode.isGenerator()) { reportError("msg.arrowfunction.generator"); return makeErrorNode(); } fnNode.setSourceName(sourceURI); fnNode.setBaseLineno(baseLineno); fnNode.setEndLineno(ts.lineno); return fnNode; } private void arrowFunctionParams( FunctionNode fnNode, AstNode params, Map<String, Node> destructuring, Set<String> paramNames) { if (params instanceof ArrayLiteral || params instanceof ObjectLiteral) { markDestructuring(params); fnNode.addParam(params); String pname = currentScriptOrFn.getNextTempName(); defineSymbol(Token.LP, pname, false); destructuring.put(pname, params); } else if (params instanceof InfixExpression && params.getType() == Token.COMMA) { arrowFunctionParams( fnNode, ((InfixExpression) params).getLeft(), destructuring, paramNames); arrowFunctionParams( fnNode, ((InfixExpression) params).getRight(), destructuring, paramNames); } else if (params instanceof Name) { fnNode.addParam(params); String paramName = ((Name) params).getIdentifier(); defineSymbol(Token.LP, paramName); if (this.inUseStrictDirective) { if ("eval".equals(paramName) || "arguments".equals(paramName)) { reportError("msg.bad.id.strict", paramName); } if (paramNames.contains(paramName)) addError("msg.dup.param.strict", paramName); paramNames.add(paramName); } } else { reportError("msg.no.parm", params.getPosition(), params.getLength()); fnNode.addParam(makeErrorNode()); } } // This function does not match the closing RC: the caller matches // the RC so it can provide a suitable error message if not matched. // This means it's up to the caller to set the length of the node to // include the closing RC. The node start pos is set to the // absolute buffer start position, and the caller should fix it up // to be relative to the parent node. All children of this block // node are given relative start positions and correct lengths. private AstNode statements(AstNode parent) throws IOException { if (currentToken != Token.LC // assertion can be invalid in bad code && !compilerEnv.isIdeMode()) codeBug(); int pos = ts.tokenBeg; AstNode block = parent != null ? parent : new Block(pos); block.setLineno(ts.lineno); int tt; while ((tt = peekToken()) > Token.EOF && tt != Token.RC) { block.addChild(statement()); } block.setLength(ts.tokenBeg - pos); return block; } private AstNode statements() throws IOException { return statements(null); } private static class ConditionData { AstNode condition; int lp = -1; int rp = -1; } // parse and return a parenthesized expression private ConditionData condition() throws IOException { ConditionData data = new ConditionData(); if (mustMatchToken(Token.LP, "msg.no.paren.cond", true)) data.lp = ts.tokenBeg; data.condition = expr(); if (mustMatchToken(Token.RP, "msg.no.paren.after.cond", true)) data.rp = ts.tokenBeg; // Report strict warning on code like "if (a = 7) ...". Suppress the // warning if the condition is parenthesized, like "if ((a = 7)) ...". if (data.condition instanceof Assignment) { addStrictWarning( "msg.equal.as.assign", "", data.condition.getPosition(), data.condition.getLength()); } return data; } private AstNode statement() throws IOException { int pos = ts.tokenBeg; try { AstNode pn = statementHelper(); if (pn != null) { if (compilerEnv.isStrictMode() && !pn.hasSideEffects()) { int beg = pn.getPosition(); beg = Math.max(beg, lineBeginningFor(beg)); addStrictWarning( pn instanceof EmptyStatement ? "msg.extra.trailing.semi" : "msg.no.side.effects", "", beg, nodeEnd(pn) - beg); } int ntt = peekToken(); if (ntt == Token.COMMENT && pn.getLineno() == scannedComments.get(scannedComments.size() - 1).getLineno()) { pn.setInlineComment(scannedComments.get(scannedComments.size() - 1)); consumeToken(); } return pn; } } catch (ParserException e) { // an ErrorNode was added to the ErrorReporter } // error: skip ahead to a probable statement boundary guessingStatementEnd: for (; ; ) { int tt = peekTokenOrEOL(); consumeToken(); switch (tt) { case Token.ERROR: case Token.EOF: case Token.EOL: case Token.SEMI: break guessingStatementEnd; } } // We don't make error nodes explicitly part of the tree; // they get added to the ErrorReporter. May need to do // something different here. return new EmptyStatement(pos, ts.tokenBeg - pos); } private AstNode statementHelper() throws IOException { // If the statement is set, then it's been told its label by now. if (currentLabel != null && currentLabel.getStatement() != null) currentLabel = null; AstNode pn = null; int tt = peekToken(), pos = ts.tokenBeg; switch (tt) { case Token.IF: return ifStatement(); case Token.SWITCH: return switchStatement(); case Token.WHILE: return whileLoop(); case Token.DO: return doLoop(); case Token.FOR: return forLoop(); case Token.TRY: return tryStatement(); case Token.THROW: pn = throwStatement(); break; case Token.BREAK: pn = breakStatement(); break; case Token.CONTINUE: pn = continueStatement(); break; case Token.WITH: if (this.inUseStrictDirective) { reportError("msg.no.with.strict"); } return withStatement(); case Token.CONST: case Token.VAR: consumeToken(); int lineno = ts.lineno; pn = variables(currentToken, ts.tokenBeg, true); pn.setLineno(lineno); break; case Token.LET: pn = letStatement(); if (pn instanceof VariableDeclaration && peekToken() == Token.SEMI) break; return pn; case Token.RETURN: case Token.YIELD: pn = returnOrYield(tt, false); break; case Token.DEBUGGER: consumeToken(); pn = new KeywordLiteral(ts.tokenBeg, ts.tokenEnd - ts.tokenBeg, tt); pn.setLineno(ts.lineno); break; case Token.LC: return block(); case Token.ERROR: consumeToken(); return makeErrorNode(); case Token.SEMI: consumeToken(); pos = ts.tokenBeg; pn = new EmptyStatement(pos, ts.tokenEnd - pos); pn.setLineno(ts.lineno); return pn; case Token.FUNCTION: consumeToken(); return function(FunctionNode.FUNCTION_EXPRESSION_STATEMENT); case Token.DEFAULT: pn = defaultXmlNamespace(); break; case Token.NAME: pn = nameOrLabel(); if (pn instanceof ExpressionStatement) break; return pn; // LabeledStatement case Token.COMMENT: // Do not consume token here pn = scannedComments.get(scannedComments.size() - 1); return pn; default: lineno = ts.lineno; pn = new ExpressionStatement(expr(), !insideFunction()); pn.setLineno(lineno); break; } autoInsertSemicolon(pn); return pn; } private void autoInsertSemicolon(AstNode pn) throws IOException { int ttFlagged = peekFlaggedToken(); int pos = pn.getPosition(); switch (ttFlagged & CLEAR_TI_MASK) { case Token.SEMI: // Consume ';' as a part of expression consumeToken(); // extend the node bounds to include the semicolon. pn.setLength(ts.tokenEnd - pos); break; case Token.ERROR: case Token.EOF: case Token.RC: // Autoinsert ; // Token.EOF can have negative length and negative nodeEnd(pn). // So, make the end position at least pos+1. warnMissingSemi(pos, Math.max(pos + 1, nodeEnd(pn))); break; default: if ((ttFlagged & TI_AFTER_EOL) == 0) { // Report error if no EOL or autoinsert ; otherwise reportError("msg.no.semi.stmt"); } else { warnMissingSemi(pos, nodeEnd(pn)); } break; } } private IfStatement ifStatement() throws IOException { if (currentToken != Token.IF) codeBug(); consumeToken(); int pos = ts.tokenBeg, lineno = ts.lineno, elsePos = -1; IfStatement pn = new IfStatement(pos); ConditionData data = condition(); AstNode ifTrue = getNextStatementAfterInlineComments(pn), ifFalse = null; if (matchToken(Token.ELSE, true)) { int tt = peekToken(); if (tt == Token.COMMENT) { pn.setElseKeyWordInlineComment(scannedComments.get(scannedComments.size() - 1)); consumeToken(); } elsePos = ts.tokenBeg - pos; ifFalse = statement(); } int end = getNodeEnd(ifFalse != null ? ifFalse : ifTrue); pn.setLength(end - pos); pn.setCondition(data.condition); pn.setParens(data.lp - pos, data.rp - pos); pn.setThenPart(ifTrue); pn.setElsePart(ifFalse); pn.setElsePosition(elsePos); pn.setLineno(lineno); return pn; } private SwitchStatement switchStatement() throws IOException { if (currentToken != Token.SWITCH) codeBug(); consumeToken(); int pos = ts.tokenBeg; SwitchStatement pn = new SwitchStatement(pos); if (mustMatchToken(Token.LP, "msg.no.paren.switch", true)) pn.setLp(ts.tokenBeg - pos); pn.setLineno(ts.lineno); AstNode discriminant = expr(); pn.setExpression(discriminant); enterSwitch(pn); try { if (mustMatchToken(Token.RP, "msg.no.paren.after.switch", true)) pn.setRp(ts.tokenBeg - pos); mustMatchToken(Token.LC, "msg.no.brace.switch", true); boolean hasDefault = false; int tt; switchLoop: for (; ; ) { tt = nextToken(); int casePos = ts.tokenBeg; int caseLineno = ts.lineno; AstNode caseExpression = null; switch (tt) { case Token.RC: pn.setLength(ts.tokenEnd - pos); break switchLoop; case Token.CASE: caseExpression = expr(); mustMatchToken(Token.COLON, "msg.no.colon.case", true); break; case Token.DEFAULT: if (hasDefault) { reportError("msg.double.switch.default"); } hasDefault = true; mustMatchToken(Token.COLON, "msg.no.colon.case", true); break; case Token.COMMENT: AstNode n = scannedComments.get(scannedComments.size() - 1); pn.addChild(n); continue switchLoop; default: reportError("msg.bad.switch"); break switchLoop; } SwitchCase caseNode = new SwitchCase(casePos); caseNode.setExpression(caseExpression); caseNode.setLength(ts.tokenEnd - pos); // include colon caseNode.setLineno(caseLineno); while ((tt = peekToken()) != Token.RC && tt != Token.CASE && tt != Token.DEFAULT && tt != Token.EOF) { if (tt == Token.COMMENT) { Comment inlineComment = scannedComments.get(scannedComments.size() - 1); if (caseNode.getInlineComment() == null && inlineComment.getLineno() == caseNode.getLineno()) { caseNode.setInlineComment(inlineComment); } else { caseNode.addStatement(inlineComment); } consumeToken(); continue; } AstNode nextStmt = statement(); caseNode.addStatement(nextStmt); // updates length } pn.addCase(caseNode); } } finally { exitSwitch(); } return pn; } private WhileLoop whileLoop() throws IOException { if (currentToken != Token.WHILE) codeBug(); consumeToken(); int pos = ts.tokenBeg; WhileLoop pn = new WhileLoop(pos); pn.setLineno(ts.lineno); enterLoop(pn); try { ConditionData data = condition(); pn.setCondition(data.condition); pn.setParens(data.lp - pos, data.rp - pos); AstNode body = getNextStatementAfterInlineComments(pn); pn.setLength(getNodeEnd(body) - pos); restoreRelativeLoopPosition(pn); pn.setBody(body); } finally { exitLoop(); } return pn; } private DoLoop doLoop() throws IOException { if (currentToken != Token.DO) codeBug(); consumeToken(); int pos = ts.tokenBeg, end; DoLoop pn = new DoLoop(pos); pn.setLineno(ts.lineno); enterLoop(pn); try { AstNode body = getNextStatementAfterInlineComments(pn); mustMatchToken(Token.WHILE, "msg.no.while.do", true); pn.setWhilePosition(ts.tokenBeg - pos); ConditionData data = condition(); pn.setCondition(data.condition); pn.setParens(data.lp - pos, data.rp - pos); end = getNodeEnd(body); restoreRelativeLoopPosition(pn); pn.setBody(body); } finally { exitLoop(); } // Always auto-insert semicolon to follow SpiderMonkey: // It is required by ECMAScript but is ignored by the rest of // world, see bug 238945 if (matchToken(Token.SEMI, true)) { end = ts.tokenEnd; } pn.setLength(end - pos); return pn; } private int peekUntilNonComment(int tt) throws IOException { while (tt == Token.COMMENT) { consumeToken(); tt = peekToken(); } return tt; } private AstNode getNextStatementAfterInlineComments(AstNode pn) throws IOException { AstNode body = statement(); if (Token.COMMENT == body.getType()) { AstNode commentNode = body; body = statement(); if (pn != null) { pn.setInlineComment(commentNode); } else { body.setInlineComment(commentNode); } } return body; } private Loop forLoop() throws IOException { if (currentToken != Token.FOR) codeBug(); consumeToken(); int forPos = ts.tokenBeg, lineno = ts.lineno; boolean isForEach = false, isForIn = false, isForOf = false; int eachPos = -1, inPos = -1, lp = -1, rp = -1; AstNode init = null; // init is also foo in 'foo in object' AstNode cond = null; // cond is also object in 'foo in object' AstNode incr = null; Loop pn = null; Scope tempScope = new Scope(); pushScope(tempScope); // decide below what AST class to use try { // See if this is a for each () instead of just a for () if (matchToken(Token.NAME, true)) { if ("each".equals(ts.getString())) { isForEach = true; eachPos = ts.tokenBeg - forPos; } else { reportError("msg.no.paren.for"); } } if (mustMatchToken(Token.LP, "msg.no.paren.for", true)) lp = ts.tokenBeg - forPos; int tt = peekToken(); init = forLoopInit(tt); if (matchToken(Token.IN, true)) { isForIn = true; inPos = ts.tokenBeg - forPos; markDestructuring(init); cond = expr(); // object over which we're iterating } else if (compilerEnv.getLanguageVersion() >= Context.VERSION_ES6 && matchToken(Token.NAME, true) && "of".equals(ts.getString())) { isForOf = true; inPos = ts.tokenBeg - forPos; markDestructuring(init); cond = expr(); // object over which we're iterating } else { // ordinary for-loop mustMatchToken(Token.SEMI, "msg.no.semi.for", true); if (peekToken() == Token.SEMI) { // no loop condition cond = new EmptyExpression(ts.tokenBeg, 1); cond.setLineno(ts.lineno); } else { cond = expr(); } mustMatchToken(Token.SEMI, "msg.no.semi.for.cond", true); int tmpPos = ts.tokenEnd; if (peekToken() == Token.RP) { incr = new EmptyExpression(tmpPos, 1); incr.setLineno(ts.lineno); } else { incr = expr(); } } if (mustMatchToken(Token.RP, "msg.no.paren.for.ctrl", true)) rp = ts.tokenBeg - forPos; if (isForIn || isForOf) { ForInLoop fis = new ForInLoop(forPos); if (init instanceof VariableDeclaration) { // check that there was only one variable given if (((VariableDeclaration) init).getVariables().size() > 1) { reportError("msg.mult.index"); } } if (isForOf && isForEach) { reportError("msg.invalid.for.each"); } fis.setIterator(init); fis.setIteratedObject(cond); fis.setInPosition(inPos); fis.setIsForEach(isForEach); fis.setEachPosition(eachPos); fis.setIsForOf(isForOf); pn = fis; } else { ForLoop fl = new ForLoop(forPos); fl.setInitializer(init); fl.setCondition(cond); fl.setIncrement(incr); pn = fl; } // replace temp scope with the new loop object currentScope.replaceWith(pn); popScope(); // We have to parse the body -after- creating the loop node, // so that the loop node appears in the loopSet, allowing // break/continue statements to find the enclosing loop. enterLoop(pn); try { AstNode body = getNextStatementAfterInlineComments(pn); pn.setLength(getNodeEnd(body) - forPos); restoreRelativeLoopPosition(pn); pn.setBody(body); } finally { exitLoop(); } } finally { if (currentScope == tempScope) { popScope(); } } pn.setParens(lp, rp); pn.setLineno(lineno); return pn; } private AstNode forLoopInit(int tt) throws IOException { try { inForInit = true; // checked by variables() and relExpr() AstNode init = null; if (tt == Token.SEMI) { init = new EmptyExpression(ts.tokenBeg, 1); init.setLineno(ts.lineno); } else if (tt == Token.VAR || tt == Token.LET) { consumeToken(); init = variables(tt, ts.tokenBeg, false); } else { init = expr(); } return init; } finally { inForInit = false; } } private TryStatement tryStatement() throws IOException { if (currentToken != Token.TRY) codeBug(); consumeToken(); // Pull out JSDoc info and reset it before recursing. Comment jsdocNode = getAndResetJsDoc(); int tryPos = ts.tokenBeg, lineno = ts.lineno, finallyPos = -1; TryStatement pn = new TryStatement(tryPos); // Hnadled comment here because there should not be try without LC int lctt = peekToken(); while (lctt == Token.COMMENT) { Comment commentNode = scannedComments.get(scannedComments.size() - 1); pn.setInlineComment(commentNode); consumeToken(); lctt = peekToken(); } if (lctt != Token.LC) { reportError("msg.no.brace.try"); } AstNode tryBlock = getNextStatementAfterInlineComments(pn); int tryEnd = getNodeEnd(tryBlock); List<CatchClause> clauses = null; boolean sawDefaultCatch = false; int peek = peekToken(); while (peek == Token.COMMENT) { Comment commentNode = scannedComments.get(scannedComments.size() - 1); pn.setInlineComment(commentNode); consumeToken(); peek = peekToken(); } if (peek == Token.CATCH) { while (matchToken(Token.CATCH, true)) { int catchLineNum = ts.lineno; if (sawDefaultCatch) { reportError("msg.catch.unreachable"); } int catchPos = ts.tokenBeg, lp = -1, rp = -1, guardPos = -1; if (mustMatchToken(Token.LP, "msg.no.paren.catch", true)) lp = ts.tokenBeg; mustMatchToken(Token.NAME, "msg.bad.catchcond", true); Name varName = createNameNode(); Comment jsdocNodeForName = getAndResetJsDoc(); if (jsdocNodeForName != null) { varName.setJsDocNode(jsdocNodeForName); } String varNameString = varName.getIdentifier(); if (inUseStrictDirective) { if ("eval".equals(varNameString) || "arguments".equals(varNameString)) { reportError("msg.bad.id.strict", varNameString); } } AstNode catchCond = null; if (matchToken(Token.IF, true)) { guardPos = ts.tokenBeg; catchCond = expr(); } else { sawDefaultCatch = true; } if (mustMatchToken(Token.RP, "msg.bad.catchcond", true)) rp = ts.tokenBeg; mustMatchToken(Token.LC, "msg.no.brace.catchblock", true); Block catchBlock = (Block) statements(); tryEnd = getNodeEnd(catchBlock); CatchClause catchNode = new CatchClause(catchPos); catchNode.setVarName(varName); catchNode.setCatchCondition(catchCond); catchNode.setBody(catchBlock); if (guardPos != -1) { catchNode.setIfPosition(guardPos - catchPos); } catchNode.setParens(lp, rp); catchNode.setLineno(catchLineNum); if (mustMatchToken(Token.RC, "msg.no.brace.after.body", true)) tryEnd = ts.tokenEnd; catchNode.setLength(tryEnd - catchPos); if (clauses == null) clauses = new ArrayList<CatchClause>(); clauses.add(catchNode); } } else if (peek != Token.FINALLY) { mustMatchToken(Token.FINALLY, "msg.try.no.catchfinally", true); } AstNode finallyBlock = null; if (matchToken(Token.FINALLY, true)) { finallyPos = ts.tokenBeg; finallyBlock = statement(); tryEnd = getNodeEnd(finallyBlock); } pn.setLength(tryEnd - tryPos); pn.setTryBlock(tryBlock); pn.setCatchClauses(clauses); pn.setFinallyBlock(finallyBlock); if (finallyPos != -1) { pn.setFinallyPosition(finallyPos - tryPos); } pn.setLineno(lineno); if (jsdocNode != null) { pn.setJsDocNode(jsdocNode); } return pn; } private ThrowStatement throwStatement() throws IOException { if (currentToken != Token.THROW) codeBug(); consumeToken(); int pos = ts.tokenBeg, lineno = ts.lineno; if (peekTokenOrEOL() == Token.EOL) { // ECMAScript does not allow new lines before throw expression, // see bug 256617 reportError("msg.bad.throw.eol"); } AstNode expr = expr(); ThrowStatement pn = new ThrowStatement(pos, expr); pn.setLineno(lineno); return pn; } // If we match a NAME, consume the token and return the statement // with that label. If the name does not match an existing label, // reports an error. Returns the labeled statement node, or null if // the peeked token was not a name. Side effect: sets scanner token // information for the label identifier (tokenBeg, tokenEnd, etc.) private LabeledStatement matchJumpLabelName() throws IOException { LabeledStatement label = null; if (peekTokenOrEOL() == Token.NAME) { consumeToken(); if (labelSet != null) { label = labelSet.get(ts.getString()); } if (label == null) { reportError("msg.undef.label"); } } return label; } private BreakStatement breakStatement() throws IOException { if (currentToken != Token.BREAK) codeBug(); consumeToken(); int lineno = ts.lineno, pos = ts.tokenBeg, end = ts.tokenEnd; Name breakLabel = null; if (peekTokenOrEOL() == Token.NAME) { breakLabel = createNameNode(); end = getNodeEnd(breakLabel); } // matchJumpLabelName only matches if there is one LabeledStatement labels = matchJumpLabelName(); // always use first label as target Jump breakTarget = labels == null ? null : labels.getFirstLabel(); if (breakTarget == null && breakLabel == null) { if (loopAndSwitchSet == null || loopAndSwitchSet.size() == 0) { reportError("msg.bad.break", pos, end - pos); } else { breakTarget = loopAndSwitchSet.get(loopAndSwitchSet.size() - 1); } } BreakStatement pn = new BreakStatement(pos, end - pos); pn.setBreakLabel(breakLabel); // can be null if it's a bad break in error-recovery mode if (breakTarget != null) pn.setBreakTarget(breakTarget); pn.setLineno(lineno); return pn; } private ContinueStatement continueStatement() throws IOException { if (currentToken != Token.CONTINUE) codeBug(); consumeToken(); int lineno = ts.lineno, pos = ts.tokenBeg, end = ts.tokenEnd; Name label = null; if (peekTokenOrEOL() == Token.NAME) { label = createNameNode(); end = getNodeEnd(label); } // matchJumpLabelName only matches if there is one LabeledStatement labels = matchJumpLabelName(); Loop target = null; if (labels == null && label == null) { if (loopSet == null || loopSet.size() == 0) { reportError("msg.continue.outside"); } else { target = loopSet.get(loopSet.size() - 1); } } else { if (labels == null || !(labels.getStatement() instanceof Loop)) { reportError("msg.continue.nonloop", pos, end - pos); } target = labels == null ? null : (Loop) labels.getStatement(); } ContinueStatement pn = new ContinueStatement(pos, end - pos); if (target != null) // can be null in error-recovery mode pn.setTarget(target); pn.setLabel(label); pn.setLineno(lineno); return pn; } private WithStatement withStatement() throws IOException { if (currentToken != Token.WITH) codeBug(); consumeToken(); Comment withComment = getAndResetJsDoc(); int lineno = ts.lineno, pos = ts.tokenBeg, lp = -1, rp = -1; if (mustMatchToken(Token.LP, "msg.no.paren.with", true)) lp = ts.tokenBeg; AstNode obj = expr(); if (mustMatchToken(Token.RP, "msg.no.paren.after.with", true)) rp = ts.tokenBeg; WithStatement pn = new WithStatement(pos); AstNode body = getNextStatementAfterInlineComments(pn); pn.setLength(getNodeEnd(body) - pos); pn.setJsDocNode(withComment); pn.setExpression(obj); pn.setStatement(body); pn.setParens(lp, rp); pn.setLineno(lineno); return pn; } private AstNode letStatement() throws IOException { if (currentToken != Token.LET) codeBug(); consumeToken(); int lineno = ts.lineno, pos = ts.tokenBeg; AstNode pn; if (peekToken() == Token.LP) { pn = let(true, pos); } else { pn = variables(Token.LET, pos, true); // else, e.g.: let x=6, y=7; } pn.setLineno(lineno); return pn; } /** * Returns whether or not the bits in the mask have changed to all set. * * @param before bits before change * @param after bits after change * @param mask mask for bits * @return {@code true} if all the bits in the mask are set in "after" but not in "before" */ private static final boolean nowAllSet(int before, int after, int mask) { return ((before & mask) != mask) && ((after & mask) == mask); } private AstNode returnOrYield(int tt, boolean exprContext) throws IOException { if (!insideFunction()) { reportError(tt == Token.RETURN ? "msg.bad.return" : "msg.bad.yield"); } consumeToken(); int lineno = ts.lineno, pos = ts.tokenBeg, end = ts.tokenEnd; boolean yieldStar = false; if ((tt == Token.YIELD) && (compilerEnv.getLanguageVersion() >= Context.VERSION_ES6) && (peekToken() == Token.MUL)) { yieldStar = true; consumeToken(); } AstNode e = null; // This is ugly, but we don't want to require a semicolon. switch (peekTokenOrEOL()) { case Token.SEMI: case Token.RC: case Token.RB: case Token.RP: case Token.EOF: case Token.EOL: case Token.ERROR: break; case Token.YIELD: if (compilerEnv.getLanguageVersion() < Context.VERSION_ES6) { // Take extra care to preserve language compatibility break; } // fallthrough default: e = expr(); end = getNodeEnd(e); } int before = endFlags; AstNode ret; if (tt == Token.RETURN) { endFlags |= e == null ? Node.END_RETURNS : Node.END_RETURNS_VALUE; ret = new ReturnStatement(pos, end - pos, e); // see if we need a strict mode warning if (nowAllSet(before, endFlags, Node.END_RETURNS | Node.END_RETURNS_VALUE)) addStrictWarning("msg.return.inconsistent", "", pos, end - pos); } else { if (!insideFunction()) reportError("msg.bad.yield"); endFlags |= Node.END_YIELDS; ret = new Yield(pos, end - pos, e, yieldStar); setRequiresActivation(); setIsGenerator(); if (!exprContext) { ret = new ExpressionStatement(ret); } } // see if we are mixing yields and value returns. if (insideFunction() && nowAllSet(before, endFlags, Node.END_YIELDS | Node.END_RETURNS_VALUE)) { FunctionNode fn = (FunctionNode) currentScriptOrFn; if (!fn.isES6Generator()) { Name name = ((FunctionNode) currentScriptOrFn).getFunctionName(); if (name == null || name.length() == 0) { addError("msg.anon.generator.returns", ""); } else { addError("msg.generator.returns", name.getIdentifier()); } } } ret.setLineno(lineno); return ret; } private AstNode block() throws IOException { if (currentToken != Token.LC) codeBug(); consumeToken(); int pos = ts.tokenBeg; Scope block = new Scope(pos); block.setLineno(ts.lineno); pushScope(block); try { statements(block); mustMatchToken(Token.RC, "msg.no.brace.block", true); block.setLength(ts.tokenEnd - pos); return block; } finally { popScope(); } } private AstNode defaultXmlNamespace() throws IOException { if (currentToken != Token.DEFAULT) codeBug(); consumeToken(); mustHaveXML(); setRequiresActivation(); int lineno = ts.lineno, pos = ts.tokenBeg; if (!(matchToken(Token.NAME, true) && "xml".equals(ts.getString()))) { reportError("msg.bad.namespace"); } if (!(matchToken(Token.NAME, true) && "namespace".equals(ts.getString()))) { reportError("msg.bad.namespace"); } if (!matchToken(Token.ASSIGN, true)) { reportError("msg.bad.namespace"); } AstNode e = expr(); UnaryExpression dxmln = new UnaryExpression(pos, getNodeEnd(e) - pos); dxmln.setOperator(Token.DEFAULTNAMESPACE); dxmln.setOperand(e); dxmln.setLineno(lineno); ExpressionStatement es = new ExpressionStatement(dxmln, true); return es; } private void recordLabel(Label label, LabeledStatement bundle) throws IOException { // current token should be colon that primaryExpr left untouched if (peekToken() != Token.COLON) codeBug(); consumeToken(); String name = label.getName(); if (labelSet == null) { labelSet = new HashMap<String, LabeledStatement>(); } else { LabeledStatement ls = labelSet.get(name); if (ls != null) { if (compilerEnv.isIdeMode()) { Label dup = ls.getLabelByName(name); reportError("msg.dup.label", dup.getAbsolutePosition(), dup.getLength()); } reportError("msg.dup.label", label.getPosition(), label.getLength()); } } bundle.addLabel(label); labelSet.put(name, bundle); } /** * Found a name in a statement context. If it's a label, we gather up any following labels and * the next non-label statement into a {@link LabeledStatement} "bundle" and return that. * Otherwise we parse an expression and return it wrapped in an {@link ExpressionStatement}. */ private AstNode nameOrLabel() throws IOException { if (currentToken != Token.NAME) throw codeBug(); int pos = ts.tokenBeg; // set check for label and call down to primaryExpr currentFlaggedToken |= TI_CHECK_LABEL; AstNode expr = expr(); if (expr.getType() != Token.LABEL) { AstNode n = new ExpressionStatement(expr, !insideFunction()); n.lineno = expr.lineno; return n; } LabeledStatement bundle = new LabeledStatement(pos); recordLabel((Label) expr, bundle); bundle.setLineno(ts.lineno); // look for more labels AstNode stmt = null; while (peekToken() == Token.NAME) { currentFlaggedToken |= TI_CHECK_LABEL; expr = expr(); if (expr.getType() != Token.LABEL) { stmt = new ExpressionStatement(expr, !insideFunction()); autoInsertSemicolon(stmt); break; } recordLabel((Label) expr, bundle); } // no more labels; now parse the labeled statement try { currentLabel = bundle; if (stmt == null) { stmt = statementHelper(); int ntt = peekToken(); if (ntt == Token.COMMENT && stmt.getLineno() == scannedComments.get(scannedComments.size() - 1).getLineno()) { stmt.setInlineComment(scannedComments.get(scannedComments.size() - 1)); consumeToken(); } } } finally { currentLabel = null; // remove the labels for this statement from the global set for (Label lb : bundle.getLabels()) { labelSet.remove(lb.getName()); } } // If stmt has parent assigned its position already is relative // (See bug #710225) bundle.setLength(stmt.getParent() == null ? getNodeEnd(stmt) - pos : getNodeEnd(stmt)); bundle.setStatement(stmt); return bundle; } /** * Parse a 'var' or 'const' statement, or a 'var' init list in a for statement. * * @param declType A token value: either VAR, CONST, or LET depending on context. * @param pos the position where the node should start. It's sometimes the var/const/let * keyword, and other times the beginning of the first token in the first variable * declaration. * @return the parsed variable list */ private VariableDeclaration variables(int declType, int pos, boolean isStatement) throws IOException { int end; VariableDeclaration pn = new VariableDeclaration(pos); pn.setType(declType); pn.setLineno(ts.lineno); Comment varjsdocNode = getAndResetJsDoc(); if (varjsdocNode != null) { pn.setJsDocNode(varjsdocNode); } // Example: // var foo = {a: 1, b: 2}, bar = [3, 4]; // var {b: s2, a: s1} = foo, x = 6, y, [s3, s4] = bar; for (; ; ) { AstNode destructuring = null; Name name = null; int tt = peekToken(), kidPos = ts.tokenBeg; end = ts.tokenEnd; if (tt == Token.LB || tt == Token.LC) { // Destructuring assignment, e.g., var [a,b] = ... destructuring = destructuringPrimaryExpr(); end = getNodeEnd(destructuring); if (!(destructuring instanceof DestructuringForm)) reportError("msg.bad.assign.left", kidPos, end - kidPos); markDestructuring(destructuring); } else { // Simple variable name mustMatchToken(Token.NAME, "msg.bad.var", true); name = createNameNode(); name.setLineno(ts.getLineno()); if (inUseStrictDirective) { String id = ts.getString(); if ("eval".equals(id) || "arguments".equals(ts.getString())) { reportError("msg.bad.id.strict", id); } } defineSymbol(declType, ts.getString(), inForInit); } int lineno = ts.lineno; Comment jsdocNode = getAndResetJsDoc(); AstNode init = null; if (matchToken(Token.ASSIGN, true)) { init = assignExpr(); end = getNodeEnd(init); } VariableInitializer vi = new VariableInitializer(kidPos, end - kidPos); if (destructuring != null) { if (init == null && !inForInit) { reportError("msg.destruct.assign.no.init"); } vi.setTarget(destructuring); } else { vi.setTarget(name); } vi.setInitializer(init); vi.setType(declType); vi.setJsDocNode(jsdocNode); vi.setLineno(lineno); pn.addVariable(vi); if (!matchToken(Token.COMMA, true)) break; } pn.setLength(end - pos); pn.setIsStatement(isStatement); return pn; } // have to pass in 'let' kwd position to compute kid offsets properly private AstNode let(boolean isStatement, int pos) throws IOException { LetNode pn = new LetNode(pos); pn.setLineno(ts.lineno); if (mustMatchToken(Token.LP, "msg.no.paren.after.let", true)) pn.setLp(ts.tokenBeg - pos); pushScope(pn); try { VariableDeclaration vars = variables(Token.LET, ts.tokenBeg, isStatement); pn.setVariables(vars); if (mustMatchToken(Token.RP, "msg.no.paren.let", true)) { pn.setRp(ts.tokenBeg - pos); } if (isStatement && peekToken() == Token.LC) { // let statement consumeToken(); int beg = ts.tokenBeg; // position stmt at LC AstNode stmt = statements(); mustMatchToken(Token.RC, "msg.no.curly.let", true); stmt.setLength(ts.tokenEnd - beg); pn.setLength(ts.tokenEnd - pos); pn.setBody(stmt); pn.setType(Token.LET); } else { // let expression AstNode expr = expr(); pn.setLength(getNodeEnd(expr) - pos); pn.setBody(expr); if (isStatement) { // let expression in statement context ExpressionStatement es = new ExpressionStatement(pn, !insideFunction()); es.setLineno(pn.getLineno()); return es; } } } finally { popScope(); } return pn; } void defineSymbol(int declType, String name) { defineSymbol(declType, name, false); } void defineSymbol(int declType, String name, boolean ignoreNotInBlock) { if (name == null) { if (compilerEnv.isIdeMode()) { // be robust in IDE-mode return; } codeBug(); } Scope definingScope = currentScope.getDefiningScope(name); Symbol symbol = definingScope != null ? definingScope.getSymbol(name) : null; int symDeclType = symbol != null ? symbol.getDeclType() : -1; if (symbol != null && (symDeclType == Token.CONST || declType == Token.CONST || (definingScope == currentScope && symDeclType == Token.LET))) { addError( symDeclType == Token.CONST ? "msg.const.redecl" : symDeclType == Token.LET ? "msg.let.redecl" : symDeclType == Token.VAR ? "msg.var.redecl" : symDeclType == Token.FUNCTION ? "msg.fn.redecl" : "msg.parm.redecl", name); return; } switch (declType) { case Token.LET: if (!ignoreNotInBlock && ((currentScope.getType() == Token.IF) || currentScope instanceof Loop)) { addError("msg.let.decl.not.in.block"); return; } currentScope.putSymbol(new Symbol(declType, name)); return; case Token.VAR: case Token.CONST: case Token.FUNCTION: if (symbol != null) { if (symDeclType == Token.VAR) addStrictWarning("msg.var.redecl", name); else if (symDeclType == Token.LP) { addStrictWarning("msg.var.hides.arg", name); } } else { currentScriptOrFn.putSymbol(new Symbol(declType, name)); } return; case Token.LP: if (symbol != null) { // must be duplicate parameter. Second parameter hides the // first, so go ahead and add the second parameter addWarning("msg.dup.parms", name); } currentScriptOrFn.putSymbol(new Symbol(declType, name)); return; default: throw codeBug(); } } private AstNode expr() throws IOException { AstNode pn = assignExpr(); int pos = pn.getPosition(); while (matchToken(Token.COMMA, true)) { int opPos = ts.tokenBeg; if (compilerEnv.isStrictMode() && !pn.hasSideEffects()) addStrictWarning("msg.no.side.effects", "", pos, nodeEnd(pn) - pos); if (peekToken() == Token.YIELD) reportError("msg.yield.parenthesized"); pn = new InfixExpression(Token.COMMA, pn, assignExpr(), opPos); } return pn; } private AstNode assignExpr() throws IOException { int tt = peekToken(); if (tt == Token.YIELD) { return returnOrYield(tt, true); } AstNode pn = condExpr(); boolean hasEOL = false; tt = peekTokenOrEOL(); if (tt == Token.EOL) { hasEOL = true; tt = peekToken(); } if (Token.FIRST_ASSIGN <= tt && tt <= Token.LAST_ASSIGN) { if (inDestructuringAssignment) { // default values inside destructuring assignments, // like 'var [a = 10] = b' or 'var {a: b = 10} = c', // are not supported reportError("msg.destruct.default.vals"); } consumeToken(); // Pull out JSDoc info and reset it before recursing. Comment jsdocNode = getAndResetJsDoc(); markDestructuring(pn); int opPos = ts.tokenBeg; pn = new Assignment(tt, pn, assignExpr(), opPos); if (jsdocNode != null) { pn.setJsDocNode(jsdocNode); } } else if (tt == Token.SEMI) { // This may be dead code added intentionally, for JSDoc purposes. // For example: /** @type Number */ C.prototype.x; if (currentJsDocComment != null) { pn.setJsDocNode(getAndResetJsDoc()); } } else if (!hasEOL && tt == Token.ARROW) { consumeToken(); pn = arrowFunction(pn); } return pn; } private AstNode condExpr() throws IOException { AstNode pn = orExpr(); if (matchToken(Token.HOOK, true)) { int line = ts.lineno; int qmarkPos = ts.tokenBeg, colonPos = -1; /* * Always accept the 'in' operator in the middle clause of a ternary, * where it's unambiguous, even if we might be parsing the init of a * for statement. */ boolean wasInForInit = inForInit; inForInit = false; AstNode ifTrue; try { ifTrue = assignExpr(); } finally { inForInit = wasInForInit; } if (mustMatchToken(Token.COLON, "msg.no.colon.cond", true)) colonPos = ts.tokenBeg; AstNode ifFalse = assignExpr(); int beg = pn.getPosition(), len = getNodeEnd(ifFalse) - beg; ConditionalExpression ce = new ConditionalExpression(beg, len); ce.setLineno(line); ce.setTestExpression(pn); ce.setTrueExpression(ifTrue); ce.setFalseExpression(ifFalse); ce.setQuestionMarkPosition(qmarkPos - beg); ce.setColonPosition(colonPos - beg); pn = ce; } return pn; } private AstNode orExpr() throws IOException { AstNode pn = andExpr(); if (matchToken(Token.OR, true)) { int opPos = ts.tokenBeg; pn = new InfixExpression(Token.OR, pn, orExpr(), opPos); } return pn; } private AstNode andExpr() throws IOException { AstNode pn = bitOrExpr(); if (matchToken(Token.AND, true)) { int opPos = ts.tokenBeg; pn = new InfixExpression(Token.AND, pn, andExpr(), opPos); } return pn; } private AstNode bitOrExpr() throws IOException { AstNode pn = bitXorExpr(); while (matchToken(Token.BITOR, true)) { int opPos = ts.tokenBeg; pn = new InfixExpression(Token.BITOR, pn, bitXorExpr(), opPos); } return pn; } private AstNode bitXorExpr() throws IOException { AstNode pn = bitAndExpr(); while (matchToken(Token.BITXOR, true)) { int opPos = ts.tokenBeg; pn = new InfixExpression(Token.BITXOR, pn, bitAndExpr(), opPos); } return pn; } private AstNode bitAndExpr() throws IOException { AstNode pn = eqExpr(); while (matchToken(Token.BITAND, true)) { int opPos = ts.tokenBeg; pn = new InfixExpression(Token.BITAND, pn, eqExpr(), opPos); } return pn; } private AstNode eqExpr() throws IOException { AstNode pn = relExpr(); for (; ; ) { int tt = peekToken(), opPos = ts.tokenBeg; switch (tt) { case Token.EQ: case Token.NE: case Token.SHEQ: case Token.SHNE: consumeToken(); int parseToken = tt; if (compilerEnv.getLanguageVersion() == Context.VERSION_1_2) { // JavaScript 1.2 uses shallow equality for == and != . if (tt == Token.EQ) parseToken = Token.SHEQ; else if (tt == Token.NE) parseToken = Token.SHNE; } pn = new InfixExpression(parseToken, pn, relExpr(), opPos); continue; } break; } return pn; } private AstNode relExpr() throws IOException { AstNode pn = shiftExpr(); for (; ; ) { int tt = peekToken(), opPos = ts.tokenBeg; 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(); pn = new InfixExpression(tt, pn, shiftExpr(), opPos); continue; } break; } return pn; } private AstNode shiftExpr() throws IOException { AstNode pn = addExpr(); for (; ; ) { int tt = peekToken(), opPos = ts.tokenBeg; switch (tt) { case Token.LSH: case Token.URSH: case Token.RSH: consumeToken(); pn = new InfixExpression(tt, pn, addExpr(), opPos); continue; } break; } return pn; } private AstNode addExpr() throws IOException { AstNode pn = mulExpr(); for (; ; ) { int tt = peekToken(), opPos = ts.tokenBeg; if (tt == Token.ADD || tt == Token.SUB) { consumeToken(); pn = new InfixExpression(tt, pn, mulExpr(), opPos); continue; } break; } return pn; } private AstNode mulExpr() throws IOException { AstNode pn = expExpr(); for (; ; ) { int tt = peekToken(), opPos = ts.tokenBeg; switch (tt) { case Token.MUL: case Token.DIV: case Token.MOD: consumeToken(); pn = new InfixExpression(tt, pn, expExpr(), opPos); continue; } break; } return pn; } private AstNode expExpr() throws IOException { AstNode pn = unaryExpr(); for (; ; ) { int tt = peekToken(), opPos = ts.tokenBeg; switch (tt) { case Token.EXP: if (pn instanceof UnaryExpression) { reportError( "msg.no.unary.expr.on.left.exp", AstNode.operatorToString(pn.getType())); return makeErrorNode(); } consumeToken(); pn = new InfixExpression(tt, pn, expExpr(), opPos); continue; } break; } return pn; } private AstNode unaryExpr() throws IOException { AstNode node; int tt = peekToken(); if (tt == Token.COMMENT) { consumeToken(); tt = peekUntilNonComment(tt); } int line = ts.lineno; switch (tt) { case Token.VOID: case Token.NOT: case Token.BITNOT: case Token.TYPEOF: consumeToken(); node = new UnaryExpression(tt, ts.tokenBeg, unaryExpr()); node.setLineno(line); return node; case Token.ADD: consumeToken(); // Convert to special POS token in parse tree node = new UnaryExpression(Token.POS, ts.tokenBeg, unaryExpr()); node.setLineno(line); return node; case Token.SUB: consumeToken(); // Convert to special NEG token in parse tree node = new UnaryExpression(Token.NEG, ts.tokenBeg, unaryExpr()); node.setLineno(line); return node; case Token.INC: case Token.DEC: consumeToken(); UpdateExpression expr = new UpdateExpression(tt, ts.tokenBeg, memberExpr(true)); expr.setLineno(line); checkBadIncDec(expr); return expr; case Token.DELPROP: consumeToken(); node = new UnaryExpression(tt, ts.tokenBeg, unaryExpr()); node.setLineno(line); return node; case Token.ERROR: consumeToken(); return makeErrorNode(); case Token.LT: // XML stream encountered in expression. if (compilerEnv.isXmlAvailable()) { consumeToken(); return memberExprTail(true, xmlInitializer()); } // Fall thru to the default handling of RELOP // fall through default: AstNode pn = memberExpr(true); // Don't look across a newline boundary for a postfix incop. tt = peekTokenOrEOL(); if (!(tt == Token.INC || tt == Token.DEC)) { return pn; } consumeToken(); UpdateExpression uexpr = new UpdateExpression(tt, ts.tokenBeg, pn, true); uexpr.setLineno(line); checkBadIncDec(uexpr); return uexpr; } } private AstNode xmlInitializer() throws IOException { if (currentToken != Token.LT) codeBug(); int pos = ts.tokenBeg, tt = ts.getFirstXMLToken(); if (tt != Token.XML && tt != Token.XMLEND) { reportError("msg.syntax"); return makeErrorNode(); } XmlLiteral pn = new XmlLiteral(pos); pn.setLineno(ts.lineno); for (; ; tt = ts.getNextXMLToken()) { switch (tt) { case Token.XML: pn.addFragment(new XmlString(ts.tokenBeg, ts.getString())); mustMatchToken(Token.LC, "msg.syntax", true); int beg = ts.tokenBeg; AstNode expr = (peekToken() == Token.RC) ? new EmptyExpression(beg, ts.tokenEnd - beg) : expr(); mustMatchToken(Token.RC, "msg.syntax", true); XmlExpression xexpr = new XmlExpression(beg, expr); xexpr.setIsXmlAttribute(ts.isXMLAttribute()); xexpr.setLength(ts.tokenEnd - beg); pn.addFragment(xexpr); break; case Token.XMLEND: pn.addFragment(new XmlString(ts.tokenBeg, ts.getString())); return pn; default: reportError("msg.syntax"); return makeErrorNode(); } } } private List<AstNode> argumentList() throws IOException { if (matchToken(Token.RP, true)) return null; List<AstNode> result = new ArrayList<AstNode>(); boolean wasInForInit = inForInit; inForInit = false; try { do { if (peekToken() == Token.RP) { // Quick fix to handle scenario like f1(a,); but not f1(a,b break; } if (peekToken() == Token.YIELD) { reportError("msg.yield.parenthesized"); } AstNode en = assignExpr(); if (peekToken() == Token.FOR) { try { result.add(generatorExpression(en, 0, true)); } catch (IOException ex) { // #TODO } } else { result.add(en); } } while (matchToken(Token.COMMA, true)); } finally { inForInit = wasInForInit; } mustMatchToken(Token.RP, "msg.no.paren.arg", true); return result; } /** * Parse a new-expression, or if next token isn't {@link Token#NEW}, a primary expression. * * @param allowCallSyntax passed down to {@link #memberExprTail} */ private AstNode memberExpr(boolean allowCallSyntax) throws IOException { int tt = peekToken(), lineno = ts.lineno; AstNode pn; if (tt != Token.NEW) { pn = primaryExpr(); } else { consumeToken(); int pos = ts.tokenBeg; NewExpression nx = new NewExpression(pos); AstNode target = memberExpr(false); int end = getNodeEnd(target); nx.setTarget(target); int lp = -1; if (matchToken(Token.LP, true)) { lp = ts.tokenBeg; List<AstNode> args = argumentList(); if (args != null && args.size() > ARGC_LIMIT) reportError("msg.too.many.constructor.args"); int rp = ts.tokenBeg; end = ts.tokenEnd; if (args != null) nx.setArguments(args); nx.setParens(lp - pos, rp - pos); } // 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. if (matchToken(Token.LC, true)) { ObjectLiteral initializer = objectLiteral(); end = getNodeEnd(initializer); nx.setInitializer(initializer); } nx.setLength(end - pos); pn = nx; } pn.setLineno(lineno); AstNode tail = memberExprTail(allowCallSyntax, pn); return tail; } /** * Parse any number of "(expr)", "[expr]" ".expr", "..expr", or ".(expr)" constructs trailing * the passed expression. * * @param pn the non-null parent node * @return the outermost (lexically last occurring) expression, which will have the passed * parent node as a descendant */ private AstNode memberExprTail(boolean allowCallSyntax, AstNode pn) throws IOException { // we no longer return null for errors, so this won't be null if (pn == null) codeBug(); int pos = pn.getPosition(); int lineno; tailLoop: for (; ; ) { int tt = peekToken(); switch (tt) { case Token.DOT: case Token.DOTDOT: lineno = ts.lineno; pn = propertyAccess(tt, pn); pn.setLineno(lineno); break; case Token.DOTQUERY: consumeToken(); int opPos = ts.tokenBeg, rp = -1; lineno = ts.lineno; mustHaveXML(); setRequiresActivation(); AstNode filter = expr(); int end = getNodeEnd(filter); if (mustMatchToken(Token.RP, "msg.no.paren", true)) { rp = ts.tokenBeg; end = ts.tokenEnd; } XmlDotQuery q = new XmlDotQuery(pos, end - pos); q.setLeft(pn); q.setRight(filter); q.setOperatorPosition(opPos); q.setRp(rp - pos); q.setLineno(lineno); pn = q; break; case Token.LB: consumeToken(); int lb = ts.tokenBeg, rb = -1; lineno = ts.lineno; AstNode expr = expr(); end = getNodeEnd(expr); if (mustMatchToken(Token.RB, "msg.no.bracket.index", true)) { rb = ts.tokenBeg; end = ts.tokenEnd; } ElementGet g = new ElementGet(pos, end - pos); g.setTarget(pn); g.setElement(expr); g.setParens(lb, rb); g.setLineno(lineno); pn = g; break; case Token.LP: if (!allowCallSyntax) { break tailLoop; } lineno = ts.lineno; consumeToken(); checkCallRequiresActivation(pn); FunctionCall f = new FunctionCall(pos); f.setTarget(pn); // Assign the line number for the function call to where // the paren appeared, not where the name expression started. f.setLineno(lineno); f.setLp(ts.tokenBeg - pos); List<AstNode> args = argumentList(); if (args != null && args.size() > ARGC_LIMIT) reportError("msg.too.many.function.args"); f.setArguments(args); f.setRp(ts.tokenBeg - pos); f.setLength(ts.tokenEnd - pos); pn = f; break; case Token.COMMENT: // Ignoring all the comments, because previous statement may not be terminated // properly. int currentFlagTOken = currentFlaggedToken; peekUntilNonComment(tt); currentFlaggedToken = (currentFlaggedToken & TI_AFTER_EOL) != 0 ? currentFlaggedToken : currentFlagTOken; break; case Token.TEMPLATE_LITERAL: consumeToken(); pn = taggedTemplateLiteral(pn); break; default: break tailLoop; } } return pn; } private AstNode taggedTemplateLiteral(AstNode pn) throws IOException { AstNode templateLiteral = templateLiteral(true); TaggedTemplateLiteral tagged = new TaggedTemplateLiteral(); tagged.setTarget(pn); tagged.setTemplateLiteral(templateLiteral); return tagged; } /** * Handles any construct following a "." or ".." operator. * * @param pn the left-hand side (target) of the operator. Never null. * @return a PropertyGet, XmlMemberGet, or ErrorNode */ private AstNode propertyAccess(int tt, AstNode pn) throws IOException { if (pn == null) codeBug(); int memberTypeFlags = 0, lineno = ts.lineno, dotPos = ts.tokenBeg; consumeToken(); if (tt == Token.DOTDOT) { mustHaveXML(); memberTypeFlags = Node.DESCENDANTS_FLAG; } if (!compilerEnv.isXmlAvailable()) { int maybeName = nextToken(); if (maybeName != Token.NAME && !(compilerEnv.isReservedKeywordAsIdentifier() && TokenStream.isKeyword( ts.getString(), compilerEnv.getLanguageVersion(), inUseStrictDirective))) { reportError("msg.no.name.after.dot"); } Name name = createNameNode(true, Token.GETPROP); PropertyGet pg = new PropertyGet(pn, name, dotPos); pg.setLineno(lineno); return pg; } AstNode ref = null; // right side of . or .. operator int token = nextToken(); switch (token) { case Token.THROW: // needed for generator.throw(); saveNameTokenData(ts.tokenBeg, "throw", ts.lineno); ref = propertyName(-1, memberTypeFlags); break; case Token.NAME: // handles: name, ns::name, ns::*, ns::[expr] ref = propertyName(-1, memberTypeFlags); break; case Token.MUL: // handles: *, *::name, *::*, *::[expr] saveNameTokenData(ts.tokenBeg, "*", ts.lineno); ref = propertyName(-1, memberTypeFlags); break; case Token.XMLATTR: // handles: '@attr', '@ns::attr', '@ns::*', '@ns::*', // '@::attr', '@::*', '@*', '@*::attr', '@*::*' ref = attributeAccess(); break; case Token.RESERVED: { String name = ts.getString(); saveNameTokenData(ts.tokenBeg, name, ts.lineno); ref = propertyName(-1, memberTypeFlags); break; } default: if (compilerEnv.isReservedKeywordAsIdentifier()) { // allow keywords as property names, e.g. ({if: 1}) String name = Token.keywordToName(token); if (name != null) { saveNameTokenData(ts.tokenBeg, name, ts.lineno); ref = propertyName(-1, memberTypeFlags); break; } } reportError("msg.no.name.after.dot"); return makeErrorNode(); } boolean xml = ref instanceof XmlRef; InfixExpression result = xml ? new XmlMemberGet() : new PropertyGet(); if (xml && tt == Token.DOT) result.setType(Token.DOT); int pos = pn.getPosition(); result.setPosition(pos); result.setLength(getNodeEnd(ref) - pos); result.setOperatorPosition(dotPos - pos); result.setLineno(pn.getLineno()); result.setLeft(pn); // do this after setting position result.setRight(ref); return result; } /** * Xml attribute expression: * * <p>{@code @attr}, {@code @ns::attr}, {@code @ns::*}, {@code @ns::*}, {@code @*}, * {@code @*::attr}, {@code @*::*}, {@code @ns::[expr]}, {@code @*::[expr]}, {@code @[expr]} * * <p>Called if we peeked an '@' token. */ private AstNode attributeAccess() throws IOException { int tt = nextToken(), atPos = ts.tokenBeg; switch (tt) { // handles: @name, @ns::name, @ns::*, @ns::[expr] case Token.NAME: return propertyName(atPos, 0); // handles: @*, @*::name, @*::*, @*::[expr] case Token.MUL: saveNameTokenData(ts.tokenBeg, "*", ts.lineno); return propertyName(atPos, 0); // handles @[expr] case Token.LB: return xmlElemRef(atPos, null, -1); default: reportError("msg.no.name.after.xmlAttr"); return makeErrorNode(); } } /** * Check if :: follows name in which case it becomes a qualified name. * * @param atPos a natural number if we just read an '@' token, else -1 * @param s the name or string that was matched (an identifier, "throw" or "*"). * @param memberTypeFlags flags tracking whether we're a '.' or '..' child * @return an XmlRef node if it's an attribute access, a child of a '..' operator, or the name * is followed by ::. For a plain name, returns a Name node. Returns an ErrorNode for * malformed XML expressions. (For now - might change to return a partial XmlRef.) */ private AstNode propertyName(int atPos, int memberTypeFlags) throws IOException { int pos = atPos != -1 ? atPos : ts.tokenBeg, lineno = ts.lineno; int colonPos = -1; Name name = createNameNode(true, currentToken); Name ns = null; if (matchToken(Token.COLONCOLON, true)) { ns = name; colonPos = ts.tokenBeg; switch (nextToken()) { // handles name::name case Token.NAME: name = createNameNode(); break; // handles name::* case Token.MUL: saveNameTokenData(ts.tokenBeg, "*", ts.lineno); name = createNameNode(false, -1); break; // handles name::[expr] or *::[expr] case Token.LB: return xmlElemRef(atPos, ns, colonPos); default: reportError("msg.no.name.after.coloncolon"); return makeErrorNode(); } } if (ns == null && memberTypeFlags == 0 && atPos == -1) { return name; } XmlPropRef ref = new XmlPropRef(pos, getNodeEnd(name) - pos); ref.setAtPos(atPos); ref.setNamespace(ns); ref.setColonPos(colonPos); ref.setPropName(name); ref.setLineno(lineno); return ref; } /** * Parse the [expr] portion of an xml element reference, e.g. @[expr], @*::[expr], or * ns::[expr]. */ private XmlElemRef xmlElemRef(int atPos, Name namespace, int colonPos) throws IOException { int lb = ts.tokenBeg, rb = -1, pos = atPos != -1 ? atPos : lb; AstNode expr = expr(); int end = getNodeEnd(expr); if (mustMatchToken(Token.RB, "msg.no.bracket.index", true)) { rb = ts.tokenBeg; end = ts.tokenEnd; } XmlElemRef ref = new XmlElemRef(pos, end - pos); ref.setNamespace(namespace); ref.setColonPos(colonPos); ref.setAtPos(atPos); ref.setExpression(expr); ref.setBrackets(lb, rb); return ref; } private AstNode destructuringPrimaryExpr() throws IOException, ParserException { try { inDestructuringAssignment = true; return primaryExpr(); } finally { inDestructuringAssignment = false; } } private AstNode primaryExpr() throws IOException { int ttFlagged = peekFlaggedToken(); int tt = ttFlagged & CLEAR_TI_MASK; switch (tt) { case Token.FUNCTION: consumeToken(); return function(FunctionNode.FUNCTION_EXPRESSION); case Token.LB: consumeToken(); return arrayLiteral(); case Token.LC: consumeToken(); return objectLiteral(); case Token.LET: consumeToken(); return let(false, ts.tokenBeg); case Token.LP: consumeToken(); return parenExpr(); case Token.XMLATTR: consumeToken(); mustHaveXML(); return attributeAccess(); case Token.NAME: consumeToken(); return name(ttFlagged, tt); case Token.NUMBER: case Token.BIGINT: { consumeToken(); return createNumericLiteral(tt, false); } case Token.STRING: consumeToken(); return createStringLiteral(); case Token.DIV: case Token.ASSIGN_DIV: consumeToken(); // Got / or /= which in this context means a regexp ts.readRegExp(tt); int pos = ts.tokenBeg, end = ts.tokenEnd; RegExpLiteral re = new RegExpLiteral(pos, end - pos); re.setValue(ts.getString()); re.setFlags(ts.readAndClearRegExpFlags()); return re; case Token.NULL: case Token.THIS: case Token.FALSE: case Token.TRUE: consumeToken(); pos = ts.tokenBeg; end = ts.tokenEnd; return new KeywordLiteral(pos, end - pos, tt); case Token.TEMPLATE_LITERAL: consumeToken(); return templateLiteral(false); case Token.RESERVED: consumeToken(); reportError("msg.reserved.id", ts.getString()); break; case Token.ERROR: consumeToken(); // the scanner or one of its subroutines reported the error. break; case Token.EOF: consumeToken(); reportError("msg.unexpected.eof"); break; default: consumeToken(); reportError("msg.syntax"); break; } // should only be reachable in IDE/error-recovery mode consumeToken(); return makeErrorNode(); } private AstNode parenExpr() throws IOException { boolean wasInForInit = inForInit; inForInit = false; try { Comment jsdocNode = getAndResetJsDoc(); int lineno = ts.lineno; int begin = ts.tokenBeg; AstNode e = (peekToken() == Token.RP ? new EmptyExpression(begin) : expr()); if (peekToken() == Token.FOR) { return generatorExpression(e, begin); } mustMatchToken(Token.RP, "msg.no.paren", true); if (e.getType() == Token.EMPTY && peekToken() != Token.ARROW) { reportError("msg.syntax"); return makeErrorNode(); } int length = ts.tokenEnd - begin; ParenthesizedExpression pn = new ParenthesizedExpression(begin, length, e); pn.setLineno(lineno); if (jsdocNode == null) { jsdocNode = getAndResetJsDoc(); } if (jsdocNode != null) { pn.setJsDocNode(jsdocNode); } return pn; } finally { inForInit = wasInForInit; } } private AstNode name(int ttFlagged, int tt) throws IOException { String nameString = ts.getString(); int namePos = ts.tokenBeg, nameLineno = ts.lineno; if (0 != (ttFlagged & TI_CHECK_LABEL) && peekToken() == Token.COLON) { // Do not consume colon. It is used as an unwind indicator // to return to statementHelper. Label label = new Label(namePos, ts.tokenEnd - namePos); label.setName(nameString); label.setLineno(ts.lineno); return label; } // Not a label. Unfortunately peeking the next token to check for // a colon has biffed ts.tokenBeg, ts.tokenEnd. We store the name's // bounds in instance vars and createNameNode uses them. saveNameTokenData(namePos, nameString, nameLineno); if (compilerEnv.isXmlAvailable()) { return propertyName(-1, 0); } return createNameNode(true, Token.NAME); } /** May return an {@link ArrayLiteral} or {@link ArrayComprehension}. */ private AstNode arrayLiteral() throws IOException { if (currentToken != Token.LB) codeBug(); int pos = ts.tokenBeg, end = ts.tokenEnd; List<AstNode> elements = new ArrayList<AstNode>(); ArrayLiteral pn = new ArrayLiteral(pos); boolean after_lb_or_comma = true; int afterComma = -1; int skipCount = 0; for (; ; ) { int tt = peekToken(); if (tt == Token.COMMA) { consumeToken(); afterComma = ts.tokenEnd; if (!after_lb_or_comma) { after_lb_or_comma = true; } else { elements.add(new EmptyExpression(ts.tokenBeg, 1)); skipCount++; } } else if (tt == Token.COMMENT) { consumeToken(); } else if (tt == Token.RB) { consumeToken(); // for ([a,] in obj) is legal, but for ([a] in obj) is // not since we have both key and value supplied. The // trick is that [a,] and [a] are equivalent in other // array literal contexts. So we calculate a special // length value just for destructuring assignment. end = ts.tokenEnd; pn.setDestructuringLength(elements.size() + (after_lb_or_comma ? 1 : 0)); pn.setSkipCount(skipCount); if (afterComma != -1) warnTrailingComma(pos, elements, afterComma); break; } else if (tt == Token.FOR && !after_lb_or_comma && elements.size() == 1) { return arrayComprehension(elements.get(0), pos); } else if (tt == Token.EOF) { reportError("msg.no.bracket.arg"); break; } else { if (!after_lb_or_comma) { reportError("msg.no.bracket.arg"); } elements.add(assignExpr()); after_lb_or_comma = false; afterComma = -1; } } for (AstNode e : elements) { pn.addElement(e); } pn.setLength(end - pos); return pn; } /** * Parse a JavaScript 1.7 Array comprehension. * * @param result the first expression after the opening left-bracket * @param pos start of LB token that begins the array comprehension * @return the array comprehension or an error node */ private AstNode arrayComprehension(AstNode result, int pos) throws IOException { List<ArrayComprehensionLoop> loops = new ArrayList<ArrayComprehensionLoop>(); while (peekToken() == Token.FOR) { loops.add(arrayComprehensionLoop()); } int ifPos = -1; ConditionData data = null; if (peekToken() == Token.IF) { consumeToken(); ifPos = ts.tokenBeg - pos; data = condition(); } mustMatchToken(Token.RB, "msg.no.bracket.arg", true); ArrayComprehension pn = new ArrayComprehension(pos, ts.tokenEnd - pos); pn.setResult(result); pn.setLoops(loops); if (data != null) { pn.setIfPosition(ifPos); pn.setFilter(data.condition); pn.setFilterLp(data.lp - pos); pn.setFilterRp(data.rp - pos); } return pn; } private ArrayComprehensionLoop arrayComprehensionLoop() throws IOException { if (nextToken() != Token.FOR) codeBug(); int pos = ts.tokenBeg; int eachPos = -1, lp = -1, rp = -1, inPos = -1; boolean isForOf = false; ArrayComprehensionLoop pn = new ArrayComprehensionLoop(pos); pushScope(pn); try { if (matchToken(Token.NAME, true)) { if (ts.getString().equals("each")) { eachPos = ts.tokenBeg - pos; } else { reportError("msg.no.paren.for"); } } if (mustMatchToken(Token.LP, "msg.no.paren.for", true)) { lp = ts.tokenBeg - pos; } AstNode iter = null; switch (peekToken()) { case Token.LB: case Token.LC: // handle destructuring assignment iter = destructuringPrimaryExpr(); markDestructuring(iter); break; case Token.NAME: consumeToken(); iter = createNameNode(); break; default: reportError("msg.bad.var"); } // Define as a let since we want the scope of the variable to // be restricted to the array comprehension if (iter.getType() == Token.NAME) { defineSymbol(Token.LET, ts.getString(), true); } switch (nextToken()) { case Token.IN: inPos = ts.tokenBeg - pos; break; case Token.NAME: if ("of".equals(ts.getString())) { if (eachPos != -1) { reportError("msg.invalid.for.each"); } inPos = ts.tokenBeg - pos; isForOf = true; break; } // fall through default: reportError("msg.in.after.for.name"); } AstNode obj = expr(); if (mustMatchToken(Token.RP, "msg.no.paren.for.ctrl", true)) rp = ts.tokenBeg - pos; pn.setLength(ts.tokenEnd - pos); pn.setIterator(iter); pn.setIteratedObject(obj); pn.setInPosition(inPos); pn.setEachPosition(eachPos); pn.setIsForEach(eachPos != -1); pn.setParens(lp, rp); pn.setIsForOf(isForOf); return pn; } finally { popScope(); } } private AstNode generatorExpression(AstNode result, int pos) throws IOException { return generatorExpression(result, pos, false); } private AstNode generatorExpression(AstNode result, int pos, boolean inFunctionParams) throws IOException { List<GeneratorExpressionLoop> loops = new ArrayList<GeneratorExpressionLoop>(); while (peekToken() == Token.FOR) { loops.add(generatorExpressionLoop()); } int ifPos = -1; ConditionData data = null; if (peekToken() == Token.IF) { consumeToken(); ifPos = ts.tokenBeg - pos; data = condition(); } if (!inFunctionParams) { mustMatchToken(Token.RP, "msg.no.paren.let", true); } GeneratorExpression pn = new GeneratorExpression(pos, ts.tokenEnd - pos); pn.setResult(result); pn.setLoops(loops); if (data != null) { pn.setIfPosition(ifPos); pn.setFilter(data.condition); pn.setFilterLp(data.lp - pos); pn.setFilterRp(data.rp - pos); } return pn; } private GeneratorExpressionLoop generatorExpressionLoop() throws IOException { if (nextToken() != Token.FOR) codeBug(); int pos = ts.tokenBeg; int lp = -1, rp = -1, inPos = -1; GeneratorExpressionLoop pn = new GeneratorExpressionLoop(pos); pushScope(pn); try { if (mustMatchToken(Token.LP, "msg.no.paren.for", true)) { lp = ts.tokenBeg - pos; } AstNode iter = null; switch (peekToken()) { case Token.LB: case Token.LC: // handle destructuring assignment iter = destructuringPrimaryExpr(); markDestructuring(iter); break; case Token.NAME: consumeToken(); iter = createNameNode(); break; default: reportError("msg.bad.var"); } // Define as a let since we want the scope of the variable to // be restricted to the array comprehension if (iter.getType() == Token.NAME) { defineSymbol(Token.LET, ts.getString(), true); } if (mustMatchToken(Token.IN, "msg.in.after.for.name", true)) inPos = ts.tokenBeg - pos; AstNode obj = expr(); if (mustMatchToken(Token.RP, "msg.no.paren.for.ctrl", true)) rp = ts.tokenBeg - pos; pn.setLength(ts.tokenEnd - pos); pn.setIterator(iter); pn.setIteratedObject(obj); pn.setInPosition(inPos); pn.setParens(lp, rp); return pn; } finally { popScope(); } } private static final int PROP_ENTRY = 1; private static final int GET_ENTRY = 2; private static final int SET_ENTRY = 4; private static final int METHOD_ENTRY = 8; private ObjectLiteral objectLiteral() throws IOException { int pos = ts.tokenBeg, lineno = ts.lineno; int afterComma = -1; List<ObjectProperty> elems = new ArrayList<ObjectProperty>(); Set<String> getterNames = null; Set<String> setterNames = null; if (this.inUseStrictDirective) { getterNames = new HashSet<String>(); setterNames = new HashSet<String>(); } Comment objJsdocNode = getAndResetJsDoc(); commaLoop: for (; ; ) { String propertyName = null; int entryKind = PROP_ENTRY; int tt = peekToken(); Comment jsdocNode = getAndResetJsDoc(); if (tt == Token.COMMENT) { consumeToken(); tt = peekUntilNonComment(tt); } if (tt == Token.RC) { if (afterComma != -1) warnTrailingComma(pos, elems, afterComma); break commaLoop; } AstNode pname = objliteralProperty(); if (pname == null) { reportError("msg.bad.prop"); } else { propertyName = ts.getString(); int ppos = ts.tokenBeg; consumeToken(); // This code path needs to handle both destructuring object // literals like: // var {get, b} = {get: 1, b: 2}; // and getters like: // var x = {get 1() { return 2; }; // So we check a whitelist of tokens to check if we're at the // first case. (Because of keywords, the second case may be // many tokens.) int peeked = peekToken(); if (peeked != Token.COMMA && peeked != Token.COLON && peeked != Token.RC) { if (peeked == Token.LP) { entryKind = METHOD_ENTRY; } else if (pname.getType() == Token.NAME) { if ("get".equals(propertyName)) { entryKind = GET_ENTRY; } else if ("set".equals(propertyName)) { entryKind = SET_ENTRY; } } if (entryKind == GET_ENTRY || entryKind == SET_ENTRY) { pname = objliteralProperty(); if (pname == null) { reportError("msg.bad.prop"); } consumeToken(); } if (pname == null) { propertyName = null; } else { propertyName = ts.getString(); ObjectProperty objectProp = methodDefinition(ppos, pname, entryKind); pname.setJsDocNode(jsdocNode); elems.add(objectProp); } } else { pname.setJsDocNode(jsdocNode); elems.add(plainProperty(pname, tt)); } } if (this.inUseStrictDirective && propertyName != null) { switch (entryKind) { case PROP_ENTRY: case METHOD_ENTRY: if (getterNames.contains(propertyName) || setterNames.contains(propertyName)) { addError("msg.dup.obj.lit.prop.strict", propertyName); } getterNames.add(propertyName); setterNames.add(propertyName); break; case GET_ENTRY: if (getterNames.contains(propertyName)) { addError("msg.dup.obj.lit.prop.strict", propertyName); } getterNames.add(propertyName); break; case SET_ENTRY: if (setterNames.contains(propertyName)) { addError("msg.dup.obj.lit.prop.strict", propertyName); } setterNames.add(propertyName); break; } } // Eat any dangling jsdoc in the property. getAndResetJsDoc(); if (matchToken(Token.COMMA, true)) { afterComma = ts.tokenEnd; } else { break commaLoop; } } mustMatchToken(Token.RC, "msg.no.brace.prop", true); ObjectLiteral pn = new ObjectLiteral(pos, ts.tokenEnd - pos); if (objJsdocNode != null) { pn.setJsDocNode(objJsdocNode); } pn.setElements(elems); pn.setLineno(lineno); return pn; } private AstNode objliteralProperty() throws IOException { AstNode pname; int tt = peekToken(); switch (tt) { case Token.NAME: pname = createNameNode(); break; case Token.STRING: pname = createStringLiteral(); break; case Token.NUMBER: case Token.BIGINT: pname = createNumericLiteral(tt, true); break; default: if (compilerEnv.isReservedKeywordAsIdentifier() && TokenStream.isKeyword( ts.getString(), compilerEnv.getLanguageVersion(), inUseStrictDirective)) { // convert keyword to property name, e.g. ({if: 1}) pname = createNameNode(); break; } return null; } return pname; } private ObjectProperty plainProperty(AstNode property, int ptt) throws IOException { // Support, e.g., |var {x, y} = o| as destructuring shorthand // for |var {x: x, y: y} = o|, as implemented in spidermonkey JS 1.8. int tt = peekToken(); if ((tt == Token.COMMA || tt == Token.RC) && ptt == Token.NAME && compilerEnv.getLanguageVersion() >= Context.VERSION_1_8) { if (!inDestructuringAssignment && compilerEnv.getLanguageVersion() < Context.VERSION_ES6) { reportError("msg.bad.object.init"); } AstNode nn = new Name(property.getPosition(), property.getString()); ObjectProperty pn = new ObjectProperty(); pn.putProp(Node.SHORTHAND_PROPERTY_NAME, Boolean.TRUE); pn.setLeftAndRight(property, nn); return pn; } mustMatchToken(Token.COLON, "msg.no.colon.prop", true); ObjectProperty pn = new ObjectProperty(); pn.setOperatorPosition(ts.tokenBeg); pn.setLeftAndRight(property, assignExpr()); return pn; } private ObjectProperty methodDefinition(int pos, AstNode propName, int entryKind) throws IOException { FunctionNode fn = function(FunctionNode.FUNCTION_EXPRESSION); // We've already parsed the function name, so fn should be anonymous. Name name = fn.getFunctionName(); if (name != null && name.length() != 0) { reportError("msg.bad.prop"); } ObjectProperty pn = new ObjectProperty(pos); switch (entryKind) { case GET_ENTRY: pn.setIsGetterMethod(); fn.setFunctionIsGetterMethod(); break; case SET_ENTRY: pn.setIsSetterMethod(); fn.setFunctionIsSetterMethod(); break; case METHOD_ENTRY: pn.setIsNormalMethod(); fn.setFunctionIsNormalMethod(); break; } int end = getNodeEnd(fn); pn.setLeft(propName); pn.setRight(fn); pn.setLength(end - pos); return pn; } private Name createNameNode() { return createNameNode(false, Token.NAME); } /** * Create a {@code Name} node using the token info from the last scanned name. In some cases we * need to either synthesize a name node, or we lost the name token information by peeking. If * the {@code token} parameter is not {@link Token#NAME}, then we use token info saved in * instance vars. */ private Name createNameNode(boolean checkActivation, int token) { int beg = ts.tokenBeg; String s = ts.getString(); int lineno = ts.lineno; if (!"".equals(prevNameTokenString)) { beg = prevNameTokenStart; s = prevNameTokenString; lineno = prevNameTokenLineno; prevNameTokenStart = 0; prevNameTokenString = ""; prevNameTokenLineno = 0; } if (s == null) { if (compilerEnv.isIdeMode()) { s = ""; } else { codeBug(); } } Name name = new Name(beg, s); name.setLineno(lineno); if (checkActivation) { checkActivationName(s, token); } return name; } private StringLiteral createStringLiteral() { int pos = ts.tokenBeg, end = ts.tokenEnd; StringLiteral s = new StringLiteral(pos, end - pos); s.setLineno(ts.lineno); s.setValue(ts.getString()); s.setQuoteCharacter(ts.getQuoteChar()); return s; } private AstNode templateLiteral(boolean isTaggedLiteral) throws IOException { if (currentToken != Token.TEMPLATE_LITERAL) codeBug(); int pos = ts.tokenBeg, end = ts.tokenEnd; List<AstNode> elements = new ArrayList<AstNode>(); TemplateLiteral pn = new TemplateLiteral(pos); int posChars = ts.tokenBeg + 1; int tt = ts.readTemplateLiteral(isTaggedLiteral); while (tt == Token.TEMPLATE_LITERAL_SUBST) { elements.add(createTemplateLiteralCharacters(posChars)); elements.add(expr()); mustMatchToken(Token.RC, "msg.syntax", true); posChars = ts.tokenBeg + 1; tt = ts.readTemplateLiteral(isTaggedLiteral); } if (tt == Token.ERROR) { return makeErrorNode(); } assert tt == Token.TEMPLATE_LITERAL; elements.add(createTemplateLiteralCharacters(posChars)); end = ts.tokenEnd; pn.setElements(elements); pn.setLength(end - pos); return pn; } private TemplateCharacters createTemplateLiteralCharacters(int pos) { TemplateCharacters chars = new TemplateCharacters(pos, ts.tokenEnd - pos - 1); chars.setValue(ts.getString()); chars.setRawValue(ts.getRawString()); return chars; } private AstNode createNumericLiteral(int tt, boolean isProperty) { String s = ts.getString(); if (this.inUseStrictDirective && ts.isNumericOldOctal()) { if (compilerEnv.getLanguageVersion() >= Context.VERSION_ES6 || !isProperty) { if (tt == Token.BIGINT) { reportError("msg.no.old.octal.bigint"); } else { reportError("msg.no.old.octal.strict"); } } } if (compilerEnv.getLanguageVersion() >= Context.VERSION_ES6 || !isProperty) { if (ts.isNumericBinary()) { s = "0b" + s; } else if (ts.isNumericOldOctal()) { s = "0" + s; } else if (ts.isNumericOctal()) { s = "0o" + s; } else if (ts.isNumericHex()) { s = "0x" + s; } } if (tt == Token.BIGINT) { return new BigIntLiteral(ts.tokenBeg, s + "n", ts.getBigInt()); } else { return new NumberLiteral(ts.tokenBeg, s, ts.getNumber()); } } protected void checkActivationName(String name, int token) { if (!insideFunction()) { return; } boolean activation = false; if ("arguments".equals(name) && // An arrow function not generate arguments. So it not need activation. ((FunctionNode) currentScriptOrFn).getFunctionType() != FunctionNode.ARROW_FUNCTION) { activation = true; } else if (compilerEnv.getActivationNames() != null && compilerEnv.getActivationNames().contains(name)) { activation = true; } else if ("length".equals(name)) { if (token == Token.GETPROP && compilerEnv.getLanguageVersion() == Context.VERSION_1_2) { // Use of "length" in 1.2 requires an activation object. activation = true; } } if (activation) { setRequiresActivation(); } } protected void setRequiresActivation() { if (insideFunction()) { ((FunctionNode) currentScriptOrFn).setRequiresActivation(); } } private void checkCallRequiresActivation(AstNode pn) { if ((pn.getType() == Token.NAME && "eval".equals(((Name) pn).getIdentifier())) || (pn.getType() == Token.GETPROP && "eval".equals(((PropertyGet) pn).getProperty().getIdentifier()))) setRequiresActivation(); } protected void setIsGenerator() { if (insideFunction()) { ((FunctionNode) currentScriptOrFn).setIsGenerator(); } } private void checkBadIncDec(UpdateExpression expr) { AstNode op = removeParens(expr.getOperand()); int tt = op.getType(); if (!(tt == Token.NAME || tt == Token.GETPROP || tt == Token.GETELEM || tt == Token.GET_REF || tt == Token.CALL)) reportError(expr.getType() == Token.INC ? "msg.bad.incr" : "msg.bad.decr"); } private ErrorNode makeErrorNode() { ErrorNode pn = new ErrorNode(ts.tokenBeg, ts.tokenEnd - ts.tokenBeg); pn.setLineno(ts.lineno); return pn; } // Return end of node. Assumes node does NOT have a parent yet. private static int nodeEnd(AstNode node) { return node.getPosition() + node.getLength(); } private void saveNameTokenData(int pos, String name, int lineno) { prevNameTokenStart = pos; prevNameTokenString = name; prevNameTokenLineno = lineno; } /** * Return the file offset of the beginning of the input source line containing the passed * position. * * @param pos an offset into the input source stream. If the offset is negative, it's converted * to 0, and if it's beyond the end of the source buffer, the last source position is used. * @return the offset of the beginning of the line containing pos (i.e. 1+ the offset of the * first preceding newline). Returns -1 if the {@link CompilerEnvirons} is not set to * ide-mode, and {@link #parse(java.io.Reader,String,int)} was used. */ private int lineBeginningFor(int pos) { if (sourceChars == null) { return -1; } if (pos <= 0) { return 0; } char[] buf = sourceChars; if (pos >= buf.length) { pos = buf.length - 1; } while (--pos >= 0) { char c = buf[pos]; if (ScriptRuntime.isJSLineTerminator(c)) { return pos + 1; // want position after the newline } } return 0; } private void warnMissingSemi(int pos, int end) { // Should probably change this to be a CompilerEnvirons setting, // with an enum Never, Always, Permissive, where Permissive means // don't warn for 1-line functions like function (s) {return x+2} if (compilerEnv.isStrictMode()) { int[] linep = new int[2]; String line = ts.getLine(end, linep); // this code originally called lineBeginningFor() and in order to // preserve its different line-offset handling, we need to special // case ide-mode here int beg = compilerEnv.isIdeMode() ? Math.max(pos, end - linep[1]) : pos; if (line != null) { addStrictWarning("msg.missing.semi", "", beg, end - beg, linep[0], line, linep[1]); } else { // no line information available, report warning at current line addStrictWarning("msg.missing.semi", "", beg, end - beg); } } } private void warnTrailingComma(int pos, List<?> elems, int commaPos) { if (compilerEnv.getWarnTrailingComma()) { // back up from comma to beginning of line or array/objlit if (!elems.isEmpty()) { pos = ((AstNode) elems.get(0)).getPosition(); } pos = Math.max(pos, lineBeginningFor(commaPos)); addWarning("msg.extra.trailing.comma", pos, commaPos - pos); } } // helps reduce clutter in the already-large function() method protected class PerFunctionVariables { private ScriptNode savedCurrentScriptOrFn; private Scope savedCurrentScope; private int savedEndFlags; private boolean savedInForInit; private Map<String, LabeledStatement> savedLabelSet; private List<Loop> savedLoopSet; private List<Jump> savedLoopAndSwitchSet; PerFunctionVariables(FunctionNode fnNode) { savedCurrentScriptOrFn = Parser.this.currentScriptOrFn; Parser.this.currentScriptOrFn = fnNode; savedCurrentScope = Parser.this.currentScope; Parser.this.currentScope = fnNode; savedLabelSet = Parser.this.labelSet; Parser.this.labelSet = null; savedLoopSet = Parser.this.loopSet; Parser.this.loopSet = null; savedLoopAndSwitchSet = Parser.this.loopAndSwitchSet; Parser.this.loopAndSwitchSet = null; savedEndFlags = Parser.this.endFlags; Parser.this.endFlags = 0; savedInForInit = Parser.this.inForInit; Parser.this.inForInit = false; } void restore() { Parser.this.currentScriptOrFn = savedCurrentScriptOrFn; Parser.this.currentScope = savedCurrentScope; Parser.this.labelSet = savedLabelSet; Parser.this.loopSet = savedLoopSet; Parser.this.loopAndSwitchSet = savedLoopAndSwitchSet; Parser.this.endFlags = savedEndFlags; Parser.this.inForInit = savedInForInit; } } /** * Given a destructuring assignment with a left hand side parsed as an array or object literal * and a right hand side expression, rewrite as a series of assignments to the variables defined * in left from property accesses to the expression on the right. * * @param type declaration type: Token.VAR or Token.LET or -1 * @param left array or object literal containing NAME nodes for variables to assign * @param right expression to assign from * @return expression that performs a series of assignments to the variables defined in left */ Node createDestructuringAssignment(int type, Node left, Node right) { String tempName = currentScriptOrFn.getNextTempName(); Node result = destructuringAssignmentHelper(type, left, right, tempName); Node comma = result.getLastChild(); comma.addChildToBack(createName(tempName)); return result; } Node destructuringAssignmentHelper(int variableType, Node left, Node right, String tempName) { Scope result = createScopeNode(Token.LETEXPR, left.getLineno()); result.addChildToFront(new Node(Token.LET, createName(Token.NAME, tempName, right))); try { pushScope(result); defineSymbol(Token.LET, tempName, true); } finally { popScope(); } Node comma = new Node(Token.COMMA); result.addChildToBack(comma); List<String> destructuringNames = new ArrayList<String>(); boolean empty = true; switch (left.getType()) { case Token.ARRAYLIT: empty = destructuringArray( (ArrayLiteral) left, variableType, tempName, comma, destructuringNames); break; case Token.OBJECTLIT: empty = destructuringObject( (ObjectLiteral) left, variableType, tempName, comma, destructuringNames); break; case Token.GETPROP: case Token.GETELEM: switch (variableType) { case Token.CONST: case Token.LET: case Token.VAR: reportError("msg.bad.assign.left"); } comma.addChildToBack(simpleAssignment(left, createName(tempName))); break; default: reportError("msg.bad.assign.left"); } if (empty) { // Don't want a COMMA node with no children. Just add a zero. comma.addChildToBack(createNumber(0)); } result.putProp(Node.DESTRUCTURING_NAMES, destructuringNames); return result; } boolean destructuringArray( ArrayLiteral array, int variableType, String tempName, Node parent, List<String> destructuringNames) { boolean empty = true; int setOp = variableType == Token.CONST ? Token.SETCONST : Token.SETNAME; int index = 0; for (AstNode n : array.getElements()) { if (n.getType() == Token.EMPTY) { index++; continue; } Node rightElem = new Node(Token.GETELEM, createName(tempName), createNumber(index)); if (n.getType() == Token.NAME) { String name = n.getString(); parent.addChildToBack( new Node(setOp, createName(Token.BINDNAME, name, null), rightElem)); if (variableType != -1) { defineSymbol(variableType, name, true); destructuringNames.add(name); } } else { parent.addChildToBack( destructuringAssignmentHelper( variableType, n, rightElem, currentScriptOrFn.getNextTempName())); } index++; empty = false; } return empty; } boolean destructuringObject( ObjectLiteral node, int variableType, String tempName, Node parent, List<String> destructuringNames) { boolean empty = true; int setOp = variableType == Token.CONST ? Token.SETCONST : Token.SETNAME; for (ObjectProperty prop : node.getElements()) { int lineno = 0; // This function is sometimes called from the IRFactory when // when executing regression tests, and in those cases the // tokenStream isn't set. Deal with it. if (ts != null) { lineno = ts.lineno; } AstNode id = prop.getLeft(); Node rightElem = null; if (id instanceof Name) { Node s = Node.newString(((Name) id).getIdentifier()); rightElem = new Node(Token.GETPROP, createName(tempName), s); } else if (id instanceof StringLiteral) { Node s = Node.newString(((StringLiteral) id).getValue()); rightElem = new Node(Token.GETPROP, createName(tempName), s); } else if (id instanceof NumberLiteral) { Node s = createNumber((int) ((NumberLiteral) id).getNumber()); rightElem = new Node(Token.GETELEM, createName(tempName), s); } else { throw codeBug(); } rightElem.setLineno(lineno); AstNode value = prop.getRight(); if (value.getType() == Token.NAME) { String name = ((Name) value).getIdentifier(); parent.addChildToBack( new Node(setOp, createName(Token.BINDNAME, name, null), rightElem)); if (variableType != -1) { defineSymbol(variableType, name, true); destructuringNames.add(name); } } else { parent.addChildToBack( destructuringAssignmentHelper( variableType, value, rightElem, currentScriptOrFn.getNextTempName())); } empty = false; } return empty; } protected Node createName(String name) { checkActivationName(name, Token.NAME); return Node.newString(Token.NAME, name); } protected Node createName(int type, String name, Node child) { Node result = createName(name); result.setType(type); if (child != null) result.addChildToBack(child); return result; } protected Node createNumber(double number) { return Node.newNumber(number); } /** * Create a node that can be used to hold lexically scoped variable definitions (via let * declarations). * * @param token the token of the node to create * @param lineno line number of source * @return the created node */ protected Scope createScopeNode(int token, int lineno) { Scope scope = new Scope(); scope.setType(token); scope.setLineno(lineno); return scope; } // Quickie tutorial for some of the interpreter bytecodes. // // GETPROP - for normal foo.bar prop access; right side is a name // GETELEM - for normal foo[bar] element access; rhs is an expr // SETPROP - for assignment when left side is a GETPROP // SETELEM - for assignment when left side is a GETELEM // DELPROP - used for delete foo.bar or foo[bar] // // GET_REF, SET_REF, DEL_REF - in general, these mean you're using // get/set/delete on a right-hand side expression (possibly with no // explicit left-hand side) that doesn't use the normal JavaScript // Object (i.e. ScriptableObject) get/set/delete functions, but wants // to provide its own versions instead. It will ultimately implement // Ref, and currently SpecialRef (for __proto__ etc.) and XmlName // (for E4X XML objects) are the only implementations. The runtime // notices these bytecodes and delegates get/set/delete to the object. // // BINDNAME: used in assignments. LHS is evaluated first to get a // specific object containing the property ("binding" the property // to the object) so that it's always the same object, regardless of // side effects in the RHS. protected Node simpleAssignment(Node left, Node right) { int nodeType = left.getType(); switch (nodeType) { case Token.NAME: String name = ((Name) left).getIdentifier(); if (inUseStrictDirective && ("eval".equals(name) || "arguments".equals(name))) { reportError("msg.bad.id.strict", name); } left.setType(Token.BINDNAME); return new Node(Token.SETNAME, left, right); case Token.GETPROP: case Token.GETELEM: { Node obj, id; // If it's a PropertyGet or ElementGet, we're in the parse pass. // We could alternately have PropertyGet and ElementGet // override getFirstChild/getLastChild and return the appropriate // field, but that seems just as ugly as this casting. if (left instanceof PropertyGet) { obj = ((PropertyGet) left).getTarget(); id = ((PropertyGet) left).getProperty(); } else if (left instanceof ElementGet) { obj = ((ElementGet) left).getTarget(); id = ((ElementGet) left).getElement(); } else { // This branch is called during IRFactory transform pass. obj = left.getFirstChild(); id = left.getLastChild(); } int type; if (nodeType == Token.GETPROP) { type = Token.SETPROP; // TODO(stevey) - see https://bugzilla.mozilla.org/show_bug.cgi?id=492036 // The new AST code generates NAME tokens for GETPROP ids where the old // parser // generated STRING nodes. If we don't set the type to STRING below, this // will // cause java.lang.VerifyError in codegen for code like // "var obj={p:3};[obj.p]=[9];" id.setType(Token.STRING); } else { type = Token.SETELEM; } return new Node(type, obj, id, right); } case Token.GET_REF: { Node ref = left.getFirstChild(); checkMutableReference(ref); return new Node(Token.SET_REF, ref, right); } } throw codeBug(); } protected void checkMutableReference(Node n) { int memberTypeFlags = n.getIntProp(Node.MEMBER_TYPE_PROP, 0); if ((memberTypeFlags & Node.DESCENDANTS_FLAG) != 0) { reportError("msg.bad.assign.left"); } } // remove any ParenthesizedExpression wrappers protected AstNode removeParens(AstNode node) { while (node instanceof ParenthesizedExpression) { node = ((ParenthesizedExpression) node).getExpression(); } return node; } void markDestructuring(AstNode node) { if (node instanceof DestructuringForm) { ((DestructuringForm) node).setIsDestructuring(true); } else if (node instanceof ParenthesizedExpression) { markDestructuring(((ParenthesizedExpression) node).getExpression()); } } // throw a failed-assertion with some helpful debugging info private RuntimeException codeBug() throws RuntimeException { throw Kit.codeBug( "ts.cursor=" + ts.cursor + ", ts.tokenBeg=" + ts.tokenBeg + ", currentToken=" + currentToken); } public void setDefaultUseStrictDirective(boolean useStrict) { defaultUseStrictDirective = useStrict; } public boolean inUseStrictDirective() { return inUseStrictDirective; } }
⏎ org/mozilla/javascript/Parser.java
Or download all of them as a single archive file:
File name: rhino-1.7.14-sources.jar File size: 1029165 bytes Release date: 2022-01-06 Download
⇒ Example code to Test rhino-runtime-1.7.14.jar
⇐ Download Rhino JavaScript Binary Package
2022-05-03, 35393👍, 1💬
Popular Posts:
iText is an ideal library for developers looking to enhance web- and other applications with dynamic...
JDK 1.1 source code directory contains Java source code for JDK 1.1 core classes: "C:\fyicenter\jdk-...
What Is poi-contrib-3.5.jar? poi-contrib-3.5.jar is one of the JAR files for Apache POI 3.5, which p...
JDOM provides a solution for using XML from Java that is as simple as Java itself. There is no compe...
Jettison is a collection of Java APIs (like STaX and DOM) which read and write JSON. This allows nea...