Commit decc7b09 authored by littledan's avatar littledan Committed by Commit bot

Sloppy-mode let parsing

This patch makes 'let' a contextual keyword in both strict and sloppy mode.
It behaves as a keyword when used at the beginning of a StatementListItem
or lexical declaration at the beginning of a for statement, if it is followed
by an identifier, [ or {. Implementing this change requires an extra token
look-ahead by the parser which is only invoked in certain cases (so as to
avoid parsing RegExps as ECMAScript tokens). This might result in a slowdown
of the scanner, but performance testing of this patch hasn't yet found much
of a regression.

BUG=v8:3305
LOG=Y
R=adamk,vogelheim

Review URL: https://codereview.chromium.org/1315673009

Cr-Commit-Position: refs/heads/master@{#30451}
parent b4164754
...@@ -1402,7 +1402,7 @@ Statement* Parser::ParseStatementListItem(bool* ok) { ...@@ -1402,7 +1402,7 @@ Statement* Parser::ParseStatementListItem(bool* ok) {
case Token::VAR: case Token::VAR:
return ParseVariableStatement(kStatementListItem, NULL, ok); return ParseVariableStatement(kStatementListItem, NULL, ok);
case Token::LET: case Token::LET:
if (allow_let()) { if (IsNextLetKeyword()) {
return ParseVariableStatement(kStatementListItem, NULL, ok); return ParseVariableStatement(kStatementListItem, NULL, ok);
} }
break; break;
...@@ -2652,9 +2652,6 @@ Statement* Parser::ParseExpressionOrLabelledStatement( ...@@ -2652,9 +2652,6 @@ Statement* Parser::ParseExpressionOrLabelledStatement(
} }
break; break;
// TODO(arv): Handle `let [`
// https://code.google.com/p/v8/issues/detail?id=3847
default: default:
break; break;
} }
...@@ -3564,7 +3561,7 @@ Statement* Parser::ParseForStatement(ZoneList<const AstRawString*>* labels, ...@@ -3564,7 +3561,7 @@ Statement* Parser::ParseForStatement(ZoneList<const AstRawString*>* labels,
DeclarationParsingResult parsing_result; DeclarationParsingResult parsing_result;
if (peek() != Token::SEMICOLON) { if (peek() != Token::SEMICOLON) {
if (peek() == Token::VAR || (peek() == Token::CONST && allow_const()) || if (peek() == Token::VAR || (peek() == Token::CONST && allow_const()) ||
(peek() == Token::LET && allow_let())) { (peek() == Token::LET && IsNextLetKeyword())) {
ParseVariableDeclarations(kForStatement, &parsing_result, CHECK_OK); ParseVariableDeclarations(kForStatement, &parsing_result, CHECK_OK);
is_const = parsing_result.descriptor.mode == CONST; is_const = parsing_result.descriptor.mode == CONST;
......
...@@ -199,7 +199,7 @@ PreParser::Statement PreParser::ParseStatementListItem(bool* ok) { ...@@ -199,7 +199,7 @@ PreParser::Statement PreParser::ParseStatementListItem(bool* ok) {
} }
break; break;
case Token::LET: case Token::LET:
if (allow_let()) { if (IsNextLetKeyword()) {
return ParseVariableStatement(kStatementListItem, ok); return ParseVariableStatement(kStatementListItem, ok);
} }
break; break;
...@@ -893,7 +893,7 @@ PreParser::Statement PreParser::ParseForStatement(bool* ok) { ...@@ -893,7 +893,7 @@ PreParser::Statement PreParser::ParseForStatement(bool* ok) {
if (peek() != Token::SEMICOLON) { if (peek() != Token::SEMICOLON) {
ForEachStatement::VisitMode mode; ForEachStatement::VisitMode mode;
if (peek() == Token::VAR || (peek() == Token::CONST && allow_const()) || if (peek() == Token::VAR || (peek() == Token::CONST && allow_const()) ||
(peek() == Token::LET && allow_let())) { (peek() == Token::LET && IsNextLetKeyword())) {
int decl_count; int decl_count;
Scanner::Location first_initializer_loc = Scanner::Location::invalid(); Scanner::Location first_initializer_loc = Scanner::Location::invalid();
Scanner::Location bindings_loc = Scanner::Location::invalid(); Scanner::Location bindings_loc = Scanner::Location::invalid();
......
...@@ -338,6 +338,11 @@ class ParserBase : public Traits { ...@@ -338,6 +338,11 @@ class ParserBase : public Traits {
return scanner()->peek(); return scanner()->peek();
} }
INLINE(Token::Value PeekAhead()) {
if (stack_overflow_) return Token::ILLEGAL;
return scanner()->PeekAhead();
}
INLINE(Token::Value Next()) { INLINE(Token::Value Next()) {
if (stack_overflow_) return Token::ILLEGAL; if (stack_overflow_) return Token::ILLEGAL;
{ {
...@@ -727,6 +732,8 @@ class ParserBase : public Traits { ...@@ -727,6 +732,8 @@ class ParserBase : public Traits {
int param_count, FunctionLiteral::ArityRestriction arity_restriction, int param_count, FunctionLiteral::ArityRestriction arity_restriction,
bool has_rest, int formals_start_pos, int formals_end_pos, bool* ok); bool has_rest, int formals_start_pos, int formals_end_pos, bool* ok);
bool IsNextLetKeyword();
// Checks if the expression is a valid reference expression (e.g., on the // Checks if the expression is a valid reference expression (e.g., on the
// left-hand side of assignments). Although ruled out by ECMA as early errors, // left-hand side of assignments). Although ruled out by ECMA as early errors,
// we allow calls for web compatibility and rewrite them to a runtime throw. // we allow calls for web compatibility and rewrite them to a runtime throw.
...@@ -3796,6 +3803,27 @@ void ParserBase<Traits>::CheckArityRestrictions( ...@@ -3796,6 +3803,27 @@ void ParserBase<Traits>::CheckArityRestrictions(
} }
template <class Traits>
bool ParserBase<Traits>::IsNextLetKeyword() {
DCHECK(peek() == Token::LET);
if (!allow_let()) {
return false;
}
Token::Value next_next = PeekAhead();
switch (next_next) {
case Token::LBRACE:
case Token::LBRACK:
case Token::IDENTIFIER:
case Token::STATIC:
case Token::LET: // Yes, you can do let let = ... in sloppy mode
case Token::YIELD:
return true;
default:
return false;
}
}
template <class Traits> template <class Traits>
typename ParserBase<Traits>::ExpressionT typename ParserBase<Traits>::ExpressionT
ParserBase<Traits>::ParseArrowFunctionLiteral( ParserBase<Traits>::ParseArrowFunctionLiteral(
......
...@@ -237,6 +237,11 @@ Token::Value Scanner::Next() { ...@@ -237,6 +237,11 @@ Token::Value Scanner::Next() {
next_.location.end_pos = current_.location.end_pos; next_.location.end_pos = current_.location.end_pos;
} }
current_ = next_; current_ = next_;
if (V8_UNLIKELY(next_next_.token != Token::UNINITIALIZED)) {
next_ = next_next_;
next_next_.token = Token::UNINITIALIZED;
return current_.token;
}
has_line_terminator_before_next_ = false; has_line_terminator_before_next_ = false;
has_multiline_comment_before_next_ = false; has_multiline_comment_before_next_ = false;
if (static_cast<unsigned>(c0_) <= 0x7f) { if (static_cast<unsigned>(c0_) <= 0x7f) {
...@@ -255,6 +260,20 @@ Token::Value Scanner::Next() { ...@@ -255,6 +260,20 @@ Token::Value Scanner::Next() {
} }
Token::Value Scanner::PeekAhead() {
if (next_next_.token != Token::UNINITIALIZED) {
return next_next_.token;
}
TokenDesc prev = current_;
Next();
Token::Value ret = next_.token;
next_next_ = next_;
next_ = current_;
current_ = prev;
return ret;
}
// TODO(yangguo): check whether this is actually necessary. // TODO(yangguo): check whether this is actually necessary.
static inline bool IsLittleEndianByteOrderMark(uc32 c) { static inline bool IsLittleEndianByteOrderMark(uc32 c) {
// The Unicode value U+FFFE is guaranteed never to be assigned as a // The Unicode value U+FFFE is guaranteed never to be assigned as a
...@@ -1432,7 +1451,7 @@ int Scanner::FindSymbol(DuplicateFinder* finder, int value) { ...@@ -1432,7 +1451,7 @@ int Scanner::FindSymbol(DuplicateFinder* finder, int value) {
bool Scanner::SetBookmark() { bool Scanner::SetBookmark() {
if (c0_ != kNoBookmark && bookmark_c0_ == kNoBookmark && if (c0_ != kNoBookmark && bookmark_c0_ == kNoBookmark &&
source_->SetBookmark()) { next_next_.token == Token::UNINITIALIZED && source_->SetBookmark()) {
bookmark_c0_ = c0_; bookmark_c0_ = c0_;
CopyTokenDesc(&bookmark_current_, &current_); CopyTokenDesc(&bookmark_current_, &current_);
CopyTokenDesc(&bookmark_next_, &next_); CopyTokenDesc(&bookmark_next_, &next_);
......
...@@ -361,6 +361,8 @@ class Scanner { ...@@ -361,6 +361,8 @@ class Scanner {
// Returns the next token and advances input. // Returns the next token and advances input.
Token::Value Next(); Token::Value Next();
// Returns the token following peek()
Token::Value PeekAhead();
// Returns the current token again. // Returns the current token again.
Token::Value current_token() { return current_.token; } Token::Value current_token() { return current_.token; }
// Returns the location information for the current token // Returns the location information for the current token
...@@ -489,6 +491,7 @@ class Scanner { ...@@ -489,6 +491,7 @@ class Scanner {
// Initialize current_ to not refer to a literal. // Initialize current_ to not refer to a literal.
current_.literal_chars = NULL; current_.literal_chars = NULL;
current_.raw_literal_chars = NULL; current_.raw_literal_chars = NULL;
next_next_.token = Token::UNINITIALIZED;
} }
// Support BookmarkScope functionality. // Support BookmarkScope functionality.
...@@ -501,16 +504,22 @@ class Scanner { ...@@ -501,16 +504,22 @@ class Scanner {
// Literal buffer support // Literal buffer support
inline void StartLiteral() { inline void StartLiteral() {
LiteralBuffer* free_buffer = (current_.literal_chars == &literal_buffer1_) ? LiteralBuffer* free_buffer =
&literal_buffer2_ : &literal_buffer1_; (current_.literal_chars == &literal_buffer0_)
? &literal_buffer1_
: (current_.literal_chars == &literal_buffer1_) ? &literal_buffer2_
: &literal_buffer0_;
free_buffer->Reset(); free_buffer->Reset();
next_.literal_chars = free_buffer; next_.literal_chars = free_buffer;
} }
inline void StartRawLiteral() { inline void StartRawLiteral() {
LiteralBuffer* free_buffer = LiteralBuffer* free_buffer =
(current_.raw_literal_chars == &raw_literal_buffer1_) ? (current_.raw_literal_chars == &raw_literal_buffer0_)
&raw_literal_buffer2_ : &raw_literal_buffer1_; ? &raw_literal_buffer1_
: (current_.raw_literal_chars == &raw_literal_buffer1_)
? &raw_literal_buffer2_
: &raw_literal_buffer0_;
free_buffer->Reset(); free_buffer->Reset();
next_.raw_literal_chars = free_buffer; next_.raw_literal_chars = free_buffer;
} }
...@@ -687,6 +696,7 @@ class Scanner { ...@@ -687,6 +696,7 @@ class Scanner {
UnicodeCache* unicode_cache_; UnicodeCache* unicode_cache_;
// Buffers collecting literal strings, numbers, etc. // Buffers collecting literal strings, numbers, etc.
LiteralBuffer literal_buffer0_;
LiteralBuffer literal_buffer1_; LiteralBuffer literal_buffer1_;
LiteralBuffer literal_buffer2_; LiteralBuffer literal_buffer2_;
...@@ -695,11 +705,13 @@ class Scanner { ...@@ -695,11 +705,13 @@ class Scanner {
LiteralBuffer source_mapping_url_; LiteralBuffer source_mapping_url_;
// Buffer to store raw string values // Buffer to store raw string values
LiteralBuffer raw_literal_buffer0_;
LiteralBuffer raw_literal_buffer1_; LiteralBuffer raw_literal_buffer1_;
LiteralBuffer raw_literal_buffer2_; LiteralBuffer raw_literal_buffer2_;
TokenDesc current_; // desc for current token (as returned by Next()) TokenDesc current_; // desc for current token (as returned by Next())
TokenDesc next_; // desc for next token (one token look-ahead) TokenDesc next_; // desc for next token (one token look-ahead)
TokenDesc next_next_; // desc for the token after next (after PeakAhead())
// Variables for Scanner::BookmarkScope and the *Bookmark implementation. // Variables for Scanner::BookmarkScope and the *Bookmark implementation.
// These variables contain the scanner state when a bookmark is set. // These variables contain the scanner state when a bookmark is set.
......
...@@ -166,6 +166,7 @@ namespace internal { ...@@ -166,6 +166,7 @@ namespace internal {
\ \
/* Scanner-internal use only. */ \ /* Scanner-internal use only. */ \
T(WHITESPACE, NULL, 0) \ T(WHITESPACE, NULL, 0) \
T(UNINITIALIZED, NULL, 0) \
\ \
/* ES6 Template Literals */ \ /* ES6 Template Literals */ \
T(TEMPLATE_SPAN, NULL, 0) \ T(TEMPLATE_SPAN, NULL, 0) \
......
...@@ -7077,3 +7077,32 @@ TEST(LanguageModeDirectivesNonSimpleParameterListErrors) { ...@@ -7077,3 +7077,32 @@ TEST(LanguageModeDirectivesNonSimpleParameterListErrors) {
RunParserSyncTest(context_data, data, kError, NULL, 0, always_flags, RunParserSyncTest(context_data, data, kError, NULL, 0, always_flags,
arraysize(always_flags)); arraysize(always_flags));
} }
TEST(LetSloppyOnly) {
// clang-format off
const char* context_data[][2] = {
{"", ""},
{"{", "}"},
{NULL, NULL}
};
const char* data[] = {
"let let",
"let",
"let let = 1",
"let = 1",
"for (let let = 1; let < 1; let++) {}",
"for (let = 1; let < 1; let++) {}",
"for (let let in {}) {}",
"for (let let of []) {}",
"for (let in {}) {}",
NULL
};
// clang-format on
static const ParserFlag always_flags[] = {kAllowHarmonySloppy,
kAllowHarmonySloppyLet};
RunParserSyncTest(context_data, data, kSuccess, NULL, 0, always_flags,
arraysize(always_flags));
}
// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --harmony-sloppy --harmony-sloppy-let --harmony-destructuring
{
assertThrows(function() { return let; }, ReferenceError);
let let;
let = 5;
assertEquals(5, let);
{ let let = 1; assertEquals(1, let); }
assertEquals(5, let);
}
assertThrows(function() { return let; }, ReferenceError);
(function() {
var let, sum = 0;
for (let in [1, 2, 3, 4]) sum += Number(let);
assertEquals(6, sum);
for (let let of [4, 5]) sum += let;
assertEquals(15, sum);
for (let let in [6]) sum += Number([6][let]);
assertEquals(21, sum);
for (let = 7; let < 8; let++) sum += let;
assertEquals(28, sum);
assertEquals(8, let);
for (let let = 8; let < 9; let++) sum += let;
assertEquals(36, sum);
assertEquals(8, let);
})()
assertThrows(function() { return let; }, ReferenceError);
{
let obj = {};
let {let} = {let() { return obj; }};
let().x = 1;
assertEquals(1, obj.x);
}
{
let obj = {};
let [let] = [function() { return obj; }];
let().x = 1;
assertEquals(1, obj.x);
}
(function() {
function let() {
return 1;
}
assertEquals(1, let());
})()
assertThrows('for (let of []) {}', SyntaxError);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment