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:
parent
34601552cf
commit
f7dc15febe
161
src/parser.cc
161
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<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);
|
||||
|
@ -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);
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user