From f7dc15febeea78b22de1f57c397a3221a43d9213 Mon Sep 17 00:00:00 2001 From: Adam Klein Date: Wed, 28 Jan 2015 11:18:37 -0800 Subject: [PATCH] Implement ParseExportDeclaration per latest ES6 spec draft One missing feature: anonymous function & class declarations in "export default". BUG=v8:1569 LOG=n R=arv@chromium.org Review URL: https://codereview.chromium.org/882893002 Cr-Commit-Position: refs/heads/master@{#26313} --- src/parser.cc | 161 +++++++++++++++++++------ src/parser.h | 5 +- test/cctest/test-parsing.cc | 130 +++++++++++++++----- test/mjsunit/harmony/module-parsing.js | 4 - 4 files changed, 232 insertions(+), 68 deletions(-) diff --git a/src/parser.cc b/src/parser.cc index dff7de9e6b..9539d3891f 100644 --- a/src/parser.cc +++ b/src/parser.cc @@ -1270,7 +1270,7 @@ Module* Parser::ParseModule(bool* ok) { } -Module* Parser::ParseModuleUrl(bool* ok) { +Module* Parser::ParseModuleSpecifier(bool* ok) { // Module: // String @@ -1299,6 +1299,44 @@ Module* Parser::ParseModuleUrl(bool* ok) { } +void* Parser::ParseModuleDeclarationClause(ZoneList* names, + bool* ok) { + // Handles both imports and exports: + // + // ImportOrExportClause : + // '{' '}' + // '{' ImportOrExportsList '}' + // '{' ImportOrExportsList ',' '}' + // + // ImportOrExportsList : + // ImportOrExportSpecifier + // ImportOrExportsList ',' ImportOrExportSpecifier + // + // ImportOrExportSpecifier : + // IdentifierName + // IdentifierName 'as' IdentifierName + + Expect(Token::LBRACE, CHECK_OK); + + while (peek() != Token::RBRACE) { + const AstRawString* name = ParseIdentifierName(CHECK_OK); + names->Add(name, zone()); + const AstRawString* export_name = NULL; + if (CheckContextualKeyword(CStrVector("as"))) { + export_name = ParseIdentifierName(CHECK_OK); + } + // TODO(ES6): Return the export_name as well as the name. + USE(export_name); + if (peek() == Token::RBRACE) break; + Expect(Token::COMMA, CHECK_OK); + } + + Expect(Token::RBRACE, CHECK_OK); + + return 0; +} + + Statement* Parser::ParseImportDeclaration(bool* ok) { // ImportDeclaration: // 'import' IdentifierName (',' IdentifierName)* 'from' ModuleUrl ';' @@ -1318,10 +1356,10 @@ Statement* Parser::ParseImportDeclaration(bool* ok) { } ExpectContextualKeyword(CStrVector("from"), CHECK_OK); - Module* module = ParseModuleUrl(CHECK_OK); + Module* module = ParseModuleSpecifier(CHECK_OK); ExpectSemicolon(CHECK_OK); - // TODO(ES6): Do something with ParseModuleUrl's return value. + // TODO(ES6): Do something with ParseModuleSpecifier's return value. USE(module); for (int i = 0; i < names.length(); ++i) { @@ -1332,35 +1370,83 @@ Statement* Parser::ParseImportDeclaration(bool* ok) { } +Statement* Parser::ParseExportDefault(bool* ok) { + // Supports the following productions, starting after the 'default' token: + // 'export' 'default' FunctionDeclaration + // 'export' 'default' ClassDeclaration + // 'export' 'default' AssignmentExpression[In] ';' + + Statement* result = NULL; + switch (peek()) { + case Token::FUNCTION: + // TODO(ES6): Support parsing anonymous function declarations here. + result = ParseFunctionDeclaration(NULL, CHECK_OK); + break; + + case Token::CLASS: + // TODO(ES6): Support parsing anonymous class declarations here. + result = ParseClassDeclaration(NULL, CHECK_OK); + break; + + default: { + int pos = peek_position(); + Expression* expr = ParseAssignmentExpression(true, CHECK_OK); + ExpectSemicolon(CHECK_OK); + result = factory()->NewExpressionStatement(expr, pos); + break; + } + } + + // TODO(ES6): Add default export to scope_->interface() + + return result; +} + + Statement* Parser::ParseExportDeclaration(bool* ok) { // ExportDeclaration: - // 'export' Identifier (',' Identifier)* ';' - // 'export' VariableDeclaration - // 'export' FunctionDeclaration - // 'export' GeneratorDeclaration - // 'export' ModuleDeclaration - // - // TODO(ES6): implement current syntax + // 'export' '*' 'from' ModuleSpecifier ';' + // 'export' ExportClause ('from' ModuleSpecifier)? ';' + // 'export' VariableStatement + // 'export' Declaration + // 'export' 'default' ... (handled in ParseExportDefault) + int pos = peek_position(); Expect(Token::EXPORT, CHECK_OK); Statement* result = NULL; ZoneList names(1, zone()); + bool is_export_from = false; switch (peek()) { - case Token::IDENTIFIER: { - int pos = position(); - const AstRawString* name = - ParseIdentifier(kDontAllowEvalOrArguments, CHECK_OK); - names.Add(name, zone()); - while (peek() == Token::COMMA) { - Consume(Token::COMMA); - name = ParseIdentifier(kDontAllowEvalOrArguments, CHECK_OK); - names.Add(name, zone()); + case Token::DEFAULT: + Consume(Token::DEFAULT); + return ParseExportDefault(ok); + + case Token::MUL: { + Consume(Token::MUL); + ExpectContextualKeyword(CStrVector("from"), CHECK_OK); + Module* module = ParseModuleSpecifier(CHECK_OK); + ExpectSemicolon(CHECK_OK); + // TODO(ES6): Do something with the return value + // of ParseModuleSpecifier. + USE(module); + is_export_from = true; + result = factory()->NewEmptyStatement(pos); + break; + } + + case Token::LBRACE: + ParseModuleDeclarationClause(&names, CHECK_OK); + if (CheckContextualKeyword(CStrVector("from"))) { + Module* module = ParseModuleSpecifier(CHECK_OK); + // TODO(ES6): Do something with the return value + // of ParseModuleSpecifier. + USE(module); + is_export_from = true; } ExpectSemicolon(CHECK_OK); result = factory()->NewEmptyStatement(pos); break; - } case Token::FUNCTION: result = ParseFunctionDeclaration(&names, CHECK_OK); @@ -1395,24 +1481,27 @@ Statement* Parser::ParseExportDeclaration(bool* ok) { } } - // Extract declared names into export declarations and interface. - Interface* interface = scope_->interface(); - for (int i = 0; i < names.length(); ++i) { + // TODO(ES6): Handle 'export from' once imports are properly implemented. + // For now we just drop such exports on the floor. + if (!is_export_from) { + // Extract declared names into export declarations and interface. + Interface* interface = scope_->interface(); + for (int i = 0; i < names.length(); ++i) { #ifdef DEBUG - if (FLAG_print_interface_details) - PrintF("# Export %.*s ", names[i]->length(), names[i]->raw_data()); + if (FLAG_print_interface_details) + PrintF("# Export %.*s ", names[i]->length(), names[i]->raw_data()); #endif - Interface* inner = Interface::NewUnknown(zone()); - interface->Add(names[i], inner, zone(), CHECK_OK); - if (!*ok) - return NULL; - VariableProxy* proxy = NewUnresolved(names[i], LET, inner); - USE(proxy); - // TODO(rossberg): Rethink whether we actually need to store export - // declarations (for compilation?). - // ExportDeclaration* declaration = - // factory()->NewExportDeclaration(proxy, scope_, position); - // scope_->AddDeclaration(declaration); + Interface* inner = Interface::NewUnknown(zone()); + interface->Add(names[i], inner, zone(), CHECK_OK); + if (!*ok) return NULL; + VariableProxy* proxy = NewUnresolved(names[i], LET, inner); + USE(proxy); + // TODO(rossberg): Rethink whether we actually need to store export + // declarations (for compilation?). + // ExportDeclaration* declaration = + // factory()->NewExportDeclaration(proxy, scope_, position); + // scope_->AddDeclaration(declaration); + } } DCHECK(result != NULL); diff --git a/src/parser.h b/src/parser.h index fe3a9a8522..e998771831 100644 --- a/src/parser.h +++ b/src/parser.h @@ -751,9 +751,12 @@ class Parser : public ParserBase { Statement* ParseStatementListItem(bool* ok); Module* ParseModule(bool* ok); Statement* ParseModuleItem(bool* ok); - Module* ParseModuleUrl(bool* ok); + Module* ParseModuleSpecifier(bool* ok); Statement* ParseImportDeclaration(bool* ok); Statement* ParseExportDeclaration(bool* ok); + Statement* ParseExportDefault(bool* ok); + void* ParseModuleDeclarationClause(ZoneList* names, + bool* ok); Statement* ParseStatement(ZoneList* labels, bool* ok); Statement* ParseFunctionDeclaration(ZoneList* names, bool* ok); diff --git a/test/cctest/test-parsing.cc b/test/cctest/test-parsing.cc index 1a9c36ce90..594e3773f0 100644 --- a/test/cctest/test-parsing.cc +++ b/test/cctest/test-parsing.cc @@ -4671,11 +4671,24 @@ TEST(ComputedPropertyNameShorthandError) { TEST(BasicImportExportParsing) { - const char kSource[] = - "export let x = 0;" - "import y from 'http://module.com/foo.js';" - "function f() {};" - "f();"; + const char* kSources[] = { + "export let x = 0;", + "export var y = 0;", + "export const z = 0;", + "export function func() { };", + "export class C { };", + "export { };", + "function f() {}; f(); export { f };", + "var a, b, c; export { a, b as baz, c };", + "var d, e; export { d as dreary, e, };", + "import y from 'http://module.com/foo.js';", + "export default function f() {}", + "export default class C {}", + "export default 42", + "var x; export default x = 7", + "export { Q } from 'somemodule.js';", + "export * from 'somemodule.js';" + }; i::Isolate* isolate = CcTest::i_isolate(); i::Factory* factory = isolate->factory(); @@ -4687,38 +4700,101 @@ TEST(BasicImportExportParsing) { isolate->stack_guard()->SetStackLimit(i::GetCurrentStackPosition() - 128 * 1024); - int kProgramByteSize = i::StrLength(kSource); - i::ScopedVector program(kProgramByteSize + 1); - i::SNPrintF(program, "%s", kSource); - i::Handle source = - factory->NewStringFromUtf8(i::CStrVector(program.start())) - .ToHandleChecked(); + for (unsigned i = 0; i < arraysize(kSources); ++i) { + int kProgramByteSize = i::StrLength(kSources[i]); + i::ScopedVector program(kProgramByteSize + 1); + i::SNPrintF(program, "%s", kSources[i]); + i::Handle source = + factory->NewStringFromUtf8(i::CStrVector(program.start())) + .ToHandleChecked(); + + // Show that parsing as a module works + { + i::Handle script = factory->NewScript(source); + i::CompilationInfoWithZone info(script); + i::Parser::ParseInfo parse_info = {isolate->stack_guard()->real_climit(), + isolate->heap()->HashSeed(), + isolate->unicode_cache()}; + i::Parser parser(&info, &parse_info); + parser.set_allow_harmony_classes(true); + parser.set_allow_harmony_modules(true); + parser.set_allow_harmony_scoping(true); + info.MarkAsModule(); + CHECK(parser.Parse()); + } + + // And that parsing a script does not. + { + i::Handle script = factory->NewScript(source); + i::CompilationInfoWithZone info(script); + i::Parser::ParseInfo parse_info = {isolate->stack_guard()->real_climit(), + isolate->heap()->HashSeed(), + isolate->unicode_cache()}; + i::Parser parser(&info, &parse_info); + parser.set_allow_harmony_classes(true); + parser.set_allow_harmony_modules(true); + parser.set_allow_harmony_scoping(true); + info.MarkAsGlobal(); + CHECK(!parser.Parse()); + } + } +} + + +TEST(ImportExportParsingErrors) { + const char* kErrorSources[] = { + "export {", + "var a; export { a", + "var a; export { a,", + "var a; export { a, ;", + "var a; export { a as };", + "var a, b; export { a as , b};", + "export }", + "var foo, bar; export { foo bar };", + "export { foo };", + "export { , };", + "export default;", + "export default var x = 7;", + "export default let x = 7;", + "export default const x = 7;", + "export *;", + "export * from;", + "export { Q } from;", + "export default from 'module.js';", + + // TODO(ES6): These two forms should be supported + "export default function() {};", + "export default class {};" + }; + + i::Isolate* isolate = CcTest::i_isolate(); + i::Factory* factory = isolate->factory(); + + v8::HandleScope handles(CcTest::isolate()); + v8::Handle context = v8::Context::New(CcTest::isolate()); + v8::Context::Scope context_scope(context); + + isolate->stack_guard()->SetStackLimit(i::GetCurrentStackPosition() - + 128 * 1024); + + for (unsigned i = 0; i < arraysize(kErrorSources); ++i) { + int kProgramByteSize = i::StrLength(kErrorSources[i]); + i::ScopedVector program(kProgramByteSize + 1); + i::SNPrintF(program, "%s", kErrorSources[i]); + i::Handle source = + factory->NewStringFromUtf8(i::CStrVector(program.start())) + .ToHandleChecked(); - // Show that parsing as a module works - { i::Handle script = factory->NewScript(source); i::CompilationInfoWithZone info(script); i::Parser::ParseInfo parse_info = {isolate->stack_guard()->real_climit(), isolate->heap()->HashSeed(), isolate->unicode_cache()}; i::Parser parser(&info, &parse_info); + parser.set_allow_harmony_classes(true); parser.set_allow_harmony_modules(true); parser.set_allow_harmony_scoping(true); info.MarkAsModule(); - CHECK(parser.Parse()); - } - - // And that parsing a script does not. - { - i::Handle script = factory->NewScript(source); - i::CompilationInfoWithZone info(script); - i::Parser::ParseInfo parse_info = {isolate->stack_guard()->real_climit(), - isolate->heap()->HashSeed(), - isolate->unicode_cache()}; - i::Parser parser(&info, &parse_info); - parser.set_allow_harmony_modules(true); - parser.set_allow_harmony_scoping(true); - info.MarkAsGlobal(); CHECK(!parser.Parse()); } } diff --git a/test/mjsunit/harmony/module-parsing.js b/test/mjsunit/harmony/module-parsing.js index 2f45326512..fa9e5ec35c 100644 --- a/test/mjsunit/harmony/module-parsing.js +++ b/test/mjsunit/harmony/module-parsing.js @@ -28,18 +28,14 @@ // Flags: --harmony-modules // Check that import/export declarations are rejected in eval or local scope. -assertThrows("export x;", SyntaxError); assertThrows("export let x;", SyntaxError); assertThrows("import x from 'http://url';", SyntaxError); -assertThrows("{ export x; }", SyntaxError); assertThrows("{ export let x; }", SyntaxError); assertThrows("{ import x from 'http://url'; }", SyntaxError); -assertThrows("function f() { export x; }", SyntaxError); assertThrows("function f() { export let x; }", SyntaxError); assertThrows("function f() { import x from 'http://url'; }", SyntaxError); -assertThrows("function f() { { export x; } }", SyntaxError); assertThrows("function f() { { export let x; } }", SyntaxError); assertThrows("function f() { { import x from 'http://url'; } }", SyntaxError);