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}
This commit is contained in:
Adam Klein 2015-01-28 11:18:37 -08:00
parent 34601552cf
commit f7dc15febe
4 changed files with 232 additions and 68 deletions

View File

@ -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<const AstRawString*>* 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<const AstRawString*> 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);

View File

@ -751,9 +751,12 @@ class Parser : public ParserBase<ParserTraits> {
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<const AstRawString*>* names,
bool* ok);
Statement* ParseStatement(ZoneList<const AstRawString*>* labels, bool* ok);
Statement* ParseFunctionDeclaration(ZoneList<const AstRawString*>* names,
bool* ok);

View File

@ -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<char> program(kProgramByteSize + 1);
i::SNPrintF(program, "%s", kSource);
i::Handle<i::String> source =
factory->NewStringFromUtf8(i::CStrVector(program.start()))
.ToHandleChecked();
for (unsigned i = 0; i < arraysize(kSources); ++i) {
int kProgramByteSize = i::StrLength(kSources[i]);
i::ScopedVector<char> program(kProgramByteSize + 1);
i::SNPrintF(program, "%s", kSources[i]);
i::Handle<i::String> source =
factory->NewStringFromUtf8(i::CStrVector(program.start()))
.ToHandleChecked();
// Show that parsing as a module works
{
i::Handle<i::Script> 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<i::Script> 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<v8::Context> 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<char> program(kProgramByteSize + 1);
i::SNPrintF(program, "%s", kErrorSources[i]);
i::Handle<i::String> source =
factory->NewStringFromUtf8(i::CStrVector(program.start()))
.ToHandleChecked();
// Show that parsing as a module works
{
i::Handle<i::Script> 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<i::Script> 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());
}
}

View File

@ -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);