Implement ParseExportDeclaration per latest ES6 spec draft
authorAdam Klein <adamk@chromium.org>
Wed, 28 Jan 2015 19:18:37 +0000 (11:18 -0800)
committerAdam Klein <adamk@chromium.org>
Wed, 28 Jan 2015 19:18:48 +0000 (19:18 +0000)
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
src/parser.h
test/cctest/test-parsing.cc
test/mjsunit/harmony/module-parsing.js

index dff7de9..9539d38 100644 (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,36 +1370,84 @@ 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);
       break;
@@ -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);
index fe3a9a8..e998771 100644 (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);
index 1a9c36c..594e377 100644 (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_modules(true);
-    parser.set_allow_harmony_scoping(true);
-    info.MarkAsModule();
-    CHECK(parser.Parse());
+    // 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();
 
-  // 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();
+    info.MarkAsModule();
     CHECK(!parser.Parse());
   }
 }
index 2f45326..fa9e5ec 100644 (file)
 // 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);