diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/AST/AST.cpp b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/AST/AST.cpp index 1d74b8a11a..6cd9a5ba08 100644 --- a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/AST/AST.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/AST/AST.cpp @@ -71,6 +71,18 @@ Vector ElseIfBranch::subtrees() return { { &m_branch } }; } +Vector IfElseIfChain::subtrees() +{ + Vector result; + for (size_t i = 0; i < branches_count(); ++i) { + result.append({ &m_conditions[i] }); + result.append({ &m_branches[i] }); + } + if (m_else_branch) + result.append({ &m_else_branch }); + return result; +} + Vector TreeList::subtrees() { Vector result; diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/AST/AST.h b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/AST/AST.h index b89cdfeb9c..ce33c3d01e 100644 --- a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/AST/AST.h +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/AST/AST.h @@ -277,6 +277,29 @@ protected: void dump_tree(StringBuilder& builder) override; }; +class IfElseIfChain : public Node { +public: + IfElseIfChain(Vector&& conditions, Vector&& branches, NullableTree else_branch) + : m_conditions(move(conditions)) + , m_branches(move(branches)) + , m_else_branch(else_branch) + { + VERIFY(m_branches.size() == m_conditions.size()); + } + + Vector subtrees() override; + + // Excluding else branch, if one is present + size_t branches_count() { return m_branches.size(); } + + Vector m_conditions; + Vector m_branches; + NullableTree m_else_branch; + +protected: + void dump_tree(StringBuilder& builder) override; +}; + class TreeList : public Node { public: TreeList(Vector&& expressions_) diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/AST/ASTPrinting.cpp b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/AST/ASTPrinting.cpp index ae2beff796..aea5e299ae 100644 --- a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/AST/ASTPrinting.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/AST/ASTPrinting.cpp @@ -97,6 +97,18 @@ void ElseIfBranch::dump_tree(StringBuilder& builder) m_branch->format_tree(builder); } +void IfElseIfChain::dump_tree(StringBuilder& builder) +{ + dump_node(builder, "IfElseIfChain"); + + for (size_t i = 0; i < branches_count(); ++i) { + m_conditions[i]->format_tree(builder); + m_branches[i]->format_tree(builder); + } + if (m_else_branch) + m_else_branch->format_tree(builder); +} + void TreeList::dump_tree(StringBuilder& builder) { dump_node(builder, "TreeList"); diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/CMakeLists.txt b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/CMakeLists.txt index 2fc266f17d..5073b9d7d7 100644 --- a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/CMakeLists.txt +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/CMakeLists.txt @@ -3,6 +3,7 @@ set(SOURCES AST/ASTPrinting.cpp Compiler/FunctionCallCanonicalizationPass.cpp Compiler/GenericASTPass.cpp + Compiler/IfBranchMergingPass.cpp Parser/Lexer.cpp Parser/ParseError.cpp Parser/SpecParser.cpp diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Compiler/IfBranchMergingPass.cpp b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Compiler/IfBranchMergingPass.cpp new file mode 100644 index 0000000000..6f83613a9d --- /dev/null +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Compiler/IfBranchMergingPass.cpp @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2023, Dan Klishch + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +#include "AST/AST.h" +#include "Compiler/IfBranchMergingPass.h" + +namespace JSSpecCompiler { + +RecursionDecision IfBranchMergingPass::on_entry(Tree tree) +{ + if (auto list = as(tree); list) { + Vector result; + Vector unmerged_branches; + + auto merge_if_needed = [&] { + if (!unmerged_branches.is_empty()) { + result.append(merge_branches(unmerged_branches)); + unmerged_branches.clear(); + } + }; + + for (auto const& node : list->m_expressions) { + if (is(node.ptr())) { + merge_if_needed(); + unmerged_branches.append(node); + } else if (is(node.ptr())) { + unmerged_branches.append(node); + } else { + merge_if_needed(); + result.append(node); + } + } + merge_if_needed(); + + list->m_expressions = move(result); + } + return RecursionDecision::Recurse; +} + +Tree IfBranchMergingPass::merge_branches(Vector const& unmerged_branches) +{ + static const Tree error = make_ref_counted("Cannot make sense of if-elseif-else chain"sv); + + VERIFY(unmerged_branches.size() >= 1); + + Vector conditions; + Vector branches; + NullableTree else_branch; + + if (auto if_branch = as(unmerged_branches[0]); if_branch) { + conditions.append(if_branch->m_condition); + branches.append(if_branch->m_branch); + } else { + return error; + } + + for (size_t i = 1; i < unmerged_branches.size(); ++i) { + auto branch = as(unmerged_branches[i]); + + if (!branch) + return error; + + if (!branch->m_condition) { + // There might be situation like: + // 1. If , then + // ... + // 2. Else, + // a. If , then + // ... + // 3. Else, + // ... + auto substep_list = as(branch->m_branch); + if (substep_list && substep_list->m_expressions.size() == 1) { + if (auto nested_if = as(substep_list->m_expressions[0]); nested_if) + branch = make_ref_counted(nested_if->m_condition, nested_if->m_branch); + } + } + + if (branch->m_condition) { + conditions.append(branch->m_condition.release_nonnull()); + branches.append(branch->m_branch); + } else { + if (i + 1 != unmerged_branches.size()) + return error; + else_branch = branch->m_branch; + } + } + + return make_ref_counted(move(conditions), move(branches), else_branch); +} + +} diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Compiler/IfBranchMergingPass.h b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Compiler/IfBranchMergingPass.h new file mode 100644 index 0000000000..69610b9301 --- /dev/null +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Compiler/IfBranchMergingPass.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2023, Dan Klishch + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include "Compiler/GenericASTPass.h" + +namespace JSSpecCompiler { + +// IfBranchMergingPass, unsurprisingly, merges if-elseif-else chains, represented as a separate +// nodes after parsing, into one IfElseIfChain node. It also deals with the following nonsense from +// the spec: +// ``` +// 1. If , then +// ... +// 2. Else, +// a. If , then +// ... +// 3. Else, +// ... +// ``` +class IfBranchMergingPass : public GenericASTPass { +public: + using GenericASTPass::GenericASTPass; + +protected: + RecursionDecision on_entry(Tree tree) override; + +private: + static Tree merge_branches(Vector const& unmerged_branches); +}; + +} diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Forward.h b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Forward.h index 7ffdb27aa7..7274e6f070 100644 --- a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Forward.h +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Forward.h @@ -29,6 +29,7 @@ class ReturnExpression; class AssertExpression; class IfBranch; class ElseIfBranch; +class IfElseIfChain; class TreeList; class RecordDirectListInitialization; class FunctionCall; diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/main.cpp b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/main.cpp index 6af22d9a18..c6e83f5b67 100644 --- a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/main.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/main.cpp @@ -10,6 +10,7 @@ #include #include "Compiler/FunctionCallCanonicalizationPass.h" +#include "Compiler/IfBranchMergingPass.h" #include "Function.h" #include "Parser/SpecParser.h" @@ -39,6 +40,7 @@ ErrorOr serenity_main(Main::Arguments) auto function = make_ref_counted(&context, spec_function.m_name, spec_function.m_algorithm.m_tree); FunctionCallCanonicalizationPass(function).run(); + IfBranchMergingPass(function).run(); out("{}", function->m_ast); return 0;