From b7c7d54167d8c743cf91975fd3f073b65290b469 Mon Sep 17 00:00:00 2001 From: davidot Date: Sat, 27 Nov 2021 00:01:23 +0100 Subject: [PATCH] LibJS: Split parsing program to script and module separately This allows us to only perform checks like export bindings existing only for modules. Also this makes it easier to set strict and other state variables with TemporaryChanges. --- Userland/Libraries/LibJS/AST.h | 9 +++- Userland/Libraries/LibJS/Parser.cpp | 72 +++++++++++++++++++++++++---- Userland/Libraries/LibJS/Parser.h | 3 ++ 3 files changed, 74 insertions(+), 10 deletions(-) diff --git a/Userland/Libraries/LibJS/AST.h b/Userland/Libraries/LibJS/AST.h index db28e68fbc..ea508a5ff6 100644 --- a/Userland/Libraries/LibJS/AST.h +++ b/Userland/Libraries/LibJS/AST.h @@ -260,8 +260,8 @@ public: ExportEntry(String export_name, String local_name) : kind(LocalExport) - , export_name(export_name) - , local_or_import_name(local_name) + , export_name(move(export_name)) + , local_or_import_name(move(local_name)) { } }; @@ -279,6 +279,9 @@ public: bool has_export(StringView export_name) const; + bool has_statement() const { return m_statement; } + Vector const& entries() const { return m_entries; } + private: RefPtr m_statement; Vector m_entries; @@ -1218,6 +1221,8 @@ public: virtual bool is_lexical_declaration() const override { return true; } + StringView name() const { return m_class_expression->name(); } + private: NonnullRefPtr m_class_expression; }; diff --git a/Userland/Libraries/LibJS/Parser.cpp b/Userland/Libraries/LibJS/Parser.cpp index 94f09a07a4..4ea3b10a9a 100644 --- a/Userland/Libraries/LibJS/Parser.cpp +++ b/Userland/Libraries/LibJS/Parser.cpp @@ -143,7 +143,7 @@ public: VERIFY(m_top_level_scope); m_top_level_scope->m_node->add_var_scoped_declaration(move(declaration)); } else { - if (m_is_top_level) { + if (m_is_top_level && m_parser.m_program_type == Program::Type::Script) { declaration->for_each_bound_name([&](auto const& name) { m_var_names.set(name); }); @@ -468,16 +468,44 @@ NonnullRefPtr Parser::parse_program(bool starts_in_strict_mode) auto rule_start = push_start(); auto program = adopt_ref(*new Program({ m_filename, rule_start.position(), position() }, m_program_type)); ScopePusher program_scope = ScopePusher::program_scope(*this, *program); - if (starts_in_strict_mode || m_program_type == Program::Type::Module) + + if (m_program_type == Program::Type::Script) + parse_script(program, starts_in_strict_mode); + else + parse_module(program); + + program->source_range().end = position(); + return program; +} + +void Parser::parse_script(Program& program, bool starts_in_strict_mode) +{ + bool strict_before = m_state.strict_mode; + if (starts_in_strict_mode) m_state.strict_mode = true; bool has_use_strict = parse_directive(program); if (m_state.strict_mode || has_use_strict) { - program->set_strict_mode(); + program.set_strict_mode(); m_state.strict_mode = true; } + parse_statement_list(program, AllowLabelledFunction::Yes); + if (!done()) { + expected("statement or declaration"); + consume(); + } + + m_state.strict_mode = strict_before; +} + +void Parser::parse_module(Program& program) +{ + TemporaryChange strict_mode_rollback(m_state.strict_mode, true); + TemporaryChange await_expression_valid_rollback(m_state.await_expression_is_valid, true); + + // Since strict mode is already enabled we skip any directive parsing. while (!done()) { parse_statement_list(program, AllowLabelledFunction::Yes); @@ -487,9 +515,9 @@ NonnullRefPtr Parser::parse_program(bool starts_in_strict_mode) 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)); + program.append_export(parse_export_statement(program)); else - program->append_import(parse_import_statement(*program)); + program.append_import(parse_import_statement(program)); } else { expected("statement or declaration"); @@ -497,8 +525,35 @@ NonnullRefPtr Parser::parse_program(bool starts_in_strict_mode) } } - program->source_range().end = position(); - return program; + for (auto& export_statement : program.exports()) { + if (export_statement.has_statement()) + continue; + for (auto& entry : export_statement.entries()) { + if (entry.kind == ExportStatement::ExportEntry::ModuleRequest) + return; + + auto const& exported_name = entry.local_or_import_name; + bool found = false; + program.for_each_lexically_declared_name([&](auto const& name) { + if (name == exported_name) { + found = true; + return IterationDecision::Break; + } + return IterationDecision::Continue; + }); + if (found) + continue; + program.for_each_var_declared_name([&](auto const& name) { + if (name == exported_name) { + found = true; + return IterationDecision::Break; + } + return IterationDecision::Continue; + }); + if (!found) + syntax_error(String::formatted("'{}' is not declared", exported_name)); + } + } } NonnullRefPtr Parser::parse_declaration() @@ -4051,12 +4106,13 @@ NonnullRefPtr Parser::parse_export_statement(Program& program) } else if (match_declaration()) { auto decl_position = position(); auto declaration = parse_declaration(); + m_state.current_scope_pusher->add_declaration(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 }); + entries_with_location.append({ { class_declaration.name(), class_declaration.name() }, class_declaration.source_range().start }); } else { VERIFY(is(*declaration)); auto& variables = static_cast(*declaration); diff --git a/Userland/Libraries/LibJS/Parser.h b/Userland/Libraries/LibJS/Parser.h index 60a767b2c0..7153f36aa9 100644 --- a/Userland/Libraries/LibJS/Parser.h +++ b/Userland/Libraries/LibJS/Parser.h @@ -176,6 +176,9 @@ public: private: friend class ScopePusher; + void parse_script(Program& program, bool starts_in_strict_mode); + void parse_module(Program& program); + Associativity operator_associativity(TokenType) const; bool match_expression() const; bool match_unary_prefixed_expression() const;