diff --git a/Userland/Libraries/LibJS/AST.cpp b/Userland/Libraries/LibJS/AST.cpp index 32f7f21f5f..78d1694d98 100644 --- a/Userland/Libraries/LibJS/AST.cpp +++ b/Userland/Libraries/LibJS/AST.cpp @@ -2434,4 +2434,72 @@ void ScopeNode::add_hoisted_function(NonnullRefPtr hoisted_ { m_hoisted_functions.append(hoisted_function); } + +Value ImportStatement::execute(Interpreter& interpreter, GlobalObject&) const +{ + InterpreterNodeScope node_scope { interpreter, *this }; + dbgln("Modules are not fully supported yet!"); + TODO(); + return {}; +} + +Value ExportStatement::execute(Interpreter& interpreter, GlobalObject& global_object) const +{ + InterpreterNodeScope node_scope { interpreter, *this }; + if (m_statement) + return m_statement->execute(interpreter, global_object); + + return {}; +} + +void ExportStatement::dump(int indent) const +{ + ASTNode::dump(indent); + print_indent(indent + 1); + outln("(ExportEntries)"); + + auto string_or_null = [](String const& string) -> String { + if (string.is_empty()) { + return "null"; + } + return String::formatted("\"{}\"", string); + }; + + for (auto& entry : m_entries) { + print_indent(indent + 2); + outln("ModuleRequest: {}, ImportName: {}, LocalName: {}, ExportName: {}", string_or_null(entry.module_request), entry.kind == ExportEntry::ModuleRequest ? string_or_null(entry.local_or_import_name) : "null", entry.kind != ExportEntry::ModuleRequest ? string_or_null(entry.local_or_import_name) : "null", string_or_null(entry.export_name)); + } +} + +void ImportStatement::dump(int indent) const +{ + ASTNode::dump(indent); + print_indent(indent + 1); + if (m_entries.is_empty()) { + // direct from "module" import + outln("Entire module '{}'", m_module_request); + } else { + outln("(ExportEntries) from {}", m_module_request); + + for (auto& entry : m_entries) { + print_indent(indent + 2); + outln("ImportName: {}, LocalName: {}", entry.import_name, entry.local_name); + } + } +} + +bool ExportStatement::has_export(StringView export_name) const +{ + return any_of(m_entries.begin(), m_entries.end(), [&](auto& entry) { + return entry.export_name == export_name; + }); +} + +bool ImportStatement::has_bound_name(StringView name) const +{ + return any_of(m_entries.begin(), m_entries.end(), [&](auto& entry) { + return entry.local_name == name; + }); +} + } diff --git a/Userland/Libraries/LibJS/AST.h b/Userland/Libraries/LibJS/AST.h index 047f1843bc..838450e8a4 100644 --- a/Userland/Libraries/LibJS/AST.h +++ b/Userland/Libraries/LibJS/AST.h @@ -166,6 +166,73 @@ private: NonnullRefPtrVector m_hoisted_functions; }; +class ImportStatement final : public Statement { +public: + struct ImportEntry { + String import_name; + String local_name; + }; + + explicit ImportStatement(SourceRange source_range, StringView from_module, Vector entries = {}) + : Statement(source_range) + , m_module_request(from_module) + , m_entries(move(entries)) + { + } + + virtual Value execute(Interpreter&, GlobalObject&) const override; + + virtual void dump(int indent) const override; + + bool has_bound_name(StringView name) const; + +private: + String m_module_request; + Vector m_entries; +}; + +class ExportStatement final : public Statement { +public: + struct ExportEntry { + enum Kind { + ModuleRequest, + LocalExport + } kind; + // Can always have + String export_name; + + // Only if module request + String module_request; + + // Has just one of ones below + String local_or_import_name; + + ExportEntry(String export_name, String local_name) + : kind(LocalExport) + , export_name(export_name) + , local_or_import_name(local_name) + { + } + }; + + explicit ExportStatement(SourceRange source_range, RefPtr statement, Vector entries) + : Statement(source_range) + , m_statement(move(statement)) + , m_entries(move(entries)) + { + } + + virtual Value execute(Interpreter&, GlobalObject&) const override; + + virtual void dump(int indent) const override; + + bool has_export(StringView export_name) const; + +private: + RefPtr m_statement; + Vector m_entries; +}; + class Program final : public ScopeNode { public: enum class Type { @@ -186,11 +253,29 @@ public: Type type() const { return m_type; } + void append_import(NonnullRefPtr import_statement) + { + m_imports.append(import_statement); + append(import_statement); + } + + void append_export(NonnullRefPtr export_statement) + { + m_exports.append(export_statement); + append(export_statement); + } + + NonnullRefPtrVector const& imports() const { return m_imports; } + NonnullRefPtrVector const& exports() const { return m_exports; } + private: virtual bool is_program() const override { return true; } bool m_is_strict_mode { false }; Type m_type { Type::Script }; + + NonnullRefPtrVector m_imports; + NonnullRefPtrVector m_exports; }; class BlockStatement final : public ScopeNode { diff --git a/Userland/Libraries/LibJS/Parser.cpp b/Userland/Libraries/LibJS/Parser.cpp index 9b6a7ff640..2de0fde491 100644 --- a/Userland/Libraries/LibJS/Parser.cpp +++ b/Userland/Libraries/LibJS/Parser.cpp @@ -317,6 +317,14 @@ NonnullRefPtr Parser::parse_program(bool starts_in_strict_mode) parsing_directives = false; } + } else if (match_export_or_import()) { + VERIFY(m_state.current_token.type() == TokenType::Export || m_state.current_token.type() == TokenType::Import); + if (m_state.current_token.type() == TokenType::Export) + program->append_export(parse_export_statement(*program)); + else + program->append_import(parse_import_statement(*program)); + + parsing_directives = false; } else { expected("statement or declaration"); consume(); @@ -2596,6 +2604,13 @@ bool Parser::match_statement() const || type == TokenType::Semicolon; } +bool Parser::match_export_or_import() const +{ + auto type = m_state.current_token.type(); + return type == TokenType::Export + || type == TokenType::Import; +} + bool Parser::match_declaration() const { auto type = m_state.current_token.type(); @@ -2808,4 +2823,322 @@ void Parser::check_identifier_name_for_assignment_validity(StringView name, bool } } +NonnullRefPtr Parser::parse_import_statement(Program& program) +{ + auto rule_start = push_start(); + if (program.type() != Program::Type::Module) + syntax_error("Cannot use import statement outside a module"); + + consume(TokenType::Import); + + if (match(TokenType::StringLiteral)) { + auto module_name = consume(TokenType::StringLiteral).value(); + return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, module_name); + } + + auto match_imported_binding = [&] { + return match_identifier() || match(TokenType::Yield) || match(TokenType::Await); + }; + + auto match_as = [&] { + return match(TokenType::Identifier) && m_state.current_token.value() == "as"sv; + }; + + bool continue_parsing = true; + + struct ImportWithLocation { + ImportStatement::ImportEntry entry; + Position position; + }; + + Vector entries_with_location; + + if (match_imported_binding()) { + auto id_position = position(); + auto bound_name = consume().value(); + entries_with_location.append({ { "default", bound_name }, id_position }); + + if (match(TokenType::Comma)) { + consume(TokenType::Comma); + } else { + continue_parsing = false; + } + } + + if (!continue_parsing) { + // skip the rest + } else if (match(TokenType::Asterisk)) { + consume(TokenType::Asterisk); + + if (!match_as()) + syntax_error(String::formatted("Unexpected token: {}", m_state.current_token.name())); + + consume(TokenType::Identifier); + + if (match_imported_binding()) { + auto namespace_position = position(); + auto namespace_name = consume().value(); + entries_with_location.append({ { "*", namespace_name }, namespace_position }); + } else { + syntax_error(String::formatted("Unexpected token: {}", m_state.current_token.name())); + } + + } else if (match(TokenType::CurlyOpen)) { + consume(TokenType::CurlyOpen); + + while (!done() && !match(TokenType::CurlyClose)) { + if (match_identifier_name()) { + auto require_as = !match_imported_binding(); + auto name_position = position(); + auto name = consume().value(); + + if (match_as()) { + consume(TokenType::Identifier); + + auto alias_position = position(); + auto alias = consume_identifier().value(); + check_identifier_name_for_assignment_validity(alias); + + entries_with_location.append({ { name, alias }, alias_position }); + } else if (require_as) { + syntax_error(String::formatted("Unexpected reserved word '{}'", name)); + } else { + check_identifier_name_for_assignment_validity(name); + + entries_with_location.append({ { name, name }, name_position }); + } + } else { + expected("identifier"); + break; + } + + if (!match(TokenType::Comma)) + break; + + consume(TokenType::Comma); + } + + consume(TokenType::CurlyClose); + } else { + expected("import clauses"); + } + + auto from_statement = consume(TokenType::Identifier).value(); + if (from_statement != "from"sv) + syntax_error(String::formatted("Expected 'from' got {}", from_statement)); + + auto module_name = consume(TokenType::StringLiteral).value(); + + Vector entries; + entries.ensure_capacity(entries_with_location.size()); + + for (auto& entry : entries_with_location) { + for (auto& import_statement : program.imports()) { + if (import_statement.has_bound_name(entry.entry.local_name)) + syntax_error(String::formatted("Identifier '{}' already declared", entry.entry.local_name), entry.position); + } + + for (auto& new_entry : entries) { + if (new_entry.local_name == entry.entry.local_name) + syntax_error(String::formatted("Identifier '{}' already declared", entry.entry.local_name), entry.position); + } + + entries.append(move(entry.entry)); + } + + return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, module_name, move(entries)); +} + +NonnullRefPtr Parser::parse_export_statement(Program& program) +{ + auto rule_start = push_start(); + if (program.type() != Program::Type::Module) + syntax_error("Cannot use export statement outside a module"); + + auto match_as = [&] { + return match(TokenType::Identifier) && m_state.current_token.value() == "as"sv; + }; + + auto match_from = [&] { + return match(TokenType::Identifier) && m_state.current_token.value() == "from"sv; + }; + + consume(TokenType::Export); + + struct EntryAndLocation { + ExportStatement::ExportEntry entry; + Position position; + + void to_module_request(String from_module) + { + entry.kind = ExportStatement::ExportEntry::Kind::ModuleRequest; + entry.module_request = from_module; + } + }; + + Vector entries_with_location; + + RefPtr expression = {}; + + if (match(TokenType::Default)) { + auto default_position = position(); + consume(TokenType::Default); + + String local_name; + + if (match(TokenType::Class)) { + auto class_expression = parse_class_expression(false); + local_name = class_expression->name(); + expression = move(class_expression); + } else if (match(TokenType::Function)) { + auto func_expr = parse_function_node(); + local_name = func_expr->name(); + expression = move(func_expr); + // TODO: Allow async function + } else if (match_expression()) { + expression = parse_expression(2); + consume_or_insert_semicolon(); + local_name = "*default*"; + } else { + expected("Declaration or assignment expression"); + } + + entries_with_location.append({ { "default", local_name }, default_position }); + } else { + enum FromSpecifier { + NotAllowed, + Optional, + Required + } check_for_from { NotAllowed }; + + if (match(TokenType::Asterisk)) { + auto asterisk_position = position(); + consume(TokenType::Asterisk); + + if (match_as()) { + consume(TokenType::Identifier); + if (match_identifier_name()) { + auto namespace_position = position(); + auto exported_name = consume().value(); + entries_with_location.append({ { exported_name, "*" }, namespace_position }); + } else { + expected("identifier"); + } + } else { + entries_with_location.append({ { {}, "*" }, asterisk_position }); + } + check_for_from = Required; + } else if (match_declaration()) { + auto decl_position = position(); + auto declaration = parse_declaration(); + if (is(*declaration)) { + auto& func = static_cast(*declaration); + entries_with_location.append({ { func.name(), func.name() }, func.source_range().start }); + } else if (is(*declaration)) { + auto& class_declaration = static_cast(*declaration); + entries_with_location.append({ { class_declaration.class_name(), class_declaration.class_name() }, class_declaration.source_range().start }); + } else { + VERIFY(is(*declaration)); + auto& variables = static_cast(*declaration); + for (auto& decl : variables.declarations()) { + decl.target().visit( + [&](NonnullRefPtr const& identifier) { + entries_with_location.append({ { identifier->string(), identifier->string() }, identifier->source_range().start }); + }, + [&](NonnullRefPtr const& binding) { + binding->for_each_bound_name([&](auto& name) { + entries_with_location.append({ { name, name }, decl_position }); + }); + }); + } + } + expression = declaration; + } else if (match(TokenType::Var)) { + auto variable_position = position(); + auto variable_declaration = parse_variable_declaration(); + for (auto& decl : variable_declaration->declarations()) { + decl.target().visit( + [&](NonnullRefPtr const& identifier) { + entries_with_location.append({ { identifier->string(), identifier->string() }, identifier->source_range().start }); + }, + [&](NonnullRefPtr const& binding) { + binding->for_each_bound_name([&](auto& name) { + entries_with_location.append({ { name, name }, variable_position }); + }); + }); + } + expression = variable_declaration; + } else if (match(TokenType::CurlyOpen)) { + consume(TokenType::CurlyOpen); + + while (!done() && !match(TokenType::CurlyClose)) { + if (match_identifier_name()) { + auto identifier_position = position(); + auto identifier = consume().value(); + + if (match_as()) { + consume(TokenType::Identifier); + if (match_identifier_name()) { + auto export_name = consume().value(); + entries_with_location.append({ { export_name, identifier }, identifier_position }); + } else { + expected("identifier name"); + } + } else { + entries_with_location.append({ { identifier, identifier }, identifier_position }); + } + } else { + expected("identifier"); + break; + } + + if (!match(TokenType::Comma)) + break; + + consume(TokenType::Comma); + } + + consume(TokenType::CurlyClose); + check_for_from = Optional; + } else { + syntax_error("Unexpected token 'export'", rule_start.position()); + } + + if (check_for_from != NotAllowed && match_from()) { + consume(TokenType::Identifier); + if (match(TokenType::StringLiteral)) { + auto from_specifier = consume().value(); + for (auto& entry : entries_with_location) + entry.to_module_request(from_specifier); + } else { + expected("ModuleSpecifier"); + } + } else if (check_for_from == Required) { + expected("from"); + } + + if (check_for_from != NotAllowed) + consume_or_insert_semicolon(); + } + + Vector entries; + entries.ensure_capacity(entries_with_location.size()); + + for (auto& entry : entries_with_location) { + for (auto& export_statement : program.exports()) { + if (export_statement.has_export(entry.entry.export_name)) + syntax_error(String::formatted("Duplicate export with name: '{}'", entry.entry.export_name), entry.position); + } + + for (auto& new_entry : entries) { + if (new_entry.export_name == entry.entry.export_name) + syntax_error(String::formatted("Duplicate export with name: '{}'", entry.entry.export_name), entry.position); + } + + entries.append(move(entry.entry)); + } + + return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, move(expression), move(entries)); +} + } diff --git a/Userland/Libraries/LibJS/Parser.h b/Userland/Libraries/LibJS/Parser.h index 625542ee6d..121408dca7 100644 --- a/Userland/Libraries/LibJS/Parser.h +++ b/Userland/Libraries/LibJS/Parser.h @@ -87,6 +87,8 @@ public: NonnullRefPtr parse_property_key(); NonnullRefPtr parse_assignment_expression(AssignmentOp, NonnullRefPtr lhs, int min_precedence, Associativity); NonnullRefPtr parse_identifier(); + NonnullRefPtr parse_import_statement(Program& program); + NonnullRefPtr parse_export_statement(Program& program); RefPtr try_parse_arrow_function_expression(bool expect_parens); RefPtr try_parse_labelled_statement(); @@ -150,6 +152,7 @@ private: bool match_unary_prefixed_expression() const; bool match_secondary_expression(const Vector& forbidden = {}) const; bool match_statement() const; + bool match_export_or_import() const; bool match_declaration() const; bool match_variable_declaration() const; bool match_identifier() const;