All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sourceforge.htmlunit.corejs.javascript.Parser Maven / Gradle / Ivy

/* -*- 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 net.sourceforge.htmlunit.corejs.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 net.sourceforge.htmlunit.corejs.javascript.ast.ArrayComprehension;
import net.sourceforge.htmlunit.corejs.javascript.ast.ArrayComprehensionLoop;
import net.sourceforge.htmlunit.corejs.javascript.ast.ArrayLiteral;
import net.sourceforge.htmlunit.corejs.javascript.ast.Assignment;
import net.sourceforge.htmlunit.corejs.javascript.ast.AstNode;
import net.sourceforge.htmlunit.corejs.javascript.ast.AstRoot;
import net.sourceforge.htmlunit.corejs.javascript.ast.BigIntLiteral;
import net.sourceforge.htmlunit.corejs.javascript.ast.Block;
import net.sourceforge.htmlunit.corejs.javascript.ast.BreakStatement;
import net.sourceforge.htmlunit.corejs.javascript.ast.CatchClause;
import net.sourceforge.htmlunit.corejs.javascript.ast.Comment;
import net.sourceforge.htmlunit.corejs.javascript.ast.ConditionalExpression;
import net.sourceforge.htmlunit.corejs.javascript.ast.ContinueStatement;
import net.sourceforge.htmlunit.corejs.javascript.ast.DestructuringForm;
import net.sourceforge.htmlunit.corejs.javascript.ast.DoLoop;
import net.sourceforge.htmlunit.corejs.javascript.ast.ElementGet;
import net.sourceforge.htmlunit.corejs.javascript.ast.EmptyExpression;
import net.sourceforge.htmlunit.corejs.javascript.ast.EmptyStatement;
import net.sourceforge.htmlunit.corejs.javascript.ast.ErrorNode;
import net.sourceforge.htmlunit.corejs.javascript.ast.ExpressionStatement;
import net.sourceforge.htmlunit.corejs.javascript.ast.ForInLoop;
import net.sourceforge.htmlunit.corejs.javascript.ast.ForLoop;
import net.sourceforge.htmlunit.corejs.javascript.ast.FunctionCall;
import net.sourceforge.htmlunit.corejs.javascript.ast.FunctionNode;
import net.sourceforge.htmlunit.corejs.javascript.ast.GeneratorExpression;
import net.sourceforge.htmlunit.corejs.javascript.ast.GeneratorExpressionLoop;
import net.sourceforge.htmlunit.corejs.javascript.ast.IdeErrorReporter;
import net.sourceforge.htmlunit.corejs.javascript.ast.IfStatement;
import net.sourceforge.htmlunit.corejs.javascript.ast.InfixExpression;
import net.sourceforge.htmlunit.corejs.javascript.ast.Jump;
import net.sourceforge.htmlunit.corejs.javascript.ast.KeywordLiteral;
import net.sourceforge.htmlunit.corejs.javascript.ast.Label;
import net.sourceforge.htmlunit.corejs.javascript.ast.LabeledStatement;
import net.sourceforge.htmlunit.corejs.javascript.ast.LetNode;
import net.sourceforge.htmlunit.corejs.javascript.ast.Loop;
import net.sourceforge.htmlunit.corejs.javascript.ast.Name;
import net.sourceforge.htmlunit.corejs.javascript.ast.NewExpression;
import net.sourceforge.htmlunit.corejs.javascript.ast.NumberLiteral;
import net.sourceforge.htmlunit.corejs.javascript.ast.ObjectLiteral;
import net.sourceforge.htmlunit.corejs.javascript.ast.ObjectProperty;
import net.sourceforge.htmlunit.corejs.javascript.ast.ParenthesizedExpression;
import net.sourceforge.htmlunit.corejs.javascript.ast.PropertyGet;
import net.sourceforge.htmlunit.corejs.javascript.ast.RegExpLiteral;
import net.sourceforge.htmlunit.corejs.javascript.ast.ReturnStatement;
import net.sourceforge.htmlunit.corejs.javascript.ast.Scope;
import net.sourceforge.htmlunit.corejs.javascript.ast.ScriptNode;
import net.sourceforge.htmlunit.corejs.javascript.ast.StringLiteral;
import net.sourceforge.htmlunit.corejs.javascript.ast.SwitchCase;
import net.sourceforge.htmlunit.corejs.javascript.ast.SwitchStatement;
import net.sourceforge.htmlunit.corejs.javascript.ast.Symbol;
import net.sourceforge.htmlunit.corejs.javascript.ast.TaggedTemplateLiteral;
import net.sourceforge.htmlunit.corejs.javascript.ast.TemplateCharacters;
import net.sourceforge.htmlunit.corejs.javascript.ast.TemplateLiteral;
import net.sourceforge.htmlunit.corejs.javascript.ast.ThrowStatement;
import net.sourceforge.htmlunit.corejs.javascript.ast.TryStatement;
import net.sourceforge.htmlunit.corejs.javascript.ast.UnaryExpression;
import net.sourceforge.htmlunit.corejs.javascript.ast.UpdateExpression;
import net.sourceforge.htmlunit.corejs.javascript.ast.VariableDeclaration;
import net.sourceforge.htmlunit.corejs.javascript.ast.VariableInitializer;
import net.sourceforge.htmlunit.corejs.javascript.ast.WhileLoop;
import net.sourceforge.htmlunit.corejs.javascript.ast.WithStatement;
import net.sourceforge.htmlunit.corejs.javascript.ast.XmlDotQuery;
import net.sourceforge.htmlunit.corejs.javascript.ast.XmlElemRef;
import net.sourceforge.htmlunit.corejs.javascript.ast.XmlExpression;
import net.sourceforge.htmlunit.corejs.javascript.ast.XmlLiteral;
import net.sourceforge.htmlunit.corejs.javascript.ast.XmlMemberGet;
import net.sourceforge.htmlunit.corejs.javascript.ast.XmlPropRef;
import net.sourceforge.htmlunit.corejs.javascript.ast.XmlRef;
import net.sourceforge.htmlunit.corejs.javascript.ast.XmlString;
import net.sourceforge.htmlunit.corejs.javascript.ast.Yield;

/**
 * This class implements the JavaScript parser.
 *
 * 

It is based on the SpiderMonkey C source files jsparse.c and jsparse.h in the jsref package. * *

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. * *

This parser implementation is not intended to be reused after a parse finishes, and will throw * an IllegalStateException() if invoked again. * *

* * @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 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 labelSet; private List loopSet; private List 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 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(); loopSet.add(loop); if (loopAndSwitchSet == null) loopAndSwitchSet = new ArrayList(); 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(); 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 destructuring = null; Set paramNames = new HashSet(); 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 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 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 destructuring = new HashMap(); Set paramNames = new HashSet(); 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 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 destructuring, Set 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); // HtmlUnit - HACK // allow const in for-of loop's by treating them as let // see JavaScriptEngine2Test.constInOfLoop() // // HtmlUnit - HACK } else if (tt == Token.CONST) { consumeToken(); init = variables(Token.LET, ts.tokenBeg, false); // HtmlUnit - HACK } 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 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; Name varName = null; AstNode catchCond = null; switch (peekToken()) { case Token.LP: { matchToken(Token.LP, true); lp = ts.tokenBeg; mustMatchToken(Token.NAME, "msg.bad.catchcond", true); 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); } } 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); } break; case Token.LC: if (compilerEnv.getLanguageVersion() >= Context.VERSION_ES6) { matchToken(Token.LC, true); } else { reportError("msg.no.paren.catch"); } break; default: reportError("msg.no.paren.catch"); break; } Scope catchScope = new Scope(catchPos); CatchClause catchNode = new CatchClause(catchPos); catchNode.setLineno(ts.lineno); pushScope(catchScope); try { statements(catchScope); } finally { popScope(); } tryEnd = getNodeEnd(catchScope); catchNode.setVarName(varName); catchNode.setCatchCondition(catchCond); catchNode.setBody(catchScope); 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(); 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(); } 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 argumentList() throws IOException { if (matchToken(Token.RP, true)) return null; List result = new ArrayList(); 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 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 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: * *

{@code @attr}, {@code @ns::attr}, {@code @ns::*}, {@code @ns::*}, {@code @*}, * {@code @*::attr}, {@code @*::*}, {@code @ns::[expr]}, {@code @*::[expr]}, {@code @[expr]} * *

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 elements = new ArrayList(); 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 loops = new ArrayList(); 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 loops = new ArrayList(); 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 elems = new ArrayList(); Set getterNames = null; Set setterNames = null; if (this.inUseStrictDirective) { getterNames = new HashSet(); setterNames = new HashSet(); } 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 elements = new ArrayList(); 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 savedLabelSet; private List savedLoopSet; private List 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 destructuringNames = new ArrayList(); 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 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 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; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy