diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 4de784ea04be..a078a94b681a 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -6447,6 +6447,11 @@ frontend::EmitTree(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) : EmitVariables(cx, bce, pn, InitializeVars); break; + case PNK_IMPORT: + // TODO: Implement emitter support for modules + bce->reportError(nullptr, JSMSG_MODULES_NOT_IMPLEMENTED); + return false; + case PNK_ARRAYPUSH: { int slot; diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h index 7bde5f46eca9..b638d76daeff 100644 --- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -295,6 +295,16 @@ class FullParseHandler return new_(PNK_SEMI, JSOP_NOP, pos, (ParseNode *) nullptr); } + ParseNode *newImportDeclaration(ParseNode *importSpecSet, + ParseNode *moduleSpec, const TokenPos &pos) + { + ParseNode *pn = new_(PNK_IMPORT, JSOP_NOP, pos, + importSpecSet, moduleSpec); + if (!pn) + return null(); + return pn; + } + ParseNode *newExprStatement(ParseNode *expr, uint32_t end) { JS_ASSERT(expr->pn_pos.end <= end); return new_(PNK_SEMI, JSOP_NOP, TokenPos(expr->pn_pos.begin, end), expr); diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index bb981cb8cbaf..4b8cb1bf7943 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -128,6 +128,9 @@ class UpvarCookie F(ARRAYPUSH) \ F(LEXICALSCOPE) \ F(LET) \ + F(IMPORT) \ + F(IMPORT_SPEC_LIST) \ + F(IMPORT_SPEC) \ F(SEQ) \ F(FORIN) \ F(FOROF) \ diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 0cb25dbac7e3..d191d17b8ffe 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -3628,6 +3628,134 @@ Parser::letStatement() return SyntaxParseHandler::NodeFailure; } +template +typename ParseHandler::Node +Parser::importDeclaration() +{ + JS_ASSERT(tokenStream.currentToken().type == TOK_IMPORT); + + if (pc->sc->isFunctionBox() || !pc->atBodyLevel()) { + report(ParseError, false, null(), JSMSG_IMPORT_DECL_AT_TOP_LEVEL); + return null(); + } + + uint32_t begin = pos().begin; + TokenKind tt = tokenStream.getToken(); + + Node importSpecSet = handler.newList(PNK_IMPORT_SPEC_LIST); + if (!importSpecSet) + return null(); + + if (tt == TOK_NAME || tt == TOK_LC) { + if (tt == TOK_NAME) { + // Handle the form |import a from 'b'|, by adding a single import + // specifier to the list, with 'default' as the import name and + // 'a' as the binding name. This is equivalent to + // |import { default as a } from 'b'|. + Node importName = newName(context->names().default_); + if (!importName) + return null(); + + Node bindingName = newName(tokenStream.currentName()); + if (!bindingName) + return null(); + + Node importSpec = handler.newBinary(PNK_IMPORT_SPEC, importName, bindingName); + if (!importSpec) + return null(); + + handler.addList(importSpecSet, importSpec); + } else { + do { + // Handle the forms |import {} from 'a'| and + // |import { ..., } from 'a'| (where ... is non empty), by + // escaping the loop early if the next token is }. + tt = tokenStream.peekToken(TokenStream::KeywordIsName); + if (tt == TOK_ERROR) + return null(); + if (tt == TOK_RC) + break; + + // If the next token is a keyword, the previous call to + // peekToken matched it as a TOK_NAME, and put it in the + // lookahead buffer, so this call will match keywords as well. + MUST_MATCH_TOKEN(TOK_NAME, JSMSG_NO_IMPORT_NAME); + Node importName = newName(tokenStream.currentName()); + if (!importName) + return null(); + + if (tokenStream.getToken() == TOK_NAME && + tokenStream.currentName() == context->names().as) + { + if (tokenStream.getToken() != TOK_NAME) { + report(ParseError, false, null(), JSMSG_NO_BINDING_NAME); + return null(); + } + } else { + // Keywords cannot be bound to themselves, so an import name + // that is a keyword is a syntax error if it is not followed + // by the keyword 'as'. + if (IsKeyword(importName->name())) { + JSAutoByteString bytes; + if (!AtomToPrintableString(context, importName->name(), &bytes)) + return null(); + report(ParseError, false, null(), JSMSG_AS_AFTER_RESERVED_WORD, bytes.ptr()); + return null(); + } + tokenStream.ungetToken(); + } + Node bindingName = newName(tokenStream.currentName()); + if (!bindingName) + return null(); + + Node importSpec = handler.newBinary(PNK_IMPORT_SPEC, importName, bindingName); + if (!importSpec) + return null(); + + handler.addList(importSpecSet, importSpec); + } while (tokenStream.matchToken(TOK_COMMA)); + + MUST_MATCH_TOKEN(TOK_RC, JSMSG_RC_AFTER_IMPORT_SPEC_LIST); + } + + if (tokenStream.getToken() != TOK_NAME || + tokenStream.currentName() != context->names().from) + { + report(ParseError, false, null(), JSMSG_FROM_AFTER_IMPORT_SPEC_SET); + return null(); + } + + MUST_MATCH_TOKEN(TOK_STRING, JSMSG_MODULE_SPEC_AFTER_FROM); + } else { + if (tt != TOK_STRING) { + report(ParseError, false, null(), JSMSG_DECLARATION_AFTER_IMPORT); + return null(); + } + + // Handle the form |import 'a'| by leaving the list empty. This is + // equivalent to |import {} from 'a'|. + importSpecSet->pn_pos.end = importSpecSet->pn_pos.begin; + } + + Node moduleSpec = stringLiteral(); + if (!moduleSpec) + return null(); + + if (!MatchOrInsertSemicolon(tokenStream)) + return null(); + + return handler.newImportDeclaration(importSpecSet, moduleSpec, + TokenPos(begin, pos().end)); +} + +template<> +SyntaxParseHandler::Node +Parser::importDeclaration() +{ + JS_ALWAYS_FALSE(abortIfSyntaxParser()); + return SyntaxParseHandler::NodeFailure; +} + template typename ParseHandler::Node Parser::expressionStatement() @@ -4907,6 +5035,8 @@ Parser::statement(bool canHaveDirectives) case TOK_LET: return letStatement(); + case TOK_IMPORT: + return importDeclaration(); case TOK_SEMI: return handler.newEmptyStatement(pos()); case TOK_IF: diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index 2de501307e5b..119a6892d434 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -512,6 +512,7 @@ class Parser : private AutoGCRooter, public StrictModeGetter Node debuggerStatement(); Node letStatement(); + Node importDeclaration(); Node expressionStatement(); Node variables(ParseNodeKind kind, bool *psimple = nullptr, StaticBlockObject *blockObj = nullptr, diff --git a/js/src/js.msg b/js/src/js.msg index 15986cd569c7..8bd4f8bd0556 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -417,3 +417,13 @@ MSG_DEF(JSMSG_TYPEDOBJECT_NO_SUCH_PROP, 363, 1, JSEXN_TYPEERR, "No such property MSG_DEF(JSMSG_TYPEDOBJECT_HANDLE_BAD_ARGS, 364, 2, JSEXN_TYPEERR, "argument {0} invalid: expected {1}") MSG_DEF(JSMSG_TYPEDOBJECT_HANDLE_UNATTACHED, 365, 0, JSEXN_TYPEERR, "handle unattached") MSG_DEF(JSMSG_TYPEDOBJECT_HANDLE_BAD_TYPE, 366, 0, JSEXN_TYPEERR, "handle moved to destination of incorrect type") + +MSG_DEF(JSMSG_IMPORT_DECL_AT_TOP_LEVEL, 367, 0, JSEXN_SYNTAXERR, "import declarations may only appear at top level") +MSG_DEF(JSMSG_NO_IMPORT_NAME, 368, 0, JSEXN_SYNTAXERR, "missing import name") +MSG_DEF(JSMSG_AS_AFTER_RESERVED_WORD, 369, 1, JSEXN_SYNTAXERR, "missing keyword 'as' after reserved word '{0}'") +MSG_DEF(JSMSG_NO_BINDING_NAME, 370, 0, JSEXN_SYNTAXERR, "missing binding name") +MSG_DEF(JSMSG_RC_AFTER_IMPORT_SPEC_LIST, 371, 0, JSEXN_SYNTAXERR, "missing '}' after module specifier list") +MSG_DEF(JSMSG_FROM_AFTER_IMPORT_SPEC_SET, 372, 0, JSEXN_SYNTAXERR, "missing keyword 'from' after import specifier set") +MSG_DEF(JSMSG_DECLARATION_AFTER_IMPORT, 373, 0, JSEXN_SYNTAXERR, "missing declaration after 'import' keyword") +MSG_DEF(JSMSG_MODULE_SPEC_AFTER_FROM, 374, 0, JSEXN_SYNTAXERR, "missing module specifier after 'from' keyword") +MSG_DEF(JSMSG_MODULES_NOT_IMPLEMENTED, 375, 0, JSEXN_SYNTAXERR, "modules are not implemented yet") diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h index f4f3e03ba8bd..6406291bfa6b 100644 --- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -15,6 +15,7 @@ macro(anonymous, anonymous, "anonymous") \ macro(apply, apply, "apply") \ macro(arguments, arguments, "arguments") \ + macro(as, as, "as") \ macro(ArrayType, ArrayType, "ArrayType") \ macro(buffer, buffer, "buffer") \ macro(builder, builder, "builder") \ @@ -43,6 +44,7 @@ macro(DateTimeFormatFormatGet, DateTimeFormatFormatGet, "Intl_DateTimeFormat_format_get") \ macro(decodeURI, decodeURI, "decodeURI") \ macro(decodeURIComponent, decodeURIComponent, "decodeURIComponent") \ + macro(default_, default_, "default") \ macro(defineProperty, defineProperty, "defineProperty") \ macro(defineGetter, defineGetter, "__defineGetter__") \ macro(defineSetter, defineSetter, "__defineSetter__") \ @@ -68,6 +70,7 @@ macro(float32, float32, "float32") \ macro(float64, float64, "float64") \ macro(format, format, "format") \ + macro(from, from, "from") \ macro(get, get, "get") \ macro(getInternals, getInternals, "getInternals") \ macro(getOwnPropertyDescriptor, getOwnPropertyDescriptor, "getOwnPropertyDescriptor") \ diff --git a/js/src/vm/Keywords.h b/js/src/vm/Keywords.h index f1d7211b2029..a6c867e79214 100644 --- a/js/src/vm/Keywords.h +++ b/js/src/vm/Keywords.h @@ -41,12 +41,12 @@ macro(void, void_, TOK_VOID, JSVERSION_DEFAULT) \ macro(while, while_, TOK_WHILE, JSVERSION_DEFAULT) \ macro(with, with, TOK_WITH, JSVERSION_DEFAULT) \ + macro(import, import, TOK_IMPORT, JSVERSION_DEFAULT) \ /* Reserved keywords. */ \ macro(class, class_, TOK_RESERVED, JSVERSION_DEFAULT) \ macro(enum, enum_, TOK_RESERVED, JSVERSION_DEFAULT) \ macro(export, export, TOK_RESERVED, JSVERSION_DEFAULT) \ macro(extends, extends, TOK_RESERVED, JSVERSION_DEFAULT) \ - macro(import, import, TOK_RESERVED, JSVERSION_DEFAULT) \ macro(super, super, TOK_RESERVED, JSVERSION_DEFAULT) \ /* Future reserved keywords, but only in strict mode. */ \ macro(implements, implements, TOK_STRICT_RESERVED, JSVERSION_DEFAULT) \