diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Forward.h b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Forward.h index 50da4f2b1c..8c217a901c 100644 --- a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Forward.h +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Forward.h @@ -36,4 +36,10 @@ class SlotName; class Variable; class FunctionPointer; +// Parser/SpecParser.h +class AlgorithmStep; +class AlgorithmStepList; +class Algorithm; +class SpecFunction; + } diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/SpecParser.cpp b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/SpecParser.cpp new file mode 100644 index 0000000000..f5485e289a --- /dev/null +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/SpecParser.cpp @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2023, Dan Klishch + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +#include "Parser/Lexer.h" +#include "Parser/SpecParser.h" +#include "Parser/TextParser.h" +#include "Parser/XMLUtils.h" + +namespace JSSpecCompiler { + +ParseErrorOr AlgorithmStep::create(XML::Node const* node) +{ + VERIFY(node->as_element().name == tag_li); + + auto [tokens, substeps] = TRY(tokenize_tree(node, true)); + AlgorithmStep result { .m_tokens = move(tokens), .m_node = node }; + + if (substeps) + result.m_substeps = TRY(AlgorithmStepList::create(substeps->as_element())).m_expression; + + result.m_expression = TRY(result.parse()); + return result; +} + +ParseErrorOr AlgorithmStep::parse() +{ + TextParser parser(m_tokens, m_node); + + if (m_substeps) + return parser.parse_step_with_substeps(RefPtr(m_substeps).release_nonnull()); + else + return parser.parse_step_without_substeps(); +} + +ParseErrorOr AlgorithmStepList::create(XML::Node::Element const& element) +{ + VERIFY(element.name == tag_ol); + + AlgorithmStepList result; + auto& steps = result.m_steps; + + Vector step_expressions; + + for (auto const& child : element.children) { + TRY(child->content.visit( + [&](XML::Node::Element const& element) -> ParseErrorOr { + if (element.name != tag_li) + return ParseError::create("
    > :not(
  1. ) should not match any elements"sv, child); + steps.append(TRY(AlgorithmStep::create(child))); + step_expressions.append(steps.last().m_expression); + return {}; + }, + [&](XML::Node::Text const&) -> ParseErrorOr { + if (!contains_empty_text(child)) + return ParseError::create("
      should not have non-empty child text nodes"sv, child); + return {}; + }, + move(ignore_comments))); + } + + result.m_expression = make_ref_counted(move(step_expressions)); + + return result; +} + +ParseErrorOr Algorithm::create(XML::Node const* node) +{ + VERIFY(node->as_element().name == tag_emu_alg); + + XML::Node::Element const* steps_list = nullptr; + for (auto const& child : node->as_element().children) { + TRY(child->content.visit( + [&](XML::Node::Element const& element) -> ParseErrorOr { + if (element.name == tag_ol) { + if (steps_list != nullptr) + return ParseError::create(" should have exactly one
        child"sv, child); + steps_list = &element; + return {}; + } else { + return ParseError::create(" should not have children other than
          "sv, child); + } + }, + [&](XML::Node::Text const&) -> ParseErrorOr { + if (!contains_empty_text(child)) + return ParseError::create(" should not have non-empty child text nodes"sv, child); + return {}; + }, + move(ignore_comments))); + } + + if (steps_list == nullptr) + return ParseError::create(" should have exactly one
            child"sv, node); + + Algorithm algorithm; + algorithm.m_steps = TRY(AlgorithmStepList::create(*steps_list)); + algorithm.m_tree = algorithm.m_steps.m_expression; + + return algorithm; +} + +ParseErrorOr Function::create(XML::Node const* element) +{ + VERIFY(element->as_element().name == tag_emu_clause); + + Function result; + result.m_id = TRY(get_attribute_by_name(element, attribute_id)); + result.m_name = TRY(get_attribute_by_name(element, attribute_aoid)); + + u32 children_count = 0; + bool has_definition = false; + + XML::Node const* algorithm_node = nullptr; + XML::Node const* prose_node = nullptr; + + 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; + } else if (element.name == tag_p) { + if (prose_node == nullptr) + prose_node = child; + } else if (element.name == tag_emu_alg) { + algorithm_node = child; + } else { + return ParseError::create("Unknown child of "sv, child); + } + return {}; + }, + [&](XML::Node::Text const&) -> ParseErrorOr { + if (!contains_empty_text(child)) { + return ParseError::create(" should not have non-empty child text nodes"sv, child); + } + return {}; + }, + move(ignore_comments))); + } + + 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; +} + +ParseErrorOr Function::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 }); + + return {}; +} + +} diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/SpecParser.h b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/SpecParser.h new file mode 100644 index 0000000000..1d81c798bd --- /dev/null +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Parser/SpecParser.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2023, Dan Klishch + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include "AST/AST.h" +#include "Forward.h" +#include "Parser/ParseError.h" +#include "Parser/Token.h" + +namespace JSSpecCompiler { +class AlgorithmStepList { +public: + static ParseErrorOr create(XML::Node::Element const& element); + + Vector m_steps; + Tree m_expression = error_tree; +}; + +class AlgorithmStep { +public: + static ParseErrorOr create(XML::Node const* node); + + ParseErrorOr parse(); + + Tree m_expression = error_tree; + Vector m_tokens; + NullableTree m_substeps; + XML::Node const* m_node; +}; + +class Algorithm { +public: + static ParseErrorOr create(XML::Node const* node); + + AlgorithmStepList m_steps; + Tree m_tree = error_tree; +}; + +class Function { +public: + struct Argument { + StringView name; + }; + + static ParseErrorOr create(XML::Node const* element); + + ParseErrorOr parse_definition(XML::Node const* element); + + StringView m_section_number; + StringView m_id; + StringView m_name; + + Vector m_arguments; + Algorithm m_algorithm; +}; + +}