Mozilla/mozilla/js2/src/parser.cpp
rogerl%netscape.com 1aa6f500dc Fixed crash in handling 'for (var i,p in...' - the reader didn't have a
pre-peeked token for the error report unless it was backed up.


git-svn-id: svn://10.0.0.236/trunk@104053 18797224-902f-48f8-a5cc-f745e15eee43
2001-09-28 01:16:00 +00:00

2948 lines
105 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* The contents of this file are subject to the Netscape Public
* License Version 1.1 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.mozilla.org/NPL/
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* The Original Code is the JavaScript 2 Prototype.
*
* The Initial Developer of the Original Code is Netscape
* Communications Corporation. Portions created by Netscape are
* Copyright (C) 1998 Netscape Communications Corporation. All
* Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the
* terms of the GNU Public License (the "GPL"), in which case the
* provisions of the GPL are applicable instead of those above.
* If you wish to allow use of your version of this file only
* under the terms of the GPL and not to allow others to use your
* version of this file under the NPL, indicate your decision by
* deleting the provisions above and replace them with the notice
* and other provisions required by the GPL. If you do not delete
* the provisions above, a recipient may use your version of this
* file under either the NPL or the GPL.
*/
#include "numerics.h"
#include "parser.h"
namespace JS = JavaScript;
//
// Parser
//
// Create a new Parser for parsing the provided source code, interning
// identifiers, keywords, and regular expressions in the designated world,
// and allocating the parse tree in the designated arena.
// On entry to the constructor, flags is the initial set of pragma flags.
// As the parser's methods are called to parse input text, flags will change
// to reflect new top-level pragma settings. The caller must make sure that
// flags refers to a variable that will accept such mutations.
JS::Parser::Parser(World &world, Arena &arena, Pragma::Flags &flags, const String &source, const String &sourceLocation,
uint32 initialLineNum):
lexer(world, source, sourceLocation, initialLineNum),
arena(arena),
flags(flags)
{
}
// Report a syntax error at the backUp-th last token read by the Lexer.
// In other words, if backUp is 0, the error is at the next token to be read
// by the Lexer (which must have been peeked already); if backUp is 1, the
// error is at the last token read by the Lexer, and so forth.
void JS::Parser::syntaxError(const char *message, uint backUp /* = 1 */)
{
syntaxError(widenCString(message), backUp);
}
// Same as above, but the error message is already a String.
void JS::Parser::syntaxError(const String &message, uint backUp)
{
while (backUp--)
lexer.unget();
getReader().error(Exception::syntaxError, message, lexer.getPos());
}
// Get the next token using the given preferRegExp setting. If that token's
// kind matches the given kind, consume that token and return it. Otherwise
// throw a syntax error.
const JS::Token &JS::Parser::require(bool preferRegExp, Token::Kind kind)
{
const Token &t = lexer.get(preferRegExp);
if (!t.hasKind(kind)) {
String message;
bool special = Token::isSpecialKind(kind);
if (special)
message += '\'';
message += Token::kindName(kind);
if (special)
message += '\'';
message += " expected";
syntaxError(message);
}
return t;
}
// Copy the Token's chars into the current arena and return the resulting copy.
inline JS::String &JS::Parser::copyTokenChars(const Token &t)
{
return newArenaString(arena, t.getChars());
}
// If t is an Identifier, return a new IdentifierExprNode corresponding to t.
// Otherwise, return null.
JS::ExprNode *JS::Parser::makeIdentifierExpression(const Token &t) const
{
if (t.hasIdentifierKind())
return new(arena) IdentifierExprNode(t);
return 0;
}
// Signal an error if t is not an identifier. Return the interned identifier.
const JS::StringAtom &JS::Parser::ensureIdentifier(const Token &t)
{
if (!t.hasIdentifierKind())
syntaxError("Identifier expected");
return t.getIdentifier();
}
// Parse and return an Identifier. If the token has been peeked, it should have
// been done with preferRegExp set to true.
const JS::StringAtom &JS::Parser::parseIdentifier()
{
return ensureIdentifier(lexer.get(true));
}
// Parse one or more identifiers separated by separator tokens. Return them as a linked list.
// If the first or second token was peeked, it should be have been done with preferRegExp set to true.
// After parseIdentifierList finishes, the next token will have been peeked with preferRegExp set to true.
JS::IdentifierList *JS::Parser::parseIdentifierList(Token::Kind separator)
{
NodeQueue<IdentifierList> includeExclude;
do includeExclude += new(arena) IdentifierList(parseIdentifier());
while (lexer.eat(true, separator));
return includeExclude.first;
}
// An identifier or parenthesized expression has just been parsed into e.
// If it is followed by one or more ::'s followed by identifiers, construct
// the appropriate qualify parse node and return it and set foundQualifiers
// to true. If no :: is found, return e and set foundQualifiers to false.
// After parseIdentifierQualifiers finishes, the next token might have been
// peeked with the given preferRegExp setting.
JS::ExprNode *JS::Parser::parseIdentifierQualifiers(ExprNode *e, bool &foundQualifiers, bool preferRegExp)
{
const Token *tDoubleColon = lexer.eat(preferRegExp, Token::doubleColon);
if (!tDoubleColon) {
foundQualifiers = false;
return e;
}
foundQualifiers = true;
size_t pos = tDoubleColon->getPos();
return new(arena) QualifyExprNode(pos, e, parseIdentifier());
}
// An opening parenthesis has just been parsed into tParen. Finish parsing a
// ParenthesizedListExpression (or ParenthesizedExpression if noComma is true).
// If it is in fact a ParenthesizedExpression and is followed by a :: and an identifier,
// construct the appropriate qualify parse node, return it, set foundQualifiers to true.
// Otherwise return the ParenthesizedListExpression and set foundQualifiers to false.
// After parseParenthesesAndIdentifierQualifiers finishes, the next token might have been
// peeked with the given preferRegExp setting.
JS::ExprNode *JS::Parser::parseParenthesesAndIdentifierQualifiers(const Token &tParen, bool noComma, bool &foundQualifiers,
bool preferRegExp)
{
size_t pos = tParen.getPos();
ExprNode *inner = parseGeneralExpression(false, false, false, noComma);
ExprNode *e = new(arena) UnaryExprNode(pos, ExprNode::parentheses, inner);
require(false, Token::closeParenthesis);
if (inner->hasKind(ExprNode::comma)) {
foundQualifiers = false;
return e;
}
return parseIdentifierQualifiers(e, foundQualifiers, preferRegExp);
}
// Parse and return a qualifiedIdentifier. The first token has already been parsed and is in t.
// If the second token was peeked, it should be have been done with the given preferRegExp setting.
// After parseQualifiedIdentifier finishes, the next token might have been peeked with the given
// preferRegExp setting.
JS::ExprNode *JS::Parser::parseQualifiedIdentifier(const Token &t, bool preferRegExp)
{
bool foundQualifiers;
ExprNode *e = makeIdentifierExpression(t);
if (e)
return parseIdentifierQualifiers(e, foundQualifiers, preferRegExp);
if (t.hasKind(Token::openParenthesis)) {
e = parseParenthesesAndIdentifierQualifiers(t, true, foundQualifiers, preferRegExp);
goto checkQualifiers;
}
if (t.hasKind(Token::Public) || t.hasKind(Token::Private)) {
e = parseIdentifierQualifiers(new(arena) IdentifierExprNode(t), foundQualifiers, preferRegExp);
checkQualifiers:
if (!foundQualifiers)
syntaxError("'::' expected", 0);
return e;
}
syntaxError("Qualified identifier expected");
return 0; // Unreachable code here just to shut up compiler warnings
}
// Parse and return an arrayLiteral. The opening bracket has already been read into initialToken.
JS::PairListExprNode *JS::Parser::parseArrayLiteral(const Token &initialToken)
{
size_t initialPos = initialToken.getPos();
NodeQueue<ExprPairList> elements;
while (true) {
ExprNode *element = 0;
const Token &t = lexer.peek(true);
if (t.hasKind(Token::comma) || t.hasKind(Token::closeBracket))
lexer.redesignate(false); // Safe: neither ',' nor '}' starts with a slash.
else
element = parseAssignmentExpression(false);
elements += new(arena) ExprPairList(0, element);
const Token &tSeparator = lexer.get(false);
if (tSeparator.hasKind(Token::closeBracket))
break;
if (!tSeparator.hasKind(Token::comma))
syntaxError("',' expected");
}
return new(arena) PairListExprNode(initialPos, ExprNode::arrayLiteral, elements.first);
}
// Parse and return an objectLiteral. The opening brace has already been read into initialToken.
JS::PairListExprNode *JS::Parser::parseObjectLiteral(const Token &initialToken)
{
size_t initialPos = initialToken.getPos();
NodeQueue<ExprPairList> elements;
if (!lexer.eat(true, Token::closeBrace))
while (true) {
const Token &t = lexer.get(true);
ExprNode *field = makeIdentifierExpression(t);
if (!field) {
if (t.hasKind(Token::string))
field = new(arena) StringExprNode(t.getPos(), ExprNode::string, copyTokenChars(t));
else if (t.hasKind(Token::number))
field = new(arena) NumberExprNode(t);
else if (t.hasKind(Token::openParenthesis)) {
field = parseAssignmentExpression(false);
require(false, Token::closeParenthesis);
} else
syntaxError("Field name expected");
}
require(false, Token::colon);
elements += new(arena) ExprPairList(field, parseAssignmentExpression(false));
const Token &tSeparator = lexer.get(false);
if (tSeparator.hasKind(Token::closeBrace))
break;
if (!tSeparator.hasKind(Token::comma))
syntaxError("',' expected");
}
return new(arena) PairListExprNode(initialPos, ExprNode::objectLiteral, elements.first);
}
// e is a UnitExpression or a ParenthesizedListExpression. If it is followed by one or more
// string literals on the same line, construct and return the appropriate outer UnitExpression;
// otherwise return e. After parseUnitSuffixes finishes, the next token might have been peeked
// with preferRegExp set to false.
JS::ExprNode *JS::Parser::parseUnitSuffixes(ExprNode *e)
{
while (true) {
const Token &t = lexer.peek(false);
if (lineBreakBefore(t) || !t.hasKind(Token::string))
return e;
lexer.skip();
e = new(arena) ExprUnitExprNode(t.getPos(), ExprNode::exprUnit, e, copyTokenChars(t));
}
}
// A super token has just been read. If superState is ssExpr, parse a SuperExpression;
// if superState is ssStmt, parse either a SuperExpression or a SuperStatement, giving
// preference to SuperExpression. superState cannot be ssNone. pos is the position of
// the super token.
// After parseSuper finishes, the next token might have been peeked with preferRegExp set to false.
JS::ExprNode *JS::Parser::parseSuper(size_t pos, SuperState superState)
{
ASSERT(superState != ssNone);
ExprNode *e = 0;
if (lexer.eat(false, Token::openParenthesis)) {
if (superState == ssExpr) {
e = parseAssignmentExpression(false);
require(false, Token::closeParenthesis);
} else {
InvokeExprNode *se = parseInvoke(0, pos, Token::closeParenthesis, ExprNode::superStmt);
// Simplify a one-anonymous-argument superStmt into a superExpr.
ExprPairList *pairs = se->pairs;
if (!pairs || pairs->next || pairs->field)
return se;
e = pairs->value;
}
}
return new(arena) SuperExprNode(pos, e);
}
// Parse and return a FunctionExpression. The 'function' token has already been read; pos is its position.
//
// After parseFunctionExpression finishes, the next token might have been peeked with preferRegExp set to false.
JS::FunctionExprNode *JS::Parser::parseFunctionExpression(size_t pos)
{
FunctionExprNode *f = new(arena) FunctionExprNode(pos);
const Token &t = lexer.peek(true);
if (t.hasIdentifierKind()) {
f->function.name = &t.getIdentifier();
lexer.skip();
}
parseFunctionSignature(f->function);
f->function.body = parseBody(0);
return f;
}
// Parse and return a PrimaryExpression. If superState is ssExpr, also allow a SuperExpression;
// if superState is ssStmt, also allow a SuperExpression or a SuperStatement.
// If the first token was peeked, it should be have been done with preferRegExp set to true.
// If the first token is 'private', the second token might have been peeked with preferRegExp set to false.
//
// After parsePrimaryExpression finishes, the next token might have been peeked with preferRegExp set to false.
JS::ExprNode *JS::Parser::parsePrimaryExpression(SuperState superState)
{
ExprNode *e;
ExprNode::Kind eKind;
bool b;
const Token &t = lexer.get(true);
switch (t.getKind()) {
case Token::Null:
eKind = ExprNode::Null;
goto makeExprNode;
case Token::True:
b = true;
goto makeBooleanNode;
case Token::False:
b = false;
makeBooleanNode:
e = new(arena) BooleanExprNode(t.getPos(), b);
break;
case Token::This:
eKind = ExprNode::This;
makeExprNode:
e = new(arena) ExprNode(t.getPos(), eKind);
break;
case Token::Public:
if (lexer.peek(false).hasKind(Token::doubleColon))
goto makeQualifiedIdentifierNode;
e = new(arena) IdentifierExprNode(t);
break;
case Token::number:
{
const Token &tUnit = lexer.peek(false);
if (!lineBreakBefore(tUnit) && (tUnit.hasKind(Token::unit) || tUnit.hasKind(Token::string))) {
lexer.skip();
e = parseUnitSuffixes(new(arena) NumUnitExprNode(t.getPos(), ExprNode::numUnit, copyTokenChars(t),
t.getValue(), copyTokenChars(tUnit)));
} else
e = new(arena) NumberExprNode(t);
}
break;
case Token::string:
e = new(arena) StringExprNode(t.getPos(), ExprNode::string, copyTokenChars(t));
break;
case Token::regExp:
e = new(arena) RegExpExprNode(t.getPos(), ExprNode::regExp, t.getIdentifier(), copyTokenChars(t));
break;
case Token::Private:
case CASE_TOKEN_NONRESERVED:
makeQualifiedIdentifierNode:
e = parseQualifiedIdentifier(t, false);
break;
case Token::openParenthesis:
{
bool foundQualifiers;
e = parseParenthesesAndIdentifierQualifiers(t, false, foundQualifiers, false);
if (!foundQualifiers)
e = parseUnitSuffixes(e);
}
break;
case Token::openBracket:
e = parseArrayLiteral(t);
break;
case Token::openBrace:
e = parseObjectLiteral(t);
break;
case Token::Function:
e = parseFunctionExpression(t.getPos());
break;
case Token::Super:
if (superState != ssNone) {
e = parseSuper(t.getPos(), superState);
break;
}
// Fall through to a syntax error if super is not allowed.
default:
syntaxError("Expression expected");
// Unreachable code here just to shut up compiler warnings
e = 0;
}
return e;
}
// Parse a QualifiedIdentifier, ParenthesizedExpression, or 'class' following a dot
// and return the resulting ExprNode.
// tOperator is the . token. target is the first operand. If target is a superExpr then
// only a QualifiedIdentifier is allowed after the dot.
// After parseMember finishes, the next token might have been peeked with the given preferRegExp setting.
JS::ExprNode *JS::Parser::parseMember(ExprNode *target, const Token &tOperator, bool preferRegExp)
{
size_t pos = tOperator.getPos();
const Token &t2 = lexer.get(true);
if (t2.hasKind(Token::Class) && !target->hasKind(ExprNode::superExpr))
return new(arena) UnaryExprNode(pos, ExprNode::dotClass, target);
ExprNode *member;
ExprNode::Kind kind = ExprNode::dot;
if (t2.hasKind(Token::openParenthesis) && !target->hasKind(ExprNode::superExpr)) {
bool foundQualifiers;
member = parseParenthesesAndIdentifierQualifiers(t2, true, foundQualifiers, false);
if (!foundQualifiers)
kind = ExprNode::dotParen;
} else
member = parseQualifiedIdentifier(t2, preferRegExp);
return new(arena) BinaryExprNode(pos, kind, target, member);
}
// Parse an ArgumentsList followed by a closing parenthesis or bracket and return the resulting InvokeExprNode.
// The target function, indexed object, or created class is supplied. The opening parenthesis
// or bracket has already been read. pos is the position to use for the generated node.
JS::InvokeExprNode *JS::Parser::parseInvoke(ExprNode *target, size_t pos, Token::Kind closingTokenKind, ExprNode::Kind invokeKind)
{
NodeQueue<ExprPairList> arguments;
bool hasNamedArgument = false;
if (!lexer.eat(true, closingTokenKind))
while (true) {
ExprNode *field = 0;
ExprNode *value = parseAssignmentExpression(false);
if (lexer.eat(false, Token::colon)) {
field = value;
if (!(field->hasKind(ExprNode::identifier) ||
field->hasKind(ExprNode::number) ||
field->hasKind(ExprNode::string) ||
field->hasKind(ExprNode::parentheses) && !checked_cast<UnaryExprNode *>(field)->op->hasKind(ExprNode::comma)))
syntaxError("Argument name must be an identifier, string, number, or parenthesized expression");
hasNamedArgument = true;
value = parseAssignmentExpression(false);
} else if (hasNamedArgument)
syntaxError("Unnamed argument cannot follow named argument", 0);
arguments += new(arena) ExprPairList(field, value);
const Token &tSeparator = lexer.get(false);
if (tSeparator.hasKind(closingTokenKind))
break;
if (!tSeparator.hasKind(Token::comma))
syntaxError("',' expected");
}
return new(arena) InvokeExprNode(pos, invokeKind, target, arguments.first);
}
// Given an alreaddy parsed PrimaryExpression, parse and return a PostfixExpression.
// If attribute is true, only allow AttributeExpression operators.
// If newExpression is true, the PrimaryExpression is immediately preceded by 'new',
// so don't allow call, postincrement, or postdecrement operators on it.
// If the first token was peeked, it should be have been done with preferRegExp set to
// the value of the attribute parameter. After parsePostfixOperator finishes, the next token
// might have been peeked with preferRegExp set to the value of the attribute parameter.
JS::ExprNode *JS::Parser::parsePostfixOperator(ExprNode *e, bool newExpression, bool attribute)
{
while (true) {
ExprNode::Kind eKind;
const Token &t = lexer.get(attribute);
switch (t.getKind()) {
case Token::openParenthesis:
if (newExpression)
goto other;
e = parseInvoke(e, t.getPos(), Token::closeParenthesis, ExprNode::call);
break;
case Token::openBracket:
e = parseInvoke(e, t.getPos(), Token::closeBracket, ExprNode::index);
break;
case Token::dot:
e = parseMember(e, t, attribute);
break;
case Token::increment:
eKind = ExprNode::postIncrement;
incDec:
if (newExpression || attribute || lineBreakBefore(t))
goto other;
e = new(arena) UnaryExprNode(t.getPos(), eKind, e);
break;
case Token::decrement:
eKind = ExprNode::postDecrement;
goto incDec;
default:
other:
lexer.unget();
return e;
}
}
}
// Parse and return a PostfixExpression. If superState is ssExpr, also allow a SuperExpression;
// if superState is ssStmt, also allow a SuperExpression or a SuperStatement.
// If newExpression is true, this expression is immediately preceded by 'new',
// so don't allow call, postincrement, or postdecrement operators on it.
// If the first token was peeked, it should be have been done with preferRegExp set to true.
// If the first token is 'private', the second token might have been peeked with preferRegExp set to false.
//
// After parsePostfixExpression finishes, the next token might have been peeked with preferRegExp set to false.
JS::ExprNode *JS::Parser::parsePostfixExpression(SuperState superState, bool newExpression)
{
ExprNode *e;
const Token *tNew = lexer.eat(true, Token::New);
if (tNew) {
checkStackSize();
size_t posNew = tNew->getPos();
e = parsePostfixExpression(ssExpr, true);
if (lexer.eat(false, Token::openParenthesis))
e = parseInvoke(e, posNew, Token::closeParenthesis, ExprNode::New);
else
e = new(arena) InvokeExprNode(posNew, ExprNode::New, e, 0);
} else {
e = parsePrimaryExpression(superState);
if (e->hasKind(ExprNode::superStmt)) {
ASSERT(superState == ssStmt);
return e;
}
}
return parsePostfixOperator(e, newExpression, false);
}
// Ensure that e is a PostfixExpression. If not, throw a syntax error on
// the current token.
void JS::Parser::ensurePostfix(const ExprNode *e)
{
ASSERT(e);
if (!e->isPostfix())
syntaxError("Only a postfix expression can be used as the target "
"of an assignment; enclose this expression in parentheses", 0);
}
// Parse and return a UnaryExpression. If superState is ssExpr, also allow a SuperExpression;
// if superState is ssStmt, also allow a SuperExpression or a SuperStatement.
// If the first token was peeked, it should be have been done with preferRegExp set to true.
// If the first token is 'private', the second token might have been peeked with preferRegExp set to false.
//
// After parseUnaryExpression finishes, the next token might have been peeked with preferRegExp set to false.
JS::ExprNode *JS::Parser::parseUnaryExpression(SuperState superState)
{
ExprNode::Kind eKind;
ExprNode *e;
const Token &t = lexer.peek(true);
size_t pos = t.getPos();
switch (t.getKind()) {
case Token::Delete:
eKind = ExprNode::Delete;
goto getPostfixExpression;
case Token::increment:
eKind = ExprNode::preIncrement;
goto getPostfixExpression;
case Token::decrement:
eKind = ExprNode::preDecrement;
getPostfixExpression:
lexer.skip();
e = parsePostfixExpression(ssExpr, false);
break;
case Token::Void:
eKind = ExprNode::Void;
goto getUnaryExpressionNotSuper;
case Token::Typeof:
eKind = ExprNode::Typeof;
goto getUnaryExpressionNotSuper;
case Token::plus:
eKind = ExprNode::plus;
goto getUnaryExpressionOrSuper;
case Token::minus:
eKind = ExprNode::minus;
goto getUnaryExpressionOrSuper;
case Token::complement:
eKind = ExprNode::complement;
getUnaryExpressionOrSuper:
superState = ssExpr;
goto getUnaryExpression;
case Token::logicalNot:
eKind = ExprNode::logicalNot;
getUnaryExpressionNotSuper:
superState = ssNone;
getUnaryExpression:
lexer.skip();
checkStackSize();
e = parseUnaryExpression(superState);
break;
default:
return parsePostfixExpression(superState, false);
}
return new(arena) UnaryExprNode(pos, eKind, e);
}
const JS::Parser::BinaryOperatorInfo JS::Parser::tokenBinaryOperatorInfos[Token::kindsEnd] = {
// Special
{ExprNode::none, pExpression, pNone, false}, // Token::end
{ExprNode::none, pExpression, pNone, false}, // Token::number
{ExprNode::none, pExpression, pNone, false}, // Token::string
{ExprNode::none, pExpression, pNone, false}, // Token::unit
{ExprNode::none, pExpression, pNone, false}, // Token::regExp
// Punctuators
{ExprNode::none, pExpression, pNone, false}, // Token::openParenthesis
{ExprNode::none, pExpression, pNone, false}, // Token::closeParenthesis
{ExprNode::none, pExpression, pNone, false}, // Token::openBracket
{ExprNode::none, pExpression, pNone, false}, // Token::closeBracket
{ExprNode::none, pExpression, pNone, false}, // Token::openBrace
{ExprNode::none, pExpression, pNone, false}, // Token::closeBrace
{ExprNode::comma, pAssignment, pExpression, false}, // Token::comma (right-associative for efficiency)
{ExprNode::none, pExpression, pNone, false}, // Token::semicolon
{ExprNode::none, pExpression, pNone, false}, // Token::dot
{ExprNode::none, pExpression, pNone, false}, // Token::doubleDot
{ExprNode::none, pExpression, pNone, false}, // Token::tripleDot
{ExprNode::none, pExpression, pNone, false}, // Token::arrow
{ExprNode::none, pExpression, pNone, false}, // Token::colon
{ExprNode::none, pExpression, pNone, false}, // Token::doubleColon
{ExprNode::none, pExpression, pNone, false}, // Token::pound
{ExprNode::none, pExpression, pNone, false}, // Token::at
{ExprNode::none, pExpression, pNone, false}, // Token::increment
{ExprNode::none, pExpression, pNone, false}, // Token::decrement
{ExprNode::none, pExpression, pNone, false}, // Token::complement
{ExprNode::none, pExpression, pNone, false}, // Token::logicalNot
{ExprNode::multiply, pMultiplicative, pMultiplicative, true}, // Token::times
{ExprNode::divide, pMultiplicative, pMultiplicative, true}, // Token::divide
{ExprNode::modulo, pMultiplicative, pMultiplicative, true}, // Token::modulo
{ExprNode::add, pAdditive, pAdditive, true}, // Token::plus
{ExprNode::subtract, pAdditive, pAdditive, true}, // Token::minus
{ExprNode::leftShift, pShift, pShift, true}, // Token::leftShift
{ExprNode::rightShift, pShift, pShift, true}, // Token::rightShift
{ExprNode::logicalRightShift, pShift, pShift, true}, // Token::logicalRightShift
{ExprNode::logicalAnd, pBitwiseOr, pLogicalAnd, false}, // Token::logicalAnd (right-associative for efficiency)
{ExprNode::logicalXor, pLogicalAnd, pLogicalXor, false}, // Token::logicalXor (right-associative for efficiency)
{ExprNode::logicalOr, pLogicalXor, pLogicalOr, false}, // Token::logicalOr (right-associative for efficiency)
{ExprNode::bitwiseAnd, pBitwiseAnd, pBitwiseAnd, true}, // Token::bitwiseAnd
{ExprNode::bitwiseXor, pBitwiseXor, pBitwiseXor, true}, // Token::bitwiseXor
{ExprNode::bitwiseOr, pBitwiseOr, pBitwiseOr, true}, // Token::bitwiseOr
{ExprNode::assignment, pPostfix, pAssignment, false}, // Token::assignment
{ExprNode::multiplyEquals, pPostfix, pAssignment, true}, // Token::timesEquals
{ExprNode::divideEquals, pPostfix, pAssignment, true}, // Token::divideEquals
{ExprNode::moduloEquals, pPostfix, pAssignment, true}, // Token::moduloEquals
{ExprNode::addEquals, pPostfix, pAssignment, true}, // Token::plusEquals
{ExprNode::subtractEquals, pPostfix, pAssignment, true}, // Token::minusEquals
{ExprNode::leftShiftEquals, pPostfix, pAssignment, true}, // Token::leftShiftEquals
{ExprNode::rightShiftEquals, pPostfix, pAssignment, true}, // Token::rightShiftEquals
{ExprNode::logicalRightShiftEquals, pPostfix, pAssignment, true}, // Token::logicalRightShiftEquals
{ExprNode::logicalAndEquals, pPostfix, pAssignment, false}, // Token::logicalAndEquals
{ExprNode::logicalXorEquals, pPostfix, pAssignment, false}, // Token::logicalXorEquals
{ExprNode::logicalOrEquals, pPostfix, pAssignment, false}, // Token::logicalOrEquals
{ExprNode::bitwiseAndEquals, pPostfix, pAssignment, true}, // Token::bitwiseAndEquals
{ExprNode::bitwiseXorEquals, pPostfix, pAssignment, true}, // Token::bitwiseXorEquals
{ExprNode::bitwiseOrEquals, pPostfix, pAssignment, true}, // Token::bitwiseOrEquals
{ExprNode::equal, pEquality, pEquality, true}, // Token::equal
{ExprNode::notEqual, pEquality, pEquality, true}, // Token::notEqual
{ExprNode::lessThan, pRelational, pRelational, true}, // Token::lessThan
{ExprNode::lessThanOrEqual, pRelational, pRelational, true}, // Token::lessThanOrEqual
{ExprNode::greaterThan, pRelational, pRelational, true}, // Token::greaterThan
{ExprNode::greaterThanOrEqual, pRelational, pRelational, true}, // Token::greaterThanOrEqual
{ExprNode::identical, pEquality, pEquality, true}, // Token::identical
{ExprNode::notIdentical, pEquality, pEquality, true}, // Token::notIdentical
{ExprNode::conditional, pLogicalOr, pConditional, false}, // Token::question
// Reserved words
{ExprNode::none, pExpression, pNone, false}, // Token::Abstract
{ExprNode::In, pRelational, pRelational, false}, // Token::As
{ExprNode::none, pExpression, pNone, false}, // Token::Break
{ExprNode::none, pExpression, pNone, false}, // Token::Case
{ExprNode::none, pExpression, pNone, false}, // Token::Catch
{ExprNode::none, pExpression, pNone, false}, // Token::Class
{ExprNode::none, pExpression, pNone, false}, // Token::Const
{ExprNode::none, pExpression, pNone, false}, // Token::Continue
{ExprNode::none, pExpression, pNone, false}, // Token::Debugger
{ExprNode::none, pExpression, pNone, false}, // Token::Default
{ExprNode::none, pExpression, pNone, false}, // Token::Delete
{ExprNode::none, pExpression, pNone, false}, // Token::Do
{ExprNode::none, pExpression, pNone, false}, // Token::Else
{ExprNode::none, pExpression, pNone, false}, // Token::Enum
{ExprNode::none, pExpression, pNone, false}, // Token::Export
{ExprNode::none, pExpression, pNone, false}, // Token::Extends
{ExprNode::none, pExpression, pNone, false}, // Token::False
{ExprNode::none, pExpression, pNone, false}, // Token::Final
{ExprNode::none, pExpression, pNone, false}, // Token::Finally
{ExprNode::none, pExpression, pNone, false}, // Token::For
{ExprNode::none, pExpression, pNone, false}, // Token::Function
{ExprNode::none, pExpression, pNone, false}, // Token::Goto
{ExprNode::none, pExpression, pNone, false}, // Token::If
{ExprNode::none, pExpression, pNone, false}, // Token::Implements
{ExprNode::none, pExpression, pNone, false}, // Token::Import
{ExprNode::In, pRelational, pRelational, false}, // Token::In
{ExprNode::Instanceof, pRelational, pRelational, false}, // Token::Instanceof
{ExprNode::none, pExpression, pNone, false}, // Token::Interface
{ExprNode::Is, pRelational, pRelational, false}, // Token::Is
{ExprNode::none, pExpression, pNone, false}, // Token::Namespace
{ExprNode::none, pExpression, pNone, false}, // Token::Native
{ExprNode::none, pExpression, pNone, false}, // Token::New
{ExprNode::none, pExpression, pNone, false}, // Token::Null
{ExprNode::none, pExpression, pNone, false}, // Token::Package
{ExprNode::none, pExpression, pNone, false}, // Token::Private
{ExprNode::none, pExpression, pNone, false}, // Token::Protected
{ExprNode::none, pExpression, pNone, false}, // Token::Public
{ExprNode::none, pExpression, pNone, false}, // Token::Return
{ExprNode::none, pExpression, pNone, false}, // Token::Static
{ExprNode::none, pExpression, pNone, false}, // Token::Super
{ExprNode::none, pExpression, pNone, false}, // Token::Switch
{ExprNode::none, pExpression, pNone, false}, // Token::Synchronized
{ExprNode::none, pExpression, pNone, false}, // Token::This
{ExprNode::none, pExpression, pNone, false}, // Token::Throw
{ExprNode::none, pExpression, pNone, false}, // Token::Throws
{ExprNode::none, pExpression, pNone, false}, // Token::Transient
{ExprNode::none, pExpression, pNone, false}, // Token::True
{ExprNode::none, pExpression, pNone, false}, // Token::Try
{ExprNode::none, pExpression, pNone, false}, // Token::Typeof
{ExprNode::none, pExpression, pNone, false}, // Token::Use
{ExprNode::none, pExpression, pNone, false}, // Token::Var
{ExprNode::none, pExpression, pNone, false}, // Token::Void
{ExprNode::none, pExpression, pNone, false}, // Token::Volatile
{ExprNode::none, pExpression, pNone, false}, // Token::While
{ExprNode::none, pExpression, pNone, false}, // Token::With
// Non-reserved words
{ExprNode::none, pExpression, pNone, false}, // Token::Ecmascript
{ExprNode::none, pExpression, pNone, false}, // Token::Eval
{ExprNode::none, pExpression, pNone, false}, // Token::Exclude
{ExprNode::none, pExpression, pNone, false}, // Token::Get
{ExprNode::none, pExpression, pNone, false}, // Token::Include
{ExprNode::none, pExpression, pNone, false}, // Token::Javascript
{ExprNode::none, pExpression, pNone, false}, // Token::Set
{ExprNode::none, pExpression, pNone, false}, // Token::Strict
{ExprNode::none, pExpression, pNone, false} // Token::identifier
};
struct JS::Parser::StackedSubexpression {
ExprNode::Kind kind; // The kind of BinaryExprNode the subexpression should generate
uchar precedence; // Precedence of an operator with respect to operators on its right
bool superRight; // True if the right operand can be super
size_t pos; // The operator token's position
ExprNode *op1; // First operand of the operator
ExprNode *op2; // Second operand of the operator (used for ?: only)
};
// Parse and return a ListExpression. If allowSuperStmt is true, also allow the expression to be a SuperStatement.
// If noAssignment is false, allow the = and op= operators. If noComma is false, allow the comma operator.
// If the first token was peeked, it should have been done with preferRegExp set to true.
// If the first token is 'private', the second token might have been peeked with preferRegExp set to false.
//
// After parseGeneralExpression finishes, the next token might have been peeked with preferRegExp set to false.
JS::ExprNode *JS::Parser::parseGeneralExpression(bool allowSuperStmt, bool noIn, bool noAssignment, bool noComma)
{
ArrayBuffer<StackedSubexpression, 10> subexpressionStack;
checkStackSize();
// Push a limiter onto subexpressionStack.
subexpressionStack.reserve_advance_back()->precedence = pNone;
ExprNode *e = parseUnaryExpression(allowSuperStmt ? ssStmt : ssExpr);
if (e->hasKind(ExprNode::superStmt)) {
ASSERT(allowSuperStmt);
return e;
}
while (true) {
const Token &t = lexer.peek(false);
const BinaryOperatorInfo &binOpInfo = tokenBinaryOperatorInfos[t.getKind()];
Precedence precedence = binOpInfo.precedenceLeft;
ExprNode::Kind kind = binOpInfo.kind;
ASSERT(precedence > pNone);
// Disqualify assignments, 'in', and comma if the flags indicate that these should end the expression.
if (precedence == pPostfix && noAssignment || kind == ExprNode::In && noIn || kind == ExprNode::comma && noComma) {
kind = ExprNode::none;
precedence = pExpression;
}
if (precedence == pPostfix) {
// Ensure that the target of an assignment is a postfix subexpression or, where permitted,
// a super subexpression.
if (!(binOpInfo.superLeft && e->hasKind(ExprNode::superExpr)))
ensurePostfix(e);
} else
// Reduce already stacked operators with precedenceLeft or higher precedence
while (subexpressionStack.back().precedence >= precedence) {
StackedSubexpression &s = subexpressionStack.pop_back();
if (e->hasKind(ExprNode::superExpr) && !s.superRight)
syntaxError("super expression not allowed here", 0);
if (s.kind == ExprNode::conditional) {
if (s.op2)
e = new(arena) TernaryExprNode(s.pos, s.kind, s.op1, s.op2, e);
else {
if (!t.hasKind(Token::colon))
syntaxError("':' expected", 0);
lexer.skip();
subexpressionStack.advance_back();
s.op2 = e;
goto foundColon;
}
} else
e = new(arena) BinaryExprNode(s.pos, s.kind, s.op1, e);
}
if (kind == ExprNode::none)
break;
// Push the current operator onto the subexpressionStack.
lexer.skip();
{
StackedSubexpression &s = *subexpressionStack.reserve_advance_back();
bool superLeft = binOpInfo.superLeft;
if (e->hasKind(ExprNode::superExpr) && !superLeft)
syntaxError("super expression not allowed here", 1);
s.kind = kind;
s.precedence = binOpInfo.precedenceRight;
s.superRight = superLeft || s.kind == ExprNode::In;
s.pos = t.getPos();
s.op1 = e;
s.op2 = 0;
}
foundColon:
e = parseUnaryExpression(ssExpr);
}
ASSERT(subexpressionStack.size() == 1);
if (e->hasKind(ExprNode::superExpr))
if (allowSuperStmt && checked_cast<SuperExprNode *>(e)->op) {
// Convert the superExpr into a superStmt.
ExprPairList *arg = new(arena) ExprPairList(0, checked_cast<SuperExprNode *>(e)->op);
e = new(arena) InvokeExprNode(e->pos, ExprNode::superStmt, 0, arg);
} else
syntaxError("super expression not allowed here", 0);
return e;
}
// Parse and return an Attribute. The first token has already been read into t.
//
// After parseAttribute finishes, the next token might have been peeked with preferRegExp set to true.
JS::ExprNode *JS::Parser::parseAttribute(const Token &t)
{
bool b;
switch (t.getKind()) {
case Token::True:
b = true;
goto makeBooleanNode;
case Token::False:
b = false;
makeBooleanNode:
return new(arena) BooleanExprNode(t.getPos(), b);
case Token::Public:
case Token::Private:
if (lexer.peek(true).hasKind(Token::doubleColon))
break;
case CASE_TOKEN_NONEXPRESSION_ATTRIBUTE:
return new(arena) IdentifierExprNode(t);
case CASE_TOKEN_NONRESERVED:
break;
default:
syntaxError("Attribute expected");
}
return parsePostfixOperator(parseQualifiedIdentifier(t, true), false, true);
}
// One attribute has already been read and is provided in the attribute parameter. Parse
// other attributes, if any, juxtaposed with the first one, and return the resulting juxtapose
// expression. pos is the position of the first attribute.
// If the next token was peeked, it should be have been done with preferRegExp set to true.
// After parseAttributes finishes, the next token might have been peeked with preferRegExp set to true.
JS::ExprNode *JS::Parser::parseAttributes(size_t pos, ExprNode *attribute)
{
ASSERT(attribute);
while (true) {
const Token &t = lexer.peek(true);
if (lineBreakBefore(t) || !t.getFlag(Token::isAttribute))
return attribute;
lexer.skip();
attribute = new(arena) BinaryExprNode(pos, ExprNode::juxtapose, attribute, parseAttribute(t));
}
}
// e is a parsed ListExpression or SuperStatement. Return true if e is also an Attribute.
bool JS::Parser::expressionIsAttribute(const ExprNode *e)
{
while (true)
switch (e->getKind()) {
case ExprNode::identifier:
case ExprNode::boolean:
return true;
case ExprNode::qualify:
return checked_cast<const QualifyExprNode *>(e)->qualifier->hasKind(ExprNode::identifier);
case ExprNode::call:
case ExprNode::index:
e = checked_cast<const InvokeExprNode *>(e)->op;
break;
case ExprNode::dot:
case ExprNode::dotParen:
e = checked_cast<const BinaryExprNode *>(e)->op1;
break;
case ExprNode::dotClass:
e = checked_cast<const UnaryExprNode *>(e)->op;
break;
default:
return false;
}
}
// Parse an opening parenthesis, a ListExpression, and a closing parenthesis.
// Return the ListExpression. If the first token was peeked, it should be have
// been done with preferRegExp set to true.
JS::ExprNode *JS::Parser::parseParenthesizedListExpression()
{
require(true, Token::openParenthesis);
ExprNode *e = parseListExpression(false);
require(false, Token::closeParenthesis);
return e;
}
// Parse an opening parenthesis, a list of one or more comma-separated AssignmentExpressions,
// and a closing parenthesis. Return the list of expressions. If optional is true, return
// nil without parsing anything if there is no opening parenthesis.
//
// If the first token was peeked, it should be have been done with preferRegExp set to true.
// If there was no opening parenthesis, after parseParenthesizedExpressionList returns the next
// token might have been peeked with preferRegExp set to true.
JS::ExprList *JS::Parser::parseParenthesizedExpressionList(bool optional)
{
if (optional) {
if (!lexer.eat(true, Token::openParenthesis))
return 0;
} else
require(true, Token::openParenthesis);
NodeQueue<ExprList> expressions;
do expressions += new(arena) ExprList(parseAssignmentExpression(false));
while (lexer.eat(false, Token::comma));
require(false, Token::closeParenthesis);
return expressions.first;
}
// Parse and return a TypeExpression. If noIn is false, allow the in operator.
//
// If the first token was peeked, it should be have been done with preferRegExp set to true.
// After parseTypeExpression finishes, the next token might have been peeked with preferRegExp set to true.
JS::ExprNode *JS::Parser::parseTypeExpression(bool noIn)
{
ExprNode *type = parseNonAssignmentExpression(noIn);
if (lexer.peek(false).hasKind(Token::divideEquals))
syntaxError("'/=' not allowed here", 0);
lexer.redesignate(true); // Safe: a '/' would have been interpreted as an operator, so it can't be the next
// token; a '/=' was outlawed by the check above.
return type;
}
// If the next token has the given kind, eat it and parse and return the
// following TypeExpression; otherwise return nil.
// If noIn is false, allow the in operator.
//
// If the first token was peeked, it should be have been done with preferRegExp set to true.
// After parseTypeBinding finishes, the next token might have been peeked with preferRegExp set to true.
JS::ExprNode *JS::Parser::parseTypeBinding(Token::Kind kind, bool noIn)
{
ExprNode *type = 0;
if (lexer.eat(true, kind))
type = parseTypeExpression(noIn);
return type;
}
// Return true the next token is a '::' without reading the next token.
// If the next token is a '::', preferRegExp will be set to false for it;
// otherwise, preferRegExp will be set to true for the next token.
bool JS::Parser::doubleColonFollows()
{
bool result = lexer.peek(true).hasKind(Token::doubleColon);
if (result)
lexer.redesignate(false); // Safe: the next token is '::'.
return result;
}
// Parse and return a VariableBinding (UntypedVariableBinding if untyped is true).
// pos is the position of the binding or its first attribute, if any.
// If noIn is false, allow the in operator.
// The value of the constant parameter is stored in the returned VariableBinding.
//
// If the first token was peeked, it should be have been done with preferRegExp set to true.
// After parseVariableBinding finishes, the next token might have been peeked with preferRegExp set to true.
// The reason preferRegExp is true is to correctly parse the following case of semicolon insertion:
// var a
// /regexp/
JS::VariableBinding *JS::Parser::parseVariableBinding(size_t pos, bool noIn, bool untyped, bool constant)
{
const StringAtom &name = parseIdentifier();
ExprNode *type = 0;
if (lexer.eat(true, Token::colon))
if (untyped)
syntaxError("Type annotation not allowed in a var inside a substatement; enclose the var in a block");
else
type = parseTypeExpression(noIn);
ExprNode *initializer = 0;
if (lexer.eat(true, Token::assignment)) {
const Token &t = lexer.get(true);
size_t tPos = t.getPos();
if (!untyped && (t.getFlag(Token::isNonExpressionAttribute) || t.hasKind(Token::Private) && !doubleColonFollows())) {
initializer = new(arena) IdentifierExprNode(t);
makeAttribute:
initializer = parseAttributes(tPos, initializer);
} else {
lexer.unget();
initializer = parseAssignmentExpression(noIn);
lexer.redesignate(true); // Safe: a '/' or a '/=' would have been interpreted as an operator,
// so it can't be the next token.
if (!untyped && expressionIsAttribute(initializer)) {
const Token &t2 = lexer.peek(true);
if (!lineBreakBefore(t2) && t2.getFlag(Token::canFollowAttribute))
goto makeAttribute;
}
}
}
return new(arena) VariableBinding(pos, &name, type, initializer, constant);
}
// Parse and return a VariableBinding for a function parameter. The parameter may optionally be
// preceded by the const attribute.
//
// If the first token was peeked, it should be have been done with preferRegExp set to true.
// After parseParameter finishes, the next token might have been peeked with preferRegExp set to true.
JS::VariableBinding *JS::Parser::parseParameter()
{
const Token &t = lexer.peek(true);
size_t pos = t.getPos();
bool constant = false;
if (t.hasKind(Token::Const)) {
lexer.skip();
constant = true;
}
return parseVariableBinding(pos, false, false, constant);
}
// Parse a FunctionName and initialize fn with the result.
//
// If the first token was peeked, it should be have been done with preferRegExp set to true.
// After parseFunctionName finishes, the next token might have been peeked with preferRegExp set to true.
void JS::Parser::parseFunctionName(FunctionName &fn)
{
const Token *t = &lexer.get(true);
FunctionName::Prefix prefix = FunctionName::normal;
const StringAtom *name;
if (t->hasKind(Token::string)) {
prefix = FunctionName::op;
name = &getWorld().identifiers[t->getChars()];
} else {
if (t->hasKind(Token::Get) || t->hasKind(Token::Set)) {
const Token *t2 = &lexer.peek(true);
if (!lineBreakBefore(*t2) && t2->getFlag(Token::isNonreserved)) {
prefix = t->hasKind(Token::Get) ? FunctionName::Get : FunctionName::Set;
t = &lexer.get(true);
}
}
name = &ensureIdentifier(*t);
}
fn.prefix = prefix;
fn.name = name;
}
// Parse a FunctionSignature and initialize fd with the result.
//
// If the first token was peeked, it should be have been done with preferRegExp set to true.
// After parseFunctionSignature finishes, the next token might have been
// peeked with preferRegExp set to true.
void JS::Parser::parseFunctionSignature(FunctionDefinition &fd)
{
require(true, Token::openParenthesis);
NodeQueue<VariableBinding> parameters;
VariableBinding *optParameters = 0;
VariableBinding *restParameter = 0;
if (!lexer.eat(true, Token::closeParenthesis)) {
while (true) {
if (lexer.eat(true, Token::tripleDot)) {
const Token &t1 = lexer.peek(true);
if (t1.hasKind(Token::closeParenthesis))
restParameter = new(arena) VariableBinding(t1.getPos(), 0, 0, 0, false);
else
restParameter = parseParameter();
if (!optParameters)
optParameters = restParameter;
parameters += restParameter;
require(true, Token::closeParenthesis);
break;
} else {
VariableBinding *b = parseParameter();
if (b->initializer) {
if (!optParameters)
optParameters = b;
} else
if (optParameters)
syntaxError("'=' expected", 0);
parameters += b;
const Token &t = lexer.get(true);
if (!t.hasKind(Token::comma))
if (t.hasKind(Token::closeParenthesis))
break;
else
syntaxError("',' or ')' expected");
}
}
}
fd.parameters = parameters.first;
fd.optParameters = optParameters;
fd.restParameter = restParameter;
fd.resultType = parseTypeBinding(Token::colon, false);
}
// Parse and return an ExportBinding.
//
// If the first token was peeked, it should be have been done with preferRegExp set to true.
// After parseExportBinding finishes, the next token might have been peeked with preferRegExp set to true.
JS::ExportBinding *JS::Parser::parseExportBinding()
{
ExportBinding *binding = new(arena) ExportBinding(lexer.peek(true).getPos());
parseFunctionName(binding->name);
if (lexer.eat(true, Token::assignment))
parseFunctionName(binding->initializer);
else
binding->initializer = binding->name;
return binding;
}
// Parse a list of Directives ending with a '}'. Return these directives as a linked list
// threaded through the StmtNodes' next fields. The opening '{' has already been read.
// If inSwitch is true, only allow case <expr>: and default: statements or Substatements.
//
// The list of Directives forms a scope for the purpose of pragma processing. The pragma
// settings are restored to their current values after the '}'.
JS::StmtNode *JS::Parser::parseBlockContents(bool inSwitch)
{
NodeQueue<StmtNode> q;
SaveRestore<Pragma::Flags> savedFlags(flags);
if (inSwitch) {
const Token &t = lexer.peek(true);
if (!(t.hasKind(Token::Case) || t.hasKind(Token::Default)))
syntaxError("First statement in a switch block must be 'case expr:' or 'default:'", 0);
}
while (!lexer.peek(true).hasKind(Token::closeBrace))
q += parseFullDirective(inSwitch);
lexer.skip();
return q.first;
}
// Parse an optional block of Directives beginning with a '{' and ending with a '}'.
// Return these directives as a BlockStmtNode.
// If semicolonWanted is nil, the block is required; otherwise, the block is
// optional and if it is omitted, *semicolonWanted is set to true.
//
// If the first token was peeked, it should be have been done with preferRegExp set to true.
// If semicolonWanted isn't nil, after parseBody finishes the next token might have been peeked
// with preferRegExp set to true.
JS::BlockStmtNode *JS::Parser::parseBody(bool *semicolonWanted)
{
const Token *tBrace = lexer.eat(true, Token::openBrace);
if (tBrace) {
size_t pos = tBrace->getPos();
return new(arena) BlockStmtNode(pos, StmtNode::block, 0, parseBlockContents(false));
} else {
if (!semicolonWanted)
syntaxError("'{' expected", 0);
*semicolonWanted = true;
return 0;
}
}
// Parse an IncludesExcludes, which consists of a comma followed by either 'include' or 'exclude'
// and a parenthesized list of zero or more identifiers. If there is no initial comma, assume
// that ",exclude()" was read. If there is an initial comma not followed by the rest of
// a non-empty IncludesExcludes production expansion, then signal a syntax error; the message
// depends on whether s.namespaces is nil or not.
//
// If there is no syntax error, the includeExclude and exclude members of s are initialized with
// the results of the parse.
//
// If the first or second token was peeked, it should be have been done with preferRegExp set to true.
// After parseIncludesExcludes finishes, the next token might have been peeked with preferRegExp set to true.
void JS::Parser::parseIncludeExclude(UseStmtNode &s)
{
IdentifierList *includeExclude = 0;
bool exclude = true;
if (lexer.eat(true, Token::comma)) {
const Token &t = lexer.get(true);
if (t.hasKind(Token::Include))
exclude = false;
else if (!t.hasKind(Token::Exclude))
syntaxError(s.namespaces ? "'include' or 'exclude' expected" : "'namespace', 'include', or 'exclude' expected");
require(true, Token::openParenthesis);
if (!lexer.eat(true, Token::closeParenthesis)) {
includeExclude = parseIdentifierList(Token::comma);
require(true, Token::closeParenthesis);
}
}
s.includeExclude = includeExclude;
s.exclude = exclude;
}
// Parse and return an import Directive that takes zero or more initial attributes. Do not read
// the final Semicolon. The initial attributes and the import keyword have already been parsed.
// If there were no attributes, the attributes parameter is nil.
//
// pos is the position of the beginning of the directive (its first attribute if it has attributes).
// After parseImport finishes, the next token might have been peeked with preferRegExp set to true.
JS::ImportStmtNode *JS::Parser::parseImport(size_t pos, ExprNode *attributes)
{
const StringAtom *varName = 0;
IdentifierList *packageIdList = 0;
String *packageString = 0;
ExprList *namespaces = 0;
const Token *t = &lexer.get(true);
if (t->hasIdentifierKind() && lexer.eat(true, Token::assignment)) {
varName = &t->getIdentifier();
t = &lexer.get(true);
}
if (t->hasKind(Token::string))
packageString = &copyTokenChars(*t);
else {
if (!t->hasIdentifierKind())
syntaxError("Package name or string expected");
lexer.unget();
packageIdList = parseIdentifierList(Token::dot);
}
if (lexer.eat(true, Token::comma))
if (lexer.eat(true, Token::Namespace))
namespaces = parseParenthesizedExpressionList(false);
else
lexer.unget();
ImportStmtNode *s = new(arena) ImportStmtNode(pos, attributes, varName, packageIdList, packageString, namespaces);
parseIncludeExclude(*s);
return s;
}
// Parse and return a UseDirective that takes zero or more initial attributes. Do not read the final
// Semicolon. The initial attributes and the 'use' and 'namespace' keywords have already been parsed.
// If there were no attributes, the attributes parameter is nil.
//
// pos is the position of the beginning of the directive (its first attribute if it has attributes).
// After parseUseDirective finishes, the next token might have been peeked with preferRegExp set to true.
JS::UseStmtNode *JS::Parser::parseUseDirective(size_t pos, ExprNode *attributes)
{
ExprList *namespaces = parseParenthesizedExpressionList(false);
UseStmtNode *s = new(arena) UseStmtNode(pos, StmtNode::Use, attributes, namespaces);
parseIncludeExclude(*s);
return s;
}
// A Directive that ends with an optional Semicolon production has just been parsed. Look for and
// read the closing semicolon; if one is absent, ensure that the next (unread) token is '}', the end
// of input, or any token prefixed by a line break in non-strict mode.
void JS::Parser::parseClosingSemicolon()
{
const Token &t = lexer.peek(true);
if (t.hasKind(Token::semicolon))
lexer.skip();
else if (!(t.hasKind(Token::closeBrace) || t.hasKind(Token::end) || lineBreakBefore(t)))
syntaxError("';' expected", 0);
}
// arguments is a lined list of arguments passed to a pragma. If no argument is supplied,
// return true and leave value unchanged. If more than one argument is supplied or the first
// argument isn't a boolean, signal an error unless ignoreErrors is true and return false.
// Otherwise, set value to the first argument's value and return true.
bool JS::Parser::parseBooleanPragma(const ExprList *arguments, bool &value, bool ignoreErrors)
{
if (!arguments)
return true;
ExprNode *expr = arguments->expr;
if (arguments->next || !expr->hasKind(ExprNode::boolean)) {
if (!ignoreErrors)
syntaxError("Bad pragma arguments", 0);
return false;
}
value = checked_cast<const BooleanExprNode *>(expr)->value;
return true;
}
// arguments is a lined list of arguments passed to a pragma. If no argument is supplied,
// return true and leave value unchanged. If more than one argument is supplied or the first
// argument isn't a number, signal an error unless ignoreErrors is true and return false.
// Otherwise, set value to the first argument's value and return true.
bool JS::Parser::parseNumericPragma(const ExprList *arguments, float64 &value, bool ignoreErrors)
{
if (!arguments)
return true;
ExprNode *expr = arguments->expr;
if (arguments->next || !expr->hasKind(ExprNode::number)) {
if (!ignoreErrors)
syntaxError("Bad pragma arguments", 0);
return false;
}
value = checked_cast<const NumberExprNode *>(expr)->value;
return true;
}
// Parse and return a Pragma or UseDirective. Any initial attributes and the 'use' token has already been read;
// pos is the position of the beginning of the directive (its first attribute if it has attributes).
//
// If the Directive ends with an optional Semicolon production, parseUse may or may not read a
// semicolon if one is present. If it does not read a semicolon, parseUse will set semicolonWanted
// to indicate to the outer parser that either a semicolon, a line break (in non-strict mode), or some other
// closer such as a '}' or end of input is needed to end the directive. semicolonWanted should be false on entry.
//
// After parseUse finishes, the next token might have been peeked with preferRegExp set to true.
JS::StmtNode *JS::Parser::parseUse(size_t pos, ExprNode *attributes, bool &semicolonWanted)
{
ASSERT(!semicolonWanted);
if (lexer.eat(true, Token::Namespace)) {
semicolonWanted = true;
return parseUseDirective(pos, attributes);
}
if (attributes)
syntaxError("Attributes are not allowed on a 'use' pragma", 0);
// Don't modify flags in place so that they are unchanged if an error occurs before completion
// of this directive.
Pragma::Flags newFlags = flags;
while (true) {
const Token &t = lexer.get(true);
ensureIdentifier(t);
Token::Kind tKind = t.getKind();
ExprList *arguments = parseParenthesizedExpressionList(true);
bool ignoreErrors = lexer.eat(true, Token::question) != 0;
float64 version;
switch (tKind) {
case Token::Ecmascript:
version = 4.0;
if (parseNumericPragma(arguments, version, ignoreErrors))
if (version == 4.0)
newFlags = Pragma::setLanguage(newFlags, Pragma::es4);
else if (version == 1.0 || version == 2.0 || version == 3.0)
newFlags = Pragma::setLanguage(newFlags, Pragma::js1);
else if (!ignoreErrors)
syntaxError("Unknown ecmascript version", 0);
break;
case Token::Javascript:
version = 2.0;
if (parseNumericPragma(arguments, version, ignoreErrors))
if (version == 2.0)
newFlags = Pragma::setLanguage(newFlags, Pragma::js2);
else if (version == 1.2)
newFlags = Pragma::setLanguage(newFlags, Pragma::js12);
else if (version == 1.0 || version == 1.1 || version == 1.3 || version == 1.4 || version == 1.5)
newFlags = Pragma::setLanguage(newFlags, Pragma::js1);
else if (!ignoreErrors)
syntaxError("Unknown javascript version", 0);
break;
case Token::Strict:
{
bool strict = true;
if (parseBooleanPragma(arguments, strict, ignoreErrors))
if (strict)
newFlags = setFlag(newFlags, Pragma::strict);
else
newFlags = clearFlag(newFlags, Pragma::strict);
}
break;
default:
if (!ignoreErrors)
syntaxError("Unknown 'use' pragma", 0);
}
if (!lexer.eat(true, Token::comma))
break;
}
// Call parseClosingSemicolon() now before the new flags take effect.
parseClosingSemicolon();
StmtNode *s = new(arena) PragmaStmtNode(pos, newFlags);
flags = newFlags;
return s;
}
// Parse and return a Directive that takes zero or more initial attributes. The Directive is either
// an AnnotatableDirective or an AnnotatedBlock. The attributes have already been parsed.
// If there were no attributes, the attributes parameter is nil.
// If noIn is false, allow the in operator.
// If untyped is true, do not allow types on variables declared in const or var directives.
//
// If the directive ends with an optional Semicolon production, then that semicolon is not parsed.
// Instead, parseAnnotatableDirective returns true in semicolonWanted when either a semicolon, a line break
// (in non-strict mode), or some other closer such as a '}', 'else', 'while' of a do-while, or end of input
// is needed to end the directive. semicolonWanted should be false on entry.
//
// pos is the position of the beginning of the directive (its first attribute if it has attributes).
// The first token of the directive has already been read and is provided in t.
// After parseAnnotatableDirective finishes, the next token might have been peeked with preferRegExp set to true.
JS::StmtNode *JS::Parser::parseAnnotatableDirective(size_t pos, ExprNode *attributes, const Token &t, bool noIn,
bool untyped, bool &semicolonWanted)
{
StmtNode *s;
ASSERT(!semicolonWanted);
StmtNode::Kind sKind;
if (attributes && lineBreakBefore(t))
syntaxError("Line break not allowed here");
switch (t.getKind()) {
case Token::openBrace:
s = new(arena) BlockStmtNode(pos, StmtNode::block, attributes, parseBlockContents(false));
break;
case Token::Export:
{
NodeQueue<ExportBinding> bindings;
do bindings += parseExportBinding();
while (lexer.eat(true, Token::comma));
s = new(arena) ExportStmtNode(pos, attributes, bindings.first);
}
goto insertableSemicolon;
case Token::Const:
sKind = StmtNode::Const;
goto constOrVar;
case Token::Var:
sKind = StmtNode::Var;
constOrVar:
{
NodeQueue<VariableBinding> bindings;
do bindings += parseVariableBinding(lexer.peek(true).getPos(), noIn, untyped, sKind == StmtNode::Const);
while (lexer.eat(true, Token::comma));
s = new(arena) VariableStmtNode(pos, sKind, attributes, bindings.first);
}
insertableSemicolon:
semicolonWanted = true;
break;
case Token::Function:
{
FunctionStmtNode *f = new(arena) FunctionStmtNode(pos, StmtNode::Function, attributes);
parseFunctionName(f->function);
parseFunctionSignature(f->function);
f->function.body = parseBody(&semicolonWanted);
s = f;
}
break;
case Token::Class:
{
const StringAtom &name = parseIdentifier();
ExprNode *superclass = parseTypeBinding(Token::Extends, false);
BlockStmtNode *body = parseBody(superclass ? 0 : &semicolonWanted);
s = new(arena) ClassStmtNode(pos, attributes, name, superclass, body);
}
break;
case Token::Namespace:
s = new(arena) NamespaceStmtNode(pos, StmtNode::Namespace, attributes, parseIdentifier());
goto insertableSemicolon;
case Token::Import:
s = parseImport(pos, attributes);
goto insertableSemicolon;
default:
syntaxError("Bad annotated directive");
// syntaxError cannot return, but the compiler doesn't know that.
// The break was omitted here to avoid compiler complaints about s being uninitialized.
case Token::Use:
s = parseUse(pos, attributes, semicolonWanted);
break;
}
return s;
}
// Parse and return a ForStatement. The 'for' token has already been read; its position is pos.
// If the statement ends with an optional semicolon, that semicolon might not be parsed.
// Instead, parseFor returns a semicolonWanted with the same meaning as that in parseDirective.
// semicolonWanted should be false on entry.
//
// After parseFor finishes, the next token might have been peeked with preferRegExp set to true.
JS::ForStmtNode *JS::Parser::parseFor(size_t pos, bool &semicolonWanted)
{
require(true, Token::openParenthesis);
const Token &t = lexer.get(true);
size_t tPos = t.getPos();
StmtNode *initializer = 0;
ExprNode *expr1 = 0;
ExprNode *expr2 = 0;
ExprNode *expr3 = 0;
StmtNode::Kind sKind = StmtNode::For;
ASSERT(!semicolonWanted);
switch (t.getKind()) {
case Token::semicolon:
goto threeExpr;
case Token::Const:
case Token::Var:
initializer = parseAnnotatableDirective(tPos, 0, t, true, false, semicolonWanted);
break;
case Token::Private:
if (doubleColonFollows())
goto nonAttributePrivate;
// Falls through
case CASE_TOKEN_NONEXPRESSION_ATTRIBUTE:
// Token::Public, Token::True, Token::False, and other attributes are
// handled by the default case below.
expr1 = new(arena) IdentifierExprNode(t);
makeAttribute:
{
expr1 = parseAttributes(tPos, expr1);
const Token &t2 = lexer.get(true);
if (!t2.hasKind(Token::Const) && !t2.hasKind(Token::Var))
syntaxError("'const' or 'var' expected");
initializer = parseAnnotatableDirective(tPos, expr1, t2, true, false, semicolonWanted);
expr1 = 0;
}
break;
default:
nonAttributePrivate:
lexer.unget();
expr1 = parseListExpression(true);
lexer.redesignate(true); // Safe: a '/' or a '/=' would have been interpreted as an operator,
// so it can't be the next token.
if (expressionIsAttribute(expr1)) {
const Token &t2 = lexer.peek(true);
if (!lineBreakBefore(t2) && t2.getFlag(Token::canFollowAttribute))
goto makeAttribute;
}
initializer = new(arena) ExprStmtNode(tPos, StmtNode::expression, expr1);
break;
}
if (lexer.eat(true, Token::semicolon))
threeExpr: {
if (!lexer.eat(true, Token::semicolon)) {
expr2 = parseListExpression(false);
require(false, Token::semicolon);
}
if (lexer.peek(true).hasKind(Token::closeParenthesis))
lexer.redesignate(false); // Safe: the token is ')'.
else
expr3 = parseListExpression(false);
}
else if (lexer.eat(true, Token::In)) {
sKind = StmtNode::ForIn;
if (expr1) {
ASSERT(initializer->hasKind(StmtNode::expression));
ensurePostfix(expr1);
} else {
ASSERT(initializer->hasKind(StmtNode::Const) || initializer->hasKind(StmtNode::Var));
const VariableBinding *bindings = checked_cast<VariableStmtNode *>(initializer)->bindings;
if (!bindings || bindings->next)
syntaxError("Only one variable binding can be used in a for-in statement", 1);
}
expr2 = parseListExpression(false);
}
else
syntaxError("';' or 'in' expected", 0);
require(false, Token::closeParenthesis);
return new(arena) ForStmtNode(pos, sKind, initializer, expr2, expr3, parseDirective(true, false, semicolonWanted));
}
// Parse and return a TryStatement. The 'try' token has already been read; its position is pos.
// After parseTry finishes, the next token might have been peeked with preferRegExp set to true.
JS::TryStmtNode *JS::Parser::parseTry(size_t pos)
{
StmtNode *tryBlock = parseBody(0);
NodeQueue<CatchClause> catches;
const Token *t;
while ((t = lexer.eat(true, Token::Catch)) != 0) {
size_t catchPos = t->getPos();
require(true, Token::openParenthesis);
bool constant = lexer.eat(true, Token::Const) != 0;
const StringAtom &name = parseIdentifier();
ExprNode *type = parseTypeBinding(Token::colon, true);
require(true, Token::closeParenthesis);
catches += new(arena) CatchClause(catchPos, name, type, constant, parseBody(0));
}
StmtNode *finally = 0;
if (lexer.eat(true, Token::Finally))
finally = parseBody(0);
else if (!catches.first)
syntaxError("A try statement must be followed by at least one catch or finally", 0);
return new(arena) TryStmtNode(pos, tryBlock, catches.first, finally);
}
// Parse and return a PackageDefinition. The 'package' token has already been read; its position is pos.
// After parsePackage finishes, the next token might have been peeked with preferRegExp set to true.
JS::PackageStmtNode *JS::Parser::parsePackage(size_t pos)
{
IdentifierList *packageIdList = 0;
const Token &t = lexer.peek(true);
if (t.hasIdentifierKind())
packageIdList = parseIdentifierList(Token::dot);
else if (!t.hasKind(Token::openBrace))
syntaxError("Package name or '{' expected", 0);
return new(arena) PackageStmtNode(pos, packageIdList, parseBody(0));
}
// Parse and return a Directive (if substatement is false) or a Substatement (if substatement is true).
// If inSwitch is true, allow case <expr>: and default: statements.
//
// If the Directive ends with an optional Semicolon production, parseDirective may or may not read a
// semicolon if one is present. If it does not read a semicolon, parseDirective will set semicolonWanted
// to indicate to the outer parser that either a semicolon, a line break (in non-strict mode), or some other
// closer such as a '}', 'else', 'while' of a do-while, or end of input is needed to end the directive.
// In all other cases parseDirective will clear semicolonWanted.
//
// If the first token was peeked, it should be have been done with preferRegExp set to true.
// After parseDirective finishes, the next token might have been peeked with preferRegExp set to true.
JS::StmtNode *JS::Parser::parseDirective(bool substatement, bool inSwitch, bool &semicolonWanted)
{
StmtNode *s;
ExprNode *e = 0;
StmtNode::Kind sKind;
const Token &t = lexer.get(true);
const Token *t2;
size_t pos = t.getPos();
semicolonWanted = false;
checkStackSize();
switch (t.getKind()) {
case Token::semicolon:
s = new(arena) StmtNode(pos, StmtNode::empty);
break;
case Token::If:
e = parseParenthesizedListExpression();
s = parseSubstatement(semicolonWanted);
if (lexer.eat(true, Token::Else))
s = new(arena) BinaryStmtNode(pos, StmtNode::IfElse, e, s, parseDirective(true, false, semicolonWanted));
else {
sKind = StmtNode::If;
goto makeUnary;
}
break;
case Token::Switch:
e = parseParenthesizedListExpression();
require(true, Token::openBrace);
s = new(arena) SwitchStmtNode(pos, e, parseBlockContents(true));
break;
case Token::Case:
if (!inSwitch)
goto notInSwitch;
e = parseListExpression(false);
makeSwitchCase:
require(false, Token::colon);
s = new(arena) ExprStmtNode(pos, StmtNode::Case, e);
break;
case Token::Default:
if (inSwitch)
goto makeSwitchCase;
notInSwitch:
syntaxError("case and default may only be used inside a switch statement");
break;
case Token::Do:
{
bool semicolonWanted2; // Ignore semicolonWanted2.
s = parseSubstatement(semicolonWanted2);
require(true, Token::While);
e = parseParenthesizedListExpression();
sKind = StmtNode::DoWhile;
goto makeUnary;
}
break;
case Token::With:
sKind = StmtNode::With;
goto makeWhileWith;
case Token::While:
sKind = StmtNode::While;
makeWhileWith:
e = parseParenthesizedListExpression();
s = parseDirective(true, false, semicolonWanted);
makeUnary:
s = new(arena) UnaryStmtNode(pos, sKind, e, s);
break;
case Token::For:
s = parseFor(pos, semicolonWanted);
break;
case Token::Continue:
sKind = StmtNode::Continue;
goto makeGo;
case Token::Break:
sKind = StmtNode::Break;
makeGo:
{
const StringAtom *label = 0;
t2 = &lexer.peek(true);
if (t2->hasKind(Token::identifier) && !lineBreakBefore(*t2)) {
lexer.skip();
label = &t2->getIdentifier();
}
s = new(arena) GoStmtNode(pos, sKind, label);
}
goto insertableSemicolon;
case Token::Return:
sKind = StmtNode::Return;
t2 = &lexer.peek(true);
if (lineBreakBefore(*t2) || t2->getFlag(Token::canFollowReturn))
goto makeExprStmtNode;
makeExpressionNode:
e = parseListExpression(false);
// Safe: a '/' or a '/=' would have been interpreted as an
// operator, so it can't be the next token.
lexer.redesignate(true);
goto makeExprStmtNode;
case Token::Throw:
sKind = StmtNode::Throw;
if (lineBreakBefore())
syntaxError("throw cannot be followed by a line break", 0);
goto makeExpressionNode;
case Token::Try:
s = parseTry(pos);
break;
case Token::Package:
if (substatement)
goto badSubstatement;
s = parsePackage(pos);
break;
case Token::Private:
if (doubleColonFollows())
goto nonAttributePrivate;
// Falls through
case CASE_TOKEN_NONEXPRESSION_ATTRIBUTE:
e = new(arena) IdentifierExprNode(t);
makeAttribute:
e = parseAttributes(pos, e);
t2 = &lexer.get(true);
goto annotatableDirective;
case Token::openBrace:
case Token::Import:
case Token::Export:
case Token::Const:
case Token::Function:
case Token::Class:
case Token::Namespace:
case Token::Var:
case Token::Use:
t2 = &t;
annotatableDirective:
if (substatement && !t2->hasKind(Token::openBrace) && !t2->hasKind(Token::Var)) {
badSubstatement:
syntaxError("This directive must be at the top level of either the program or a block");
}
s = parseAnnotatableDirective(pos, e, *t2, false, substatement, semicolonWanted);
break;
case Token::Include:
t2 = &lexer.peek(false);
if (!t2->hasKind(Token::string))
goto nonreservedToken;
lexer.skip();
s = new(arena) IncludeStmtNode(pos, copyTokenChars(*t2));
goto insertableSemicolon;
case CASE_TOKEN_NONRESERVED_NONINCLUDE:
t2 = &lexer.peek(false);
nonreservedToken:
if (t2->hasKind(Token::colon)) {
lexer.skip();
// Must do this now because parseDirective can invalidate t.
const StringAtom &name = t.getIdentifier();
s = new(arena) LabelStmtNode(pos, name, parseDirective(true, false, semicolonWanted));
break;
}
// Falls through
case Token::number:
case Token::string:
case Token::regExp:
case Token::openParenthesis:
case Token::openBracket:
case Token::increment:
case Token::decrement:
case Token::complement:
case Token::logicalNot:
case Token::plus:
case Token::minus:
case Token::Delete:
case Token::False:
case Token::New:
case Token::Null:
case Token::Public:
case Token::Super:
case Token::This:
case Token::True:
case Token::Typeof:
case Token::Void:
nonAttributePrivate:
lexer.unget();
e = parseGeneralExpression(true, false, false, false);
// Safe: a '/' or a '/=' would have been interpreted as an operator, so it can't be the next token.
lexer.redesignate(true);
if (expressionIsAttribute(e)) {
t2 = &lexer.peek(true);
if (!lineBreakBefore(*t2) && t2->getFlag(Token::canFollowAttribute))
goto makeAttribute;
}
sKind = StmtNode::expression;
makeExprStmtNode:
s = new(arena) ExprStmtNode(pos, sKind, e);
insertableSemicolon:
semicolonWanted = true;
break;
default:
syntaxError(substatement ? "Statement expected" : "Statement or definition expected");
// syntaxError cannot return, but the compiler doesn't know that.
// The break was omitted here to avoid compiler complaints about s being uninitialized.
case Token::Debugger:
s = new(arena) DebuggerStmtNode(pos, StmtNode::Debugger);
break;
}
return s;
}
// Parse and return a Substatement. If the substatement ends with an optional semicolon,
// that semicolon *is* parsed. parseSubstatement returns true in semicolonWanted only if the
// a Semicolon is needed to close the substatement but one wasn't found. In all other cases
// parseSubstatement will clear semicolonWanted.
//
// If the first token was peeked, it should be have been done with preferRegExp set to true.
// After parseSubstatement finishes, the next token might have been peeked with preferRegExp set to true.
JS::StmtNode *JS::Parser::parseSubstatement(bool &semicolonWanted)
{
StmtNode *s = parseDirective(true, false, semicolonWanted);
if (semicolonWanted && lexer.eat(true, Token::semicolon))
semicolonWanted = false;
return s;
}
// Parse and return a Directive (if inSwitch is false) or a Substatement (if inSwitch is true).
// If inSwitch is true, allow case <expr>: and default: statements.
//
// If the parsed Directive ends with an optional Semicolon production, a semicolon is required unless
// the next (unread) token is '}', the end of input, or any token prefixed by a line break in non-strict mode.
//
// If the first token was peeked, it should be have been done with preferRegExp set to true.
// After parseFullDirective finishes, the next token might have been peeked with preferRegExp set to true.
JS::StmtNode *JS::Parser::parseFullDirective(bool inSwitch)
{
bool semicolonWanted;
StmtNode *s = parseDirective(inSwitch, inSwitch, semicolonWanted);
if (semicolonWanted)
parseClosingSemicolon();
return s;
}
// Parse a list of Directives ending with the end of input; the end-of-input token is not read.
// Return these directives as a linked list threaded through the StmtNodes' next fields.
// After parseProgram finishes, the next token might have been peeked with preferRegExp set to true.
JS::StmtNode *JS::Parser::parseProgram()
{
NodeQueue<StmtNode> q;
while (!lexer.peek(true).hasKind(Token::end))
q += parseFullDirective(false);
return q.first;
}
//
// Parser Utilities
//
const bool debugExprNodePrint = true; // Print extra parentheses around subexpressions?
const int32 basicIndent = 4; // Size of one level of statement indentation
const int32 caseIndent = basicIndent/2; // Indentation before a case or default statement
const int32 varIndent = 2; // Indentation of var or const statement bindings
const int32 subexpressionIndent = 4; // Size of one level of expression indentation
const int32 functionHeaderIndent = 9; // Indentation of function signature
const int32 namespaceHeaderIndent = 4; // Indentation of class, interface, or namespace header
static const char functionPrefixNames[4][5] = {"", "get ", "set ", "op "};
// Print this onto f. name must be non-nil.
void JS::FunctionName::print(PrettyPrinter &f) const
{
if (prefix == FunctionName::op)
quoteString(f, *name, '"');
else {
f << functionPrefixNames[prefix];
f << *name;
}
}
// Print this onto f. if printConst is false, inhibit printing of the const keyword.
void JS::VariableBinding::print(PrettyPrinter &f, bool printConst) const
{
PrettyPrinter::Block b(f);
if (printConst && constant)
f << "const ";
if (name)
f << *name;
PrettyPrinter::Indent i(f, subexpressionIndent);
if (type) {
f.fillBreak(0);
f << ": ";
f << type;
}
if (initializer) {
f.linearBreak(1);
f << "= ";
f << initializer;
}
}
// Print this onto f. If attributes is null, this is a function expression;
// if attributes is non-null, this is a function statement with the given attributes.
// When there is no function body, print a trailing semicolon unless noSemi is true.
void JS::FunctionDefinition::print(PrettyPrinter &f, const AttributeStmtNode *attributes, bool noSemi) const
{
PrettyPrinter::Block b(f);
if (attributes)
attributes->printAttributes(f);
f << "function";
if (name) {
f << ' ';
FunctionName::print(f);
}
{
PrettyPrinter::Indent i(f, functionHeaderIndent);
f.fillBreak(0);
f << '(';
{
PrettyPrinter::Block b2(f);
const VariableBinding *p = parameters;
if (p)
while (true) {
if (p == restParameter) {
f << "...";
if (p->name)
f << ' ';
}
p->print(f, true);
p = p->next;
if (!p)
break;
f << ',';
f.fillBreak(1);
}
f << ')';
}
if (resultType) {
f.fillBreak(0);
f << ": ";
f << resultType;
}
}
if (body) {
bool loose = attributes != 0;
f.linearBreak(1, loose);
body->printBlock(f, loose);
} else
StmtNode::printSemi(f, noSemi);
}
//
// ExprNode
//
const char *const JS::ExprNode::kindNames[kindsEnd] = {
"NIL", // none
0, // identifier
"null", // Null
0, // boolean
0, // number
0, // string
0, // regExp
"this", // This
0, // parentheses
0, // numUnit
0, // exprUnit
0, // qualify
0, // objectLiteral
0, // arrayLiteral
0, // functionLiteral
0, // call
0, // New
0, // index
".", // dot
".class", // dotClass
".(", // dotParen
0, // superExpr
0, // superStmt
"delete ", // Delete
"void ", // Void
"typeof ", // Typeof
"++ ", // preIncrement
"-- ", // preDecrement
" ++", // postIncrement
" --", // postDecrement
"+ ", // plus
"- ", // minus
"~ ", // complement
"! ", // logicalNot
" ", // juxtapose
"+", // add
"-", // subtract
"*", // multiply
"/", // divide
"%", // modulo
"<<", // leftShift
">>", // rightShift
">>>", // logicalRightShift
"&", // bitwiseAnd
"^", // bitwiseXor
"|", // bitwiseOr
"&&", // logicalAnd
"^^", // logicalXor
"||", // logicalOr
"==", // equal
"!=", // notEqual
"<", // lessThan
"<=", // lessThanOrEqual
">", // greaterThan
">=", // greaterThanOrEqual
"===", // identical
"!==", // notIdentical
"as", // As
"in", // In
"instanceof", // Instanceof
"is", // Is
"=", // assignment
"+=", // addEquals
"-=", // subtractEquals
"*=", // multiplyEquals
"/=", // divideEquals
"%=", // moduloEquals
"<<=", // leftShiftEquals
">>=", // rightShiftEquals
">>>=", // logicalRightShiftEquals
"&=", // bitwiseAndEquals
"^=", // bitwiseXorEquals
"|=", // bitwiseOrEquals
"&&=", // logicalAndEquals
"^^=", // logicalXorEquals
"||=", // logicalOrEquals
"?", // conditional
"," // comma
};
// Print this onto f.
void JS::ExprNode::print(PrettyPrinter &f) const
{
f << kindName(kind);
}
void JS::IdentifierExprNode::print(PrettyPrinter &f) const
{
f << name;
}
void JS::QualifyExprNode::print(PrettyPrinter &f) const
{
f << qualifier << "::" << name;
}
void JS::BooleanExprNode::print(PrettyPrinter &f) const
{
f << value;
}
void JS::NumberExprNode::print(PrettyPrinter &f) const
{
f << value;
}
void JS::StringExprNode::print(PrettyPrinter &f) const
{
quoteString(f, str, '"');
}
void JS::RegExpExprNode::print(PrettyPrinter &f) const
{
f << '/' << re << '/' << flags;
}
void JS::NumUnitExprNode::print(PrettyPrinter &f) const
{
f << numStr;
StringExprNode::print(f);
}
void JS::ExprUnitExprNode::print(PrettyPrinter &f) const
{
f << op;
StringExprNode::print(f);
}
void JS::FunctionExprNode::print(PrettyPrinter &f) const
{
function.print(f, 0, false);
}
void JS::PairListExprNode::print(PrettyPrinter &f) const
{
char beginBracket;
char endBracket;
switch (getKind()) {
case objectLiteral:
beginBracket = '{';
endBracket = '}';
break;
case arrayLiteral:
case index:
beginBracket = '[';
endBracket = ']';
break;
case superStmt:
case call:
case New:
beginBracket = '(';
endBracket = ')';
break;
default:
NOT_REACHED("Bad kind");
return;
}
f << beginBracket;
PrettyPrinter::Block b(f);
const ExprPairList *p = pairs;
if (p)
while (true) {
const ExprNode *field = p->field;
if (field) {
f << field << ':';
f.fillBreak(0);
}
const ExprNode *value = p->value;
if (value)
f << value;
p = p->next;
if (!p)
break;
f << ',';
f.linearBreak(static_cast<uint32>(field || value));
}
f << endBracket;
}
void JS::InvokeExprNode::print(PrettyPrinter &f) const
{
PrettyPrinter::Block b(f);
if (hasKind(New))
f << "new ";
if (hasKind(superStmt))
f << "super";
else
f << op;
PrettyPrinter::Indent i(f, subexpressionIndent);
f.fillBreak(0);
PairListExprNode::print(f);
}
void JS::SuperExprNode::print(PrettyPrinter &f) const
{
f << "super";
if (op) {
f << '(';
f << op << ')';
}
}
void JS::UnaryExprNode::print(PrettyPrinter &f) const
{
if (hasKind(parentheses)) {
f << '(';
f << op << ')';
} else {
if (debugExprNodePrint)
f << '(';
const char *name = kindName(getKind());
if (hasKind(postIncrement) || hasKind(postDecrement) || hasKind(dotClass))
f << op << name;
else {
f << name;
f << op;
} if (debugExprNodePrint)
f << ')';
}
}
void JS::BinaryExprNode::print(PrettyPrinter &f) const
{
if (hasKind(juxtapose)) {
f << op1 << ' ';
f << op2;
} else {
if (debugExprNodePrint)
f << '(';
PrettyPrinter::Block b(f);
f << op1;
uint32 nSpaces = hasKind(dot) || hasKind(dotParen) ? (uint32)0 : (uint32)1;
if (nSpaces)
f << ' ';
f << kindName(getKind());
f.fillBreak(nSpaces);
f << op2;
if (hasKind(dotParen))
f << ')';
if (debugExprNodePrint)
f << ')';
}
}
void JS::TernaryExprNode::print(PrettyPrinter &f) const
{
if (debugExprNodePrint)
f << '(';
PrettyPrinter::Block b(f);
f << op1 << " ?";
f.fillBreak(1);
f << op2 << " :";
f.fillBreak(1);
f << op3;
if (debugExprNodePrint)
f << ')';
}
//
// StmtNode
//
// Print statements on separate lines onto f. Do not print a line break
// after the last statement.
void JS::StmtNode::printStatements(PrettyPrinter &f, const StmtNode *statements)
{
if (statements) {
PrettyPrinter::Block b(f);
while (true) {
const StmtNode *next = statements->next;
statements->print(f, !next);
statements = next;
if (!statements)
break;
f.requiredBreak();
}
}
}
// Print statements as a block enclosed in curly braces onto f.
// If loose is false, do not insist on each statement being on a separate
// line; instead, make breaks between statements be linear breaks in the
// enclosing PrettyPrinter::Block scope. The caller must have placed this
// call inside a PrettyPrinter::Block scope.
void JS::StmtNode::printBlockStatements(PrettyPrinter &f, const StmtNode *statements, bool loose)
{
f << '{';
if (statements) {
{
PrettyPrinter::Indent i(f, basicIndent);
uint32 nSpaces = 0;
while (statements) {
if (statements->hasKind(Case)) {
PrettyPrinter::Indent i2(f, caseIndent - basicIndent);
f.linearBreak(nSpaces, loose);
statements->print(f, false);
} else {
f.linearBreak(nSpaces, loose);
statements->print(f, !statements->next);
}
statements = statements->next;
nSpaces = 1;
}
}
f.linearBreak(0, loose);
} else
f.fillBreak(0);
f << '}';
}
// Print a closing statement semicolon onto f unless noSemi is true.
void JS::StmtNode::printSemi(PrettyPrinter &f, bool noSemi)
{
if (!noSemi)
f << ';';
}
// Print this as a substatement of a statement such as if or with.
// If this statement is a block without attributes, begin it on the current
// line and do not indent it -- the block itself will provide the indent.
// Otherwise, begin this statement on a new line and indent it.
// If continuation is non-nil, it specifies a continuation such as 'else' or
// the 'while' of a do-while statement. If this statement is a block without
// attributes, print a space and the continuation after the closing brace;
// otherwise print the continuation on a new line.
// If noSemi is true, do not print the semicolon unless it is required by the statement.
// The caller must have placed this call inside a PrettyPrinter::Block scope that encloses
// the containining statement.
void JS::StmtNode::printSubstatement(PrettyPrinter &f, bool noSemi, const char *continuation) const
{
if (hasKind(block) && !checked_cast<const BlockStmtNode *>(this)->attributes) {
f << ' ';
checked_cast<const BlockStmtNode *>(this)->printBlock(f, true);
if (continuation)
f << ' ' << continuation;
} else {
{
PrettyPrinter::Indent i(f, basicIndent);
f.requiredBreak();
this->print(f, noSemi);
}
if (continuation) {
f.requiredBreak();
f << continuation;
}
}
}
// Print the attributes on a single line separated with and followed by a space.
void JS::AttributeStmtNode::printAttributes(PrettyPrinter &f) const
{
const ExprNode *a = attributes;
if (a) {
f << a << ' ';
}
}
// Print this block, including attributes, onto f.
// If loose is false, do not insist on each statement being on a separate line;
// instead, make breaks between statements be linear breaks in the enclosing
// PrettyPrinter::Block scope.
// The caller must have placed this call inside a PrettyPrinter::Block scope.
void JS::BlockStmtNode::printBlock(PrettyPrinter &f, bool loose) const
{
printAttributes(f);
printBlockStatements(f, statements, loose);
}
// Print this onto f.
// If noSemi is true, do not print the trailing semicolon unless it is required by the statement.
void JS::StmtNode::print(PrettyPrinter &f, bool /*noSemi*/) const
{
ASSERT(hasKind(empty));
f << ';';
}
void JS::ExprStmtNode::print(PrettyPrinter &f, bool noSemi) const
{
const ExprNode *e = expr;
switch (getKind()) {
case Case:
if (e) {
f << "case ";
f << e;
} else
f << "default";
f << ':';
break;
case Return:
f << "return";
if (e) {
f << ' ';
goto showExpr;
} else
goto showSemicolon;
case Throw:
f << "throw ";
case expression:
showExpr:
f << e;
showSemicolon:
printSemi(f, noSemi);
break;
default:
NOT_REACHED("Bad kind");
}
}
void JS::DebuggerStmtNode::print(PrettyPrinter &f, bool) const
{
f << "debugger;";
}
void JS::BlockStmtNode::print(PrettyPrinter &f, bool) const
{
PrettyPrinter::Block b(f, 0);
printBlock(f, true);
}
void JS::LabelStmtNode::print(PrettyPrinter &f, bool noSemi) const
{
PrettyPrinter::Block b(f, basicIndent);
f << name << ':';
f.linearBreak(1);
stmt->print(f, noSemi);
}
void JS::UnaryStmtNode::print(PrettyPrinter &f, bool noSemi) const
{
PrettyPrinter::Block b(f, 0);
printContents(f, noSemi);
}
// Same as print except that uses the caller's PrettyPrinter::Block.
void JS::UnaryStmtNode::printContents(PrettyPrinter &f, bool noSemi) const
{
ASSERT(stmt);
const char *kindName = 0;
switch (getKind()) {
case If:
kindName = "if";
break;
case While:
kindName = "while";
break;
case DoWhile:
f << "do";
stmt->printSubstatement(f, true, "while (");
f << expr << ')';
printSemi(f, noSemi);
return;
case With:
kindName = "with";
break;
default:
NOT_REACHED("Bad kind");
}
f << kindName << " (";
f << expr << ')';
stmt->printSubstatement(f, noSemi);
}
void JS::BinaryStmtNode::printContents(PrettyPrinter &f, bool noSemi) const
{
ASSERT(stmt && stmt2 && hasKind(IfElse));
f << "if (";
f << expr << ')';
stmt->printSubstatement(f, true, "else");
if (stmt2->hasKind(If) || stmt2->hasKind(IfElse)) {
f << ' ';
checked_cast<const UnaryStmtNode *>(stmt2)->printContents(f, noSemi);
} else
stmt2->printSubstatement(f, noSemi);
}
void JS::ForStmtNode::print(PrettyPrinter &f, bool noSemi) const
{
ASSERT(stmt && (hasKind(For) || hasKind(ForIn)));
PrettyPrinter::Block b(f, 0);
f << "for (";
{
PrettyPrinter::Block b2(f);
if (initializer)
initializer->print(f, true);
if (hasKind(ForIn)) {
f.fillBreak(1);
f << "in";
f.fillBreak(1);
ASSERT(expr2 && !expr3);
f << expr2;
} else {
f << ';';
if (expr2) {
f.linearBreak(1);
f << expr2;
}
f << ';';
if (expr3) {
f.linearBreak(1);
f << expr3;
}
}
f << ')';
}
stmt->printSubstatement(f, noSemi);
}
void JS::SwitchStmtNode::print(PrettyPrinter &f, bool) const
{
PrettyPrinter::Block b(f);
f << "switch (";
f << expr << ") ";
printBlockStatements(f, statements, true);
}
void JS::GoStmtNode::print(PrettyPrinter &f, bool noSemi) const
{
const char *kindName = 0;
switch (getKind()) {
case Break:
kindName = "break";
break;
case Continue:
kindName = "continue";
break;
default:
NOT_REACHED("Bad kind");
}
f << kindName;
if (name)
f << " " << *name;
printSemi(f, noSemi);
}
void JS::TryStmtNode::print(PrettyPrinter &f, bool noSemi) const
{
PrettyPrinter::Block b(f, 0);
f << "try";
const StmtNode *s = stmt;
for (const CatchClause *c = catches; c; c = c->next) {
s->printSubstatement(f, true, "catch (");
PrettyPrinter::Block b2(f);
f << c->name;
ExprNode *t = c->type;
if (t) {
f << ':';
f.linearBreak(1);
f << t;
}
f << ')';
s = c->stmt;
}
if (finally) {
s->printSubstatement(f, true, "finally");
s = finally;
}
s->printSubstatement(f, noSemi);
}
// Print this binding onto f.
void JS::ExportBinding::print(PrettyPrinter &f) const
{
PrettyPrinter::Block b(f);
name.print(f);
PrettyPrinter::Indent i(f, subexpressionIndent);
f.linearBreak(1);
f << "= ";
initializer.print(f);
}
void JS::ExportStmtNode::print(PrettyPrinter &f, bool noSemi) const
{
printAttributes(f);
f << "export";
PrettyPrinter::Block b(f, basicIndent);
const ExportBinding *binding = bindings;
ASSERT(binding);
while (true) {
f.linearBreak(1);
binding->print(f);
binding = binding->next;
if (!binding)
break;
f << ',';
}
printSemi(f, noSemi);
}
void JS::VariableStmtNode::print(PrettyPrinter &f, bool noSemi) const
{
printAttributes(f);
ASSERT(hasKind(Const) || hasKind(Var));
f << (hasKind(Const) ? "const" : "var");
PrettyPrinter::Block b(f, basicIndent);
const VariableBinding *binding = bindings;
ASSERT(binding);
while (true) {
f.linearBreak(1);
binding->print(f, false);
binding = binding->next;
if (!binding)
break;
f << ',';
}
printSemi(f, noSemi);
}
void JS::FunctionStmtNode::print(PrettyPrinter &f, bool noSemi) const
{
function.print(f, this, noSemi);
}
void JS::NamespaceStmtNode::print(PrettyPrinter &f, bool noSemi) const
{
printAttributes(f);
ASSERT(hasKind(Namespace));
PrettyPrinter::Block b(f, namespaceHeaderIndent);
f << "namespace " << name;
printSemi(f, noSemi);
}
void JS::ClassStmtNode::print(PrettyPrinter &f, bool noSemi) const
{
printAttributes(f);
ASSERT(hasKind(Class));
{
PrettyPrinter::Block b(f, namespaceHeaderIndent);
f << "class " << name;
if (superclass) {
f.linearBreak(1);
f << "extends ";
f << superclass;
}
}
if (body) {
f.requiredBreak();
body->printBlock(f, true);
} else
printSemi(f, noSemi);
}
// Print the contents of the linked list headed by this. The list must not be empty.
// The elements of the list are separated by the separator character and a fill break;
// if the separator is a comma, then it is followed by one space.
void JS::IdentifierList::printList(PrettyPrinter &f, char separator) const
{
const IdentifierList *list = this;
ASSERT(list);
PrettyPrinter::Block b(f);
while (true) {
f << list->name;
list = list->next;
if (!list)
break;
f << separator;
f.fillBreak(separator == ',');
}
}
// Print a comma-separated ExprList on to f. The list must not be empty.
void JS::ExprList::printList(PrettyPrinter &f) const
{
const ExprList *list = this;
ASSERT(list);
PrettyPrinter::Block b(f);
while (true) {
f << list->expr;
list = list->next;
if (!list)
break;
f << ',';
f.fillBreak(1);
}
}
// Print a linear break followed by "namespace(<namespaces>)" on f.
// The linked list of namespaces must not be empty.
void JS::UseStmtNode::printNamespaces(PrettyPrinter &f) const
{
f.linearBreak(1);
f << "namespace(";
namespaces->printList(f);
f << ')';
}
// Print a comma and a linear break followed by "include" or "exclude" and a parenthesized list
// of identifiers on f.
void JS::UseStmtNode::printIncludeExclude(PrettyPrinter &f) const
{
f << ',';
f.linearBreak(1);
f << (exclude ? "ex" : "in") << "clude(";
if (includeExclude)
includeExclude->printList(f, ',');
f << ')';
}
void JS::UseStmtNode::print(PrettyPrinter &f, bool noSemi) const
{
printAttributes(f);
PrettyPrinter::Block b(f, 4);
f << "use";
ASSERT(namespaces);
printNamespaces(f);
printIncludeExclude(f);
printSemi(f, noSemi);
}
void JS::ImportStmtNode::print(PrettyPrinter &f, bool noSemi) const
{
printAttributes(f);
PrettyPrinter::Block b(f, 4);
f << "import";
f.fillBreak(1);
{
PrettyPrinter::Block b(f);
if (varName) {
f << *varName << " =";
f.fillBreak(1);
}
if (packageIdList)
packageIdList->printList(f, '.');
else {
ASSERT(packageString);
quoteString(f, *packageString, '"');
}
}
if (namespaces) {
f << ',';
printNamespaces(f);
}
printIncludeExclude(f);
printSemi(f, noSemi);
}
void JS::PackageStmtNode::print(PrettyPrinter &f, bool noSemi) const
{
PrettyPrinter::Block b(f, 0);
f << "package";
if (packageIdList) {
PrettyPrinter::Block b2(f);
f.fillBreak(1);
packageIdList->printList(f, '.');
}
body->printSubstatement(f, noSemi);
}
void JS::PragmaStmtNode::print(PrettyPrinter &f, bool noSemi) const
{
f << "use ";
const char *language;
if (testFlag(flags, Pragma::js1))
language = "ecmascript(3)";
else if (testFlag(flags, Pragma::js12))
language = "javascript(1.2)";
else if (testFlag(flags, Pragma::es4))
language = "ecmascript(4)";
else
language = "javascript(2)";
f << language << ", strict(" << testFlag(flags, Pragma::strict) << ")";
printSemi(f, noSemi);
}
void JS::IncludeStmtNode::print(PrettyPrinter &f, bool noSemi) const
{
f << "include ";
quoteString(f, name, '"');
printSemi(f, noSemi);
}