From 020bfc9d938b0e65e3a422feb665d82ad0d6529d Mon Sep 17 00:00:00 2001 From: davidot Date: Sat, 14 Aug 2021 17:42:30 +0200 Subject: [PATCH] LibJS: Parse and partially execute import and export statements We produce the import and export entries as per the spec. However we do not yet verify that named things that are exported are declared somewhere. --- Userland/Libraries/LibJS/AST.cpp | 68 ++++++ Userland/Libraries/LibJS/AST.h | 85 +++++++ Userland/Libraries/LibJS/Parser.cpp | 333 ++++++++++++++++++++++++++++ Userland/Libraries/LibJS/Parser.h | 3 + 4 files changed, 489 insertions(+) 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;