From c6c504f8b685ef032fc3759e08b0db57d8841658 Mon Sep 17 00:00:00 2001 From: "keuchel@chromium.org" Date: Tue, 16 Aug 2011 14:24:12 +0000 Subject: [PATCH] Parse harmony let declarations. Implementation of the harmony block scoped let bindings as proposed here: http://wiki.ecmascript.org/doku.php?id=harmony:block_scoped_bindings Changes to the syntax are explained there. They are active under the harmony_block_scoping_ flag in the parser. Review URL: http://codereview.chromium.org/7616009 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@8944 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- src/api.cc | 7 +- src/arm/full-codegen-arm.cc | 6 +- src/ast.h | 6 +- src/compiler.cc | 10 +- src/contexts.cc | 1 + src/ia32/full-codegen-ia32.cc | 6 +- src/messages.js | 1 + src/parser.cc | 141 ++++++----- src/parser.h | 18 +- src/preparser-api.cc | 1 + src/preparser.cc | 54 ++++- src/preparser.h | 18 +- src/runtime.cc | 5 + src/scanner-base.cc | 136 ++++++----- src/scanner-base.h | 11 + src/scopes.cc | 4 +- src/token.h | 1 + src/variables.cc | 1 + src/variables.h | 2 + src/x64/full-codegen-x64.cc | 6 +- test/cctest/test-parsing.cc | 6 +- .../mjsunit/bugs/harmony/debug-blockscopes.js | 224 ++++++++++++++++++ test/mjsunit/harmony/block-let-declaration.js | 67 ++++++ test/mjsunit/harmony/block-scoping.js | 176 +++++++++++++- test/mjsunit/harmony/debug-blockscopes.js | 56 ++--- .../harmony/debug-evaluate-blockscopes.js | 4 +- 26 files changed, 774 insertions(+), 194 deletions(-) create mode 100644 test/mjsunit/bugs/harmony/debug-blockscopes.js create mode 100644 test/mjsunit/harmony/block-let-declaration.js diff --git a/src/api.cc b/src/api.cc index 402f22055..5a5f14dc6 100644 --- a/src/api.cc +++ b/src/api.cc @@ -35,6 +35,7 @@ #include "debug.h" #include "deoptimizer.h" #include "execution.h" +#include "flags.h" #include "global-handles.h" #include "heap-profiler.h" #include "messages.h" @@ -1405,7 +1406,7 @@ void ObjectTemplate::SetInternalFieldCount(int value) { ScriptData* ScriptData::PreCompile(const char* input, int length) { i::Utf8ToUC16CharacterStream stream( reinterpret_cast(input), length); - return i::ParserApi::PreParse(&stream, NULL); + return i::ParserApi::PreParse(&stream, NULL, i::FLAG_harmony_block_scoping); } @@ -1414,10 +1415,10 @@ ScriptData* ScriptData::PreCompile(v8::Handle source) { if (str->IsExternalTwoByteString()) { i::ExternalTwoByteStringUC16CharacterStream stream( i::Handle::cast(str), 0, str->length()); - return i::ParserApi::PreParse(&stream, NULL); + return i::ParserApi::PreParse(&stream, NULL, i::FLAG_harmony_block_scoping); } else { i::GenericStringUC16CharacterStream stream(str, 0, str->length()); - return i::ParserApi::PreParse(&stream, NULL); + return i::ParserApi::PreParse(&stream, NULL, i::FLAG_harmony_block_scoping); } } diff --git a/src/arm/full-codegen-arm.cc b/src/arm/full-codegen-arm.cc index 3742d166c..3116ca455 100644 --- a/src/arm/full-codegen-arm.cc +++ b/src/arm/full-codegen-arm.cc @@ -742,9 +742,9 @@ void FullCodeGenerator::EmitDeclaration(Variable* variable, __ mov(r2, Operand(variable->name())); // Declaration nodes are always introduced in one of two modes. ASSERT(mode == Variable::VAR || - mode == Variable::CONST); - PropertyAttributes attr = - (mode == Variable::VAR) ? NONE : READ_ONLY; + mode == Variable::CONST || + mode == Variable::LET); + PropertyAttributes attr = (mode == Variable::CONST) ? READ_ONLY : NONE; __ mov(r1, Operand(Smi::FromInt(attr))); // Push initial value, if any. // Note: For variables we must not push an initial value (such as diff --git a/src/ast.h b/src/ast.h index fccf1aa1c..4031b7d81 100644 --- a/src/ast.h +++ b/src/ast.h @@ -375,9 +375,11 @@ class Declaration: public AstNode { : proxy_(proxy), mode_(mode), fun_(fun) { - ASSERT(mode == Variable::VAR || mode == Variable::CONST); + ASSERT(mode == Variable::VAR || + mode == Variable::CONST || + mode == Variable::LET); // At the moment there are no "const functions"'s in JavaScript... - ASSERT(fun == NULL || mode == Variable::VAR); + ASSERT(fun == NULL || mode == Variable::VAR || mode == Variable::LET); } DECLARE_NODE_TYPE(Declaration) diff --git a/src/compiler.cc b/src/compiler.cc index a87eecc3c..c7e78067c 100755 --- a/src/compiler.cc +++ b/src/compiler.cc @@ -478,15 +478,21 @@ Handle Compiler::Compile(Handle source, // that would be compiled lazily anyway, so we skip the preparse step // in that case too. ScriptDataImpl* pre_data = input_pre_data; + bool harmony_block_scoping = natives != NATIVES_CODE && + FLAG_harmony_block_scoping; if (pre_data == NULL && source_length >= FLAG_min_preparse_length) { if (source->IsExternalTwoByteString()) { ExternalTwoByteStringUC16CharacterStream stream( Handle::cast(source), 0, source->length()); - pre_data = ParserApi::PartialPreParse(&stream, extension); + pre_data = ParserApi::PartialPreParse(&stream, + extension, + harmony_block_scoping); } else { GenericStringUC16CharacterStream stream(source, 0, source->length()); - pre_data = ParserApi::PartialPreParse(&stream, extension); + pre_data = ParserApi::PartialPreParse(&stream, + extension, + harmony_block_scoping); } } diff --git a/src/contexts.cc b/src/contexts.cc index 72a5ae4d6..c0e724253 100644 --- a/src/contexts.cc +++ b/src/contexts.cc @@ -180,6 +180,7 @@ Handle Context::Lookup(Handle name, switch (mode) { case Variable::INTERNAL: // Fall through. case Variable::VAR: + case Variable::LET: *attributes = NONE; break; case Variable::CONST: diff --git a/src/ia32/full-codegen-ia32.cc b/src/ia32/full-codegen-ia32.cc index 0056acc95..bb75b1e6a 100644 --- a/src/ia32/full-codegen-ia32.cc +++ b/src/ia32/full-codegen-ia32.cc @@ -737,8 +737,10 @@ void FullCodeGenerator::EmitDeclaration(Variable* variable, __ push(esi); __ push(Immediate(variable->name())); // Declaration nodes are always introduced in one of two modes. - ASSERT(mode == Variable::VAR || mode == Variable::CONST); - PropertyAttributes attr = (mode == Variable::VAR) ? NONE : READ_ONLY; + ASSERT(mode == Variable::VAR || + mode == Variable::CONST || + mode == Variable::LET); + PropertyAttributes attr = (mode == Variable::CONST) ? READ_ONLY : NONE; __ push(Immediate(Smi::FromInt(attr))); // Push initial value, if any. // Note: For variables we must not push an initial value (such as diff --git a/src/messages.js b/src/messages.js index 6603185a3..58c9a5302 100644 --- a/src/messages.js +++ b/src/messages.js @@ -247,6 +247,7 @@ function FormatMessage(message) { strict_cannot_assign: ["Cannot assign to read only '", "%0", "' in strict mode"], strict_poison_pill: ["'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them"], strict_caller: ["Illegal access to a strict mode caller function."], + unprotected_let: ["Illegal let declaration in unprotected statement context."], }; } var message_type = %MessageGetType(message); diff --git a/src/parser.cc b/src/parser.cc index c60ee6792..844dd7060 100644 --- a/src/parser.cc +++ b/src/parser.cc @@ -811,6 +811,7 @@ void Parser::ReportMessageAt(Scanner::Location source_location, } void Parser::SetHarmonyBlockScoping(bool block_scoping) { + scanner().SetHarmonyBlockScoping(block_scoping); harmony_block_scoping_ = block_scoping; } @@ -1093,6 +1094,25 @@ class ThisNamedPropertyAssigmentFinder : public ParserFinder { }; +Statement* Parser::ParseSourceElement(ZoneStringList* labels, + bool* ok) { + if (peek() == Token::FUNCTION) { + // FunctionDeclaration is only allowed in the context of SourceElements + // (Ecma 262 5th Edition, clause 14): + // SourceElement: + // Statement + // FunctionDeclaration + // Common language extension is to allow function declaration in place + // of any statement. This language extension is disabled in strict mode. + return ParseFunctionDeclaration(ok); + } else if (peek() == Token::LET) { + return ParseVariableStatement(kSourceElement, ok); + } else { + return ParseStatement(labels, ok); + } +} + + void* Parser::ParseSourceElements(ZoneList* processor, int end_token, bool* ok) { @@ -1116,21 +1136,7 @@ void* Parser::ParseSourceElements(ZoneList* processor, } Scanner::Location token_loc = scanner().peek_location(); - - Statement* stat; - if (peek() == Token::FUNCTION) { - // FunctionDeclaration is only allowed in the context of SourceElements - // (Ecma 262 5th Edition, clause 14): - // SourceElement: - // Statement - // FunctionDeclaration - // Common language extension is to allow function declaration in place - // of any statement. This language extension is disabled in strict mode. - stat = ParseFunctionDeclaration(CHECK_OK); - } else { - stat = ParseStatement(NULL, CHECK_OK); - } - + Statement* stat = ParseSourceElement(NULL, CHECK_OK); if (stat == NULL || stat->IsEmpty()) { directive_prologue = false; // End of directive prologue. continue; @@ -1218,7 +1224,7 @@ Statement* Parser::ParseStatement(ZoneStringList* labels, bool* ok) { case Token::CONST: // fall through case Token::VAR: - stmt = ParseVariableStatement(ok); + stmt = ParseVariableStatement(kStatement, ok); break; case Token::SEMICOLON: @@ -1313,9 +1319,9 @@ VariableProxy* Parser::Declare(Handle name, bool resolve, bool* ok) { Variable* var = NULL; - // If we are inside a function, a declaration of a variable - // is a truly local variable, and the scope of the variable - // is always the function scope. + // If we are inside a function, a declaration of a var/const variable is a + // truly local variable, and the scope of the variable is always the function + // scope. // If a function scope exists, then we can statically declare this // variable and also set its mode. In any case, a Declaration node @@ -1325,24 +1331,28 @@ VariableProxy* Parser::Declare(Handle name, // to the calling function context. // Similarly, strict mode eval scope does not leak variable declarations to // the caller's scope so we declare all locals, too. - Scope* declaration_scope = top_scope_->DeclarationScope(); + + Scope* declaration_scope = mode == Variable::LET ? top_scope_ + : top_scope_->DeclarationScope(); if (declaration_scope->is_function_scope() || - declaration_scope->is_strict_mode_eval_scope()) { + declaration_scope->is_strict_mode_eval_scope() || + declaration_scope->is_block_scope()) { // Declare the variable in the function scope. var = declaration_scope->LocalLookup(name); if (var == NULL) { // Declare the name. var = declaration_scope->DeclareLocal(name, mode); } else { - // The name was declared before; check for conflicting - // re-declarations. If the previous declaration was a const or the - // current declaration is a const then we have a conflict. There is - // similar code in runtime.cc in the Declare functions. - if ((mode == Variable::CONST) || (var->mode() == Variable::CONST)) { - // We only have vars and consts in declarations. + // The name was declared before; check for conflicting re-declarations. + // We have a conflict if either of the declarations is not a var. There + // is similar code in runtime.cc in the Declare functions. + if ((mode != Variable::VAR) || (var->mode() != Variable::VAR)) { + // We only have vars, consts and lets in declarations. ASSERT(var->mode() == Variable::VAR || - var->mode() == Variable::CONST); - const char* type = (var->mode() == Variable::VAR) ? "var" : "const"; + var->mode() == Variable::CONST || + var->mode() == Variable::LET); + const char* type = (var->mode() == Variable::VAR) ? "var" : + (var->mode() == Variable::CONST) ? "const" : "let"; Handle type_string = isolate()->factory()->NewStringFromUtf8(CStrVector(type), TENURED); Expression* expression = @@ -1485,7 +1495,8 @@ Statement* Parser::ParseFunctionDeclaration(bool* ok) { // Even if we're not at the top-level of the global or a function // scope, we treat is as such and introduce the function with it's // initial value upon entering the corresponding scope. - Declare(name, Variable::VAR, fun, true, CHECK_OK); + Variable::Mode mode = harmony_block_scoping_ ? Variable::LET : Variable::VAR; + Declare(name, mode, fun, true, CHECK_OK); return EmptyStatement(); } @@ -1540,7 +1551,7 @@ Block* Parser::ParseScopedBlock(ZoneStringList* labels, bool* ok) { InitializationBlockFinder block_finder(top_scope_, target_stack_); while (peek() != Token::RBRACE) { - Statement* stat = ParseStatement(NULL, CHECK_OK); + Statement* stat = ParseSourceElement(NULL, CHECK_OK); if (stat && !stat->IsEmpty()) { body->AddStatement(stat); block_finder.Update(stat); @@ -1566,12 +1577,15 @@ Block* Parser::ParseScopedBlock(ZoneStringList* labels, bool* ok) { } -Block* Parser::ParseVariableStatement(bool* ok) { +Block* Parser::ParseVariableStatement(VariableDeclarationContext var_context, + bool* ok) { // VariableStatement :: // VariableDeclarations ';' Handle ignore; - Block* result = ParseVariableDeclarations(true, &ignore, CHECK_OK); + Block* result = ParseVariableDeclarations(var_context, + &ignore, + CHECK_OK); ExpectSemicolon(CHECK_OK); return result; } @@ -1588,7 +1602,7 @@ bool Parser::IsEvalOrArguments(Handle string) { // *var is untouched; in particular, it is the caller's responsibility // to initialize it properly. This mechanism is used for the parsing // of 'for-in' loops. -Block* Parser::ParseVariableDeclarations(bool accept_IN, +Block* Parser::ParseVariableDeclarations(VariableDeclarationContext var_context, Handle* out, bool* ok) { // VariableDeclarations :: @@ -1596,25 +1610,36 @@ Block* Parser::ParseVariableDeclarations(bool accept_IN, Variable::Mode mode = Variable::VAR; bool is_const = false; - Scope* declaration_scope = top_scope_->DeclarationScope(); if (peek() == Token::VAR) { Consume(Token::VAR); } else if (peek() == Token::CONST) { Consume(Token::CONST); - if (declaration_scope->is_strict_mode()) { + if (top_scope_->is_strict_mode()) { ReportMessage("strict_const", Vector::empty()); *ok = false; return NULL; } mode = Variable::CONST; is_const = true; + } else if (peek() == Token::LET) { + Consume(Token::LET); + if (var_context != kSourceElement && + var_context != kForStatement) { + ASSERT(var_context == kStatement); + ReportMessage("unprotected_let", Vector::empty()); + *ok = false; + return NULL; + } + mode = Variable::LET; } else { UNREACHABLE(); // by current callers } - // The scope of a variable/const declared anywhere inside a function + Scope* declaration_scope = mode == Variable::LET + ? top_scope_ : top_scope_->DeclarationScope(); + // The scope of a var/const declared variable anywhere inside a function // is the entire function (ECMA-262, 3rd, 10.1.3, and 12.2). Thus we can - // transform a source-level variable/const declaration into a (Function) + // transform a source-level var/const declaration into a (Function) // Scope declaration, and rewrite the source-level initialization into an // assignment statement. We use a block to collect multiple assignments. // @@ -1698,7 +1723,7 @@ Block* Parser::ParseVariableDeclarations(bool accept_IN, if (peek() == Token::ASSIGN) { Expect(Token::ASSIGN, CHECK_OK); position = scanner().location().beg_pos; - value = ParseAssignmentExpression(accept_IN, CHECK_OK); + value = ParseAssignmentExpression(var_context != kForStatement, CHECK_OK); // Don't infer if it is "a = function(){...}();"-like expression. if (fni_ != NULL && value->AsCall() == NULL && @@ -2298,7 +2323,7 @@ Statement* Parser::ParseForStatement(ZoneStringList* labels, bool* ok) { if (peek() == Token::VAR || peek() == Token::CONST) { Handle name; Block* variable_statement = - ParseVariableDeclarations(false, &name, CHECK_OK); + ParseVariableDeclarations(kForStatement, &name, CHECK_OK); if (peek() == Token::IN && !name.is_null()) { VariableProxy* each = top_scope_->NewUnresolved(name, inside_with()); @@ -3657,8 +3682,11 @@ FunctionLiteral* Parser::ParseFunctionLiteral(Handle function_name, } int num_parameters = 0; - // Function declarations are hoisted. - Scope* scope = (type == FunctionLiteral::DECLARATION) + // Function declarations are function scoped in normal mode, so they are + // hoisted. In harmony block scoping mode they are block scoped, so they + // are not hoisted. + Scope* scope = (type == FunctionLiteral::DECLARATION && + !harmony_block_scoping_) ? NewScope(top_scope_->DeclarationScope(), Scope::FUNCTION_SCOPE, false) : NewScope(top_scope_, Scope::FUNCTION_SCOPE, inside_with()); ZoneList* body = new(zone()) ZoneList(8); @@ -3960,7 +3988,7 @@ Literal* Parser::GetLiteralNumber(double value) { } -// Parses and identifier that is valid for the current scope, in particular it +// Parses an identifier that is valid for the current scope, in particular it // fails on strict mode future reserved keywords in a strict scope. Handle Parser::ParseIdentifier(bool* ok) { if (top_scope_->is_strict_mode()) { @@ -5041,9 +5069,11 @@ int ScriptDataImpl::ReadNumber(byte** source) { // Create a Scanner for the preparser to use as input, and preparse the source. static ScriptDataImpl* DoPreParse(UC16CharacterStream* source, bool allow_lazy, - ParserRecorder* recorder) { + ParserRecorder* recorder, + bool harmony_block_scoping) { Isolate* isolate = Isolate::Current(); JavaScriptScanner scanner(isolate->unicode_cache()); + scanner.SetHarmonyBlockScoping(harmony_block_scoping); scanner.Initialize(source); intptr_t stack_limit = isolate->stack_guard()->real_climit(); if (!preparser::PreParser::PreParseProgram(&scanner, @@ -5064,7 +5094,8 @@ static ScriptDataImpl* DoPreParse(UC16CharacterStream* source, // Preparse, but only collect data that is immediately useful, // even if the preparser data is only used once. ScriptDataImpl* ParserApi::PartialPreParse(UC16CharacterStream* source, - v8::Extension* extension) { + v8::Extension* extension, + bool harmony_block_scoping) { bool allow_lazy = FLAG_lazy && (extension == NULL); if (!allow_lazy) { // Partial preparsing is only about lazily compiled functions. @@ -5072,16 +5103,17 @@ ScriptDataImpl* ParserApi::PartialPreParse(UC16CharacterStream* source, return NULL; } PartialParserRecorder recorder; - return DoPreParse(source, allow_lazy, &recorder); + return DoPreParse(source, allow_lazy, &recorder, harmony_block_scoping); } ScriptDataImpl* ParserApi::PreParse(UC16CharacterStream* source, - v8::Extension* extension) { + v8::Extension* extension, + bool harmony_block_scoping) { Handle