diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Forward.h b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Forward.h index 0623764ad5..da75213ceb 100644 --- a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Forward.h +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Forward.h @@ -63,6 +63,8 @@ class AlgorithmStep; class AlgorithmStepList; class Algorithm; class SpecFunction; +class SpecificationClause; +class Specification; // DiagnosticEngine.h struct LogicalLocation; diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/Lexer.h b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/Lexer.h index 6f039d850d..9f9695d36d 100644 --- a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/Lexer.h +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/Lexer.h @@ -13,6 +13,8 @@ namespace JSSpecCompiler { inline constexpr StringView tag_emu_alg = "emu-alg"sv; inline constexpr StringView tag_emu_clause = "emu-clause"sv; +inline constexpr StringView tag_emu_import = "emu-import"sv; +inline constexpr StringView tag_emu_intro = "emu-intro"sv; inline constexpr StringView tag_emu_val = "emu-val"sv; inline constexpr StringView tag_emu_xref = "emu-xref"sv; inline constexpr StringView tag_h1 = "h1"sv; @@ -20,6 +22,7 @@ inline constexpr StringView tag_li = "li"sv; inline constexpr StringView tag_ol = "ol"sv; inline constexpr StringView tag_p = "p"sv; inline constexpr StringView tag_span = "span"sv; +inline constexpr StringView tag_specification = "specification"sv; inline constexpr StringView tag_var = "var"sv; inline constexpr StringView attribute_aoid = "aoid"sv; diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/ParseError.cpp b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/ParseError.cpp index 81f5b60fef..9d748c845c 100644 --- a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/ParseError.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/ParseError.cpp @@ -5,6 +5,7 @@ */ #include "Parser/ParseError.h" +#include "DiagnosticEngine.h" namespace JSSpecCompiler { @@ -27,7 +28,7 @@ NonnullRefPtr ParseError::create(ErrorOr message, XML::Node String ParseError::to_string() const { StringBuilder builder; - builder.appendff("error: {}\n", m_message); + builder.appendff("{}\n", m_message); XML::Node const* current = m_node; while (current != nullptr) { @@ -48,4 +49,9 @@ String ParseError::to_string() const return MUST(builder.to_string()); } +XML::Offset ParseError::offset() const +{ + return m_node->offset; +} + } diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/ParseError.h b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/ParseError.h index 76c44eed7f..50abc39700 100644 --- a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/ParseError.h +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/ParseError.h @@ -24,6 +24,7 @@ public: static NonnullRefPtr create(ErrorOr message, XML::Node const* node); String to_string() const; + XML::Offset offset() const; private: String m_message; diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/SpecParser.cpp b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/SpecParser.cpp index 8edf01dda0..ed640d117c 100644 --- a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/SpecParser.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/SpecParser.cpp @@ -138,16 +138,127 @@ ParseErrorOr Algorithm::create(XML::Node const* node) return algorithm; } -ParseErrorOr SpecFunction::create(XML::Node const* element) +NonnullOwnPtr SpecificationClause::create(SpecificationParsingContext& ctx, XML::Node const* element) +{ + return ctx.with_new_logical_scope([&] { + VERIFY(element->as_element().name == tag_emu_clause); + + SpecificationClause specification_clause; + specification_clause.parse(ctx, element); + + OwnPtr result; + + specification_clause.m_header.header.visit( + [&](AK::Empty const&) { + result = make(move(specification_clause)); + }, + [&](ClauseHeader::FunctionDefinition const&) { + result = make(move(specification_clause)); + }); + + if (!result->post_initialize(ctx, element)) + result = make(move(*result)); + + return result.release_nonnull(); + }); +} + +void SpecificationClause::collect_into(TranslationUnitRef translation_unit) +{ + do_collect(translation_unit); + for (auto& subclause : m_subclauses) + subclause->collect_into(translation_unit); +} + +ParseErrorOr SpecificationClause::parse_header(XML::Node const* element) +{ + VERIFY(element->as_element().name == tag_h1); + auto tokens = TRY(tokenize_tree(element)); + TextParser parser(tokens.tokens, element); + m_header = TRY(parser.parse_clause_header()); + return {}; +} + +void SpecificationClause::parse(SpecificationParsingContext& ctx, XML::Node const* element) +{ + u32 child_index = 0; + + Optional> header_parse_error; + + for (auto const& child : element->as_element().children) { + child->content.visit( + [&](XML::Node::Element const& element) { + if (child_index == 0) { + if (element.name != tag_h1) { + ctx.diag().error(ctx.location_from_xml_offset(child->offset), + "

must be the first child of "); + return; + } + + if (auto error = parse_header(child); error.is_error()) + header_parse_error = error.release_error(); + else + ctx.current_logical_scope().section = MUST(String::from_utf8(m_header.section_number)); + } else { + if (element.name == tag_emu_clause) { + m_subclauses.append(create(ctx, child)); + return; + } + if (header_parse_error.has_value()) { + ctx.diag().warn(ctx.location_from_xml_offset(child->offset), + "node content will be ignored since section header was not parsed successfully"); + // TODO: Integrate backtracing parser errors better + ctx.diag().note(ctx.location_from_xml_offset(header_parse_error.value()->offset()), + "{}", header_parse_error.value()->to_string()); + header_parse_error.clear(); + } + } + ++child_index; + }, + [&](XML::Node::Text const&) { + if (!contains_empty_text(child)) { + ctx.diag().error(ctx.location_from_xml_offset(child->offset), + "non-empty text node should not be a child of "); + } + }, + [&](auto) {}); + } +} + +bool SpecFunction::post_initialize(SpecificationParsingContext& ctx, XML::Node const* element) +{ + auto initialization_result = do_post_initialize(ctx, element); + if (initialization_result.is_error()) { + // TODO: Integrate backtracing parser errors better + ctx.diag().error(ctx.location_from_xml_offset(initialization_result.error()->offset()), + "{}", initialization_result.error()->to_string()); + return false; + } + return true; +} + +void SpecFunction::do_collect(TranslationUnitRef translation_unit) +{ + translation_unit->adopt_function(make_ref_counted(m_name, m_algorithm.m_tree, move(m_arguments))); +} + +ParseErrorOr SpecFunction::do_post_initialize(SpecificationParsingContext& ctx, XML::Node const* element) { VERIFY(element->as_element().name == tag_emu_clause); - SpecFunction result; - result.m_id = TRY(get_attribute_by_name(element, attribute_id)); - result.m_name = TRY(get_attribute_by_name(element, attribute_aoid)); + m_id = TRY(get_attribute_by_name(element, attribute_id)); + m_name = TRY(get_attribute_by_name(element, attribute_aoid)); + + m_section_number = m_header.section_number; + auto const& [function_name, arguments] = m_header.header.get(); + + if (m_name != function_name) { + ctx.diag().warn(ctx.location_from_xml_offset(element->offset), + "function name in header and [aoid] do not match"); + } + m_arguments = arguments; u32 children_count = 0; - bool has_definition = false; XML::Node const* algorithm_node = nullptr; XML::Node const* prose_node = nullptr; @@ -155,12 +266,9 @@ ParseErrorOr SpecFunction::create(XML::Node const* element) for (auto const& child : element->as_element().children) { TRY(child->content.visit( [&](XML::Node::Element const& element) -> ParseErrorOr { - ++children_count; if (element.name == tag_h1) { - if (children_count != 1) - return ParseError::create("

should be the first child of a "sv, child); - TRY(result.parse_definition(child)); - has_definition = true; + if (children_count != 0) + return ParseError::create("

can only be the first child of "sv, child); } else if (element.name == tag_p) { if (prose_node == nullptr) prose_node = child; @@ -169,6 +277,7 @@ ParseErrorOr SpecFunction::create(XML::Node const* element) } else { return ParseError::create("Unknown child of "sv, child); } + ++children_count; return {}; }, [&](XML::Node::Text const&) -> ParseErrorOr { @@ -182,32 +291,58 @@ ParseErrorOr SpecFunction::create(XML::Node const* element) if (algorithm_node == nullptr) return ParseError::create("No "sv, element); - if (prose_node == nullptr) - return ParseError::create("No prose element"sv, element); - if (!has_definition) - return ParseError::create("Definition was not found"sv, element); - result.m_algorithm = TRY(Algorithm::create(algorithm_node)); - return result; -} + if (prose_node) { + ctx.diag().warn(ctx.location_from_xml_offset(element->offset), + "prose is ignored"); + } -ParseErrorOr SpecFunction::parse_definition(XML::Node const* element) -{ - auto tokens = TRY(tokenize_tree(element)); - TextParser parser(tokens.tokens, element); - - auto [section_number, function_name, arguments] = TRY(parser.parse_definition()); - - if (function_name != m_name) - return ParseError::create("Function name in definition differs from [aoid]"sv, element); - - m_section_number = section_number; - for (auto const& argument : arguments) - m_arguments.append({ argument }); + m_algorithm = TRY(Algorithm::create(algorithm_node)); return {}; } +Specification Specification::create(SpecificationParsingContext& ctx, XML::Node const* element) +{ + VERIFY(element->as_element().name == tag_specification); + + Specification specification; + specification.parse(ctx, element); + return specification; +} + +void Specification::collect_into(TranslationUnitRef translation_unit) +{ + for (auto& clause : m_clauses) + clause->collect_into(translation_unit); +} + +void Specification::parse(SpecificationParsingContext& ctx, XML::Node const* element) +{ + for (auto const& child : element->as_element().children) { + child->content.visit( + [&](XML::Node::Element const& element) { + if (element.name == tag_emu_intro) { + // Introductory comments are ignored. + } else if (element.name == tag_emu_clause) { + m_clauses.append(SpecificationClause::create(ctx, child)); + } else if (element.name == tag_emu_import) { + parse(ctx, child); + } else { + ctx.diag().error(ctx.location_from_xml_offset(child->offset), + "<{}> should not be a child of ", element.name); + } + }, + [&](XML::Node::Text const&) { + if (!contains_empty_text(child)) { + ctx.diag().error(ctx.location_from_xml_offset(child->offset), + "non-empty text node should not be a child of "); + } + }, + [&](auto) {}); + } +} + SpecParsingStep::SpecParsingStep() : CompilationStep("parser"sv) { @@ -247,14 +382,14 @@ void SpecParsingStep::run(TranslationUnitRef translation_unit) } m_document = make(document_or_error.release_value()); - auto spec_function = SpecFunction::create(&m_document->root()).release_value_but_fixme_should_propagate_errors(); + auto const& root = m_document->root(); + if (!root.is_element() || root.as_element().name != tag_specification) { + ctx.diag().fatal_error(ctx.location_from_xml_offset(root.offset), + "document root must be tag"); + return; + } - Vector arguments; - for (auto const& argument : spec_function.m_arguments) - arguments.append({ argument.name }); - - translation_unit->adopt_function( - make_ref_counted(spec_function.m_name, spec_function.m_algorithm.m_tree, move(arguments))); + auto specification = Specification::create(ctx, &root); + specification.collect_into(translation_unit); } - } diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/SpecParser.h b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/SpecParser.h index 7e6df0d1d1..a252b251e1 100644 --- a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/SpecParser.h +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/SpecParser.h @@ -12,6 +12,7 @@ #include "CompilationPipeline.h" #include "Forward.h" #include "Parser/ParseError.h" +#include "Parser/TextParser.h" #include "Parser/Token.h" namespace JSSpecCompiler { @@ -68,24 +69,64 @@ public: Tree m_tree = error_tree; }; -class SpecFunction { +class SpecificationClause { + AK_MAKE_DEFAULT_MOVABLE(SpecificationClause); + public: - struct Argument { - StringView name; - }; + static NonnullOwnPtr create(SpecificationParsingContext& ctx, XML::Node const* element); - static ParseErrorOr create(XML::Node const* element); + virtual ~SpecificationClause() = default; - ParseErrorOr parse_definition(XML::Node const* element); + void collect_into(TranslationUnitRef translation_unit); + +protected: + virtual bool post_initialize(SpecificationParsingContext& /*ctx*/, XML::Node const* /*element*/) { return true; } + virtual void do_collect(TranslationUnitRef /*translation_unit*/) { } + + ClauseHeader m_header; + +private: + SpecificationClause() = default; + ParseErrorOr parse_header(XML::Node const* element); + void parse(SpecificationParsingContext& ctx, XML::Node const* element); + + Vector> m_subclauses; +}; + +class SpecFunction : public SpecificationClause { +public: + SpecFunction(SpecificationClause&& clause) + : SpecificationClause(move(clause)) + { + } + +protected: + bool post_initialize(SpecificationParsingContext& ctx, XML::Node const* element) override; + void do_collect(TranslationUnitRef translation_unit) override; + +private: + ParseErrorOr do_post_initialize(SpecificationParsingContext& ctx, XML::Node const* element); StringView m_section_number; StringView m_id; StringView m_name; - Vector m_arguments; + Vector m_arguments; Algorithm m_algorithm; }; +class Specification { +public: + static Specification create(SpecificationParsingContext& ctx, XML::Node const* element); + + void collect_into(TranslationUnitRef translation_unit); + +private: + void parse(SpecificationParsingContext& ctx, XML::Node const* element); + + Vector> m_clauses; +}; + class SpecParsingStep : public CompilationStep { public: SpecParsingStep(); diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/TextParser.cpp b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/TextParser.cpp index 5725ef5473..a084d3cf53 100644 --- a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/TextParser.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/TextParser.cpp @@ -479,24 +479,28 @@ ParseErrorOr TextParser::parse_step_with_substeps(Tree substeps) return ParseError::create("Unable to parse step with substeps"sv, m_node); } -ParseErrorOr TextParser::parse_definition() +ParseErrorOr TextParser::parse_clause_header() { - DefinitionParseResult result; + ClauseHeader result; auto section_number_token = TRY(consume_token_with_type(TokenType::SectionNumber)); result.section_number = section_number_token->data; - result.function_name = TRY(consume_token())->data; + ClauseHeader::FunctionDefinition function_definition; + + function_definition.name = TRY(consume_token())->data; TRY(consume_token_with_type(TokenType::ParenOpen)); while (true) { - result.arguments.append({ TRY(consume_token_with_type(TokenType::Identifier))->data }); + function_definition.arguments.append({ TRY(consume_token_with_type(TokenType::Identifier))->data }); auto next_token = TRY(consume_token_with_one_of_types({ TokenType::ParenClose, TokenType::Comma })); if (next_token->type == TokenType::ParenClose) break; } TRY(expect_eof()); + result.header = function_definition; + return result; } diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/TextParser.h b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/TextParser.h index 956e484cf6..627f9f353d 100644 --- a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/TextParser.h +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/TextParser.h @@ -7,26 +7,31 @@ #pragma once #include "AST/AST.h" +#include "Function.h" #include "Parser/ParseError.h" #include "Parser/Token.h" namespace JSSpecCompiler { -class TextParser { -public: - struct DefinitionParseResult { - StringView section_number; - StringView function_name; - Vector arguments; +struct ClauseHeader { + struct FunctionDefinition { + StringView name; + Vector arguments; }; + StringView section_number; + Variant header; +}; + +class TextParser { +public: TextParser(Vector& tokens_, XML::Node const* node_) : m_tokens(tokens_) , m_node(node_) { } - ParseErrorOr parse_definition(); + ParseErrorOr parse_clause_header(); ParseErrorOr parse_step_without_substeps(); ParseErrorOr parse_step_with_substeps(Tree substeps); diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/main.cpp b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/main.cpp index f70171fba8..5bd0fb647c 100644 --- a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/main.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/main.cpp @@ -161,7 +161,7 @@ ErrorOr serenity_main(Main::Arguments arguments) outln(stderr, "{}", function->m_ast); } } - if (step.dump_cfg && translation_unit.functions_to_compile()[0]->m_cfg != nullptr) { + if (step.dump_cfg && translation_unit.functions_to_compile().size() && translation_unit.functions_to_compile()[0]->m_cfg != nullptr) { outln(stderr, "===== CFG after {} =====", step.step->name()); for (auto const& function : translation_unit.functions_to_compile()) { outln(stderr, "{}({}):", function->m_name, function->m_arguments);