فهرست منبع

JSSpecCompiler: Add if branch merging pass

It merges standalone IfBranch/ElseIfBranch nodes into IfElseIfChain
nodes. This will ease CFG generation later.
Dan Klishch 1 سال پیش
والد
کامیت
4c4e1e1aed

+ 12 - 0
Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/AST/AST.cpp

@@ -71,6 +71,18 @@ Vector<NodeSubtreePointer> ElseIfBranch::subtrees()
     return { { &m_branch } };
     return { { &m_branch } };
 }
 }
 
 
+Vector<NodeSubtreePointer> IfElseIfChain::subtrees()
+{
+    Vector<NodeSubtreePointer> 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<NodeSubtreePointer> TreeList::subtrees()
 Vector<NodeSubtreePointer> TreeList::subtrees()
 {
 {
     Vector<NodeSubtreePointer> result;
     Vector<NodeSubtreePointer> result;

+ 23 - 0
Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/AST/AST.h

@@ -277,6 +277,29 @@ protected:
     void dump_tree(StringBuilder& builder) override;
     void dump_tree(StringBuilder& builder) override;
 };
 };
 
 
+class IfElseIfChain : public Node {
+public:
+    IfElseIfChain(Vector<Tree>&& conditions, Vector<Tree>&& 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<NodeSubtreePointer> subtrees() override;
+
+    // Excluding else branch, if one is present
+    size_t branches_count() { return m_branches.size(); }
+
+    Vector<Tree> m_conditions;
+    Vector<Tree> m_branches;
+    NullableTree m_else_branch;
+
+protected:
+    void dump_tree(StringBuilder& builder) override;
+};
+
 class TreeList : public Node {
 class TreeList : public Node {
 public:
 public:
     TreeList(Vector<Tree>&& expressions_)
     TreeList(Vector<Tree>&& expressions_)

+ 12 - 0
Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/AST/ASTPrinting.cpp

@@ -97,6 +97,18 @@ void ElseIfBranch::dump_tree(StringBuilder& builder)
     m_branch->format_tree(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)
 void TreeList::dump_tree(StringBuilder& builder)
 {
 {
     dump_node(builder, "TreeList");
     dump_node(builder, "TreeList");

+ 1 - 0
Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/CMakeLists.txt

@@ -3,6 +3,7 @@ set(SOURCES
     AST/ASTPrinting.cpp
     AST/ASTPrinting.cpp
     Compiler/FunctionCallCanonicalizationPass.cpp
     Compiler/FunctionCallCanonicalizationPass.cpp
     Compiler/GenericASTPass.cpp
     Compiler/GenericASTPass.cpp
+    Compiler/IfBranchMergingPass.cpp
     Parser/Lexer.cpp
     Parser/Lexer.cpp
     Parser/ParseError.cpp
     Parser/ParseError.cpp
     Parser/SpecParser.cpp
     Parser/SpecParser.cpp

+ 97 - 0
Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Compiler/IfBranchMergingPass.cpp

@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <AK/TypeCasts.h>
+
+#include "AST/AST.h"
+#include "Compiler/IfBranchMergingPass.h"
+
+namespace JSSpecCompiler {
+
+RecursionDecision IfBranchMergingPass::on_entry(Tree tree)
+{
+    if (auto list = as<TreeList>(tree); list) {
+        Vector<Tree> result;
+        Vector<Tree> 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<IfBranch>(node.ptr())) {
+                merge_if_needed();
+                unmerged_branches.append(node);
+            } else if (is<ElseIfBranch>(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<Tree> const& unmerged_branches)
+{
+    static const Tree error = make_ref_counted<ErrorNode>("Cannot make sense of if-elseif-else chain"sv);
+
+    VERIFY(unmerged_branches.size() >= 1);
+
+    Vector<Tree> conditions;
+    Vector<Tree> branches;
+    NullableTree else_branch;
+
+    if (auto if_branch = as<IfBranch>(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<ElseIfBranch>(unmerged_branches[i]);
+
+        if (!branch)
+            return error;
+
+        if (!branch->m_condition) {
+            // There might be situation like:
+            //   1. If <condition>, then
+            //      ...
+            //   2. Else,
+            //      a. If <condition>, then
+            //         ...
+            //   3. Else,
+            //      ...
+            auto substep_list = as<TreeList>(branch->m_branch);
+            if (substep_list && substep_list->m_expressions.size() == 1) {
+                if (auto nested_if = as<IfBranch>(substep_list->m_expressions[0]); nested_if)
+                    branch = make_ref_counted<ElseIfBranch>(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<IfElseIfChain>(move(conditions), move(branches), else_branch);
+}
+
+}

+ 36 - 0
Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Compiler/IfBranchMergingPass.h

@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
+ *
+ * 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 <condition>, then
+//      ...
+//   2. Else,
+//      a. If <condition>, then
+//         ...
+//   3. Else,
+//      ...
+// ```
+class IfBranchMergingPass : public GenericASTPass {
+public:
+    using GenericASTPass::GenericASTPass;
+
+protected:
+    RecursionDecision on_entry(Tree tree) override;
+
+private:
+    static Tree merge_branches(Vector<Tree> const& unmerged_branches);
+};
+
+}

+ 1 - 0
Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Forward.h

@@ -29,6 +29,7 @@ class ReturnExpression;
 class AssertExpression;
 class AssertExpression;
 class IfBranch;
 class IfBranch;
 class ElseIfBranch;
 class ElseIfBranch;
+class IfElseIfChain;
 class TreeList;
 class TreeList;
 class RecordDirectListInitialization;
 class RecordDirectListInitialization;
 class FunctionCall;
 class FunctionCall;

+ 2 - 0
Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/main.cpp

@@ -10,6 +10,7 @@
 #include <LibXML/Parser/Parser.h>
 #include <LibXML/Parser/Parser.h>
 
 
 #include "Compiler/FunctionCallCanonicalizationPass.h"
 #include "Compiler/FunctionCallCanonicalizationPass.h"
+#include "Compiler/IfBranchMergingPass.h"
 #include "Function.h"
 #include "Function.h"
 #include "Parser/SpecParser.h"
 #include "Parser/SpecParser.h"
 
 
@@ -39,6 +40,7 @@ ErrorOr<int> serenity_main(Main::Arguments)
     auto function = make_ref_counted<JSSpecCompiler::Function>(&context, spec_function.m_name, spec_function.m_algorithm.m_tree);
     auto function = make_ref_counted<JSSpecCompiler::Function>(&context, spec_function.m_name, spec_function.m_algorithm.m_tree);
 
 
     FunctionCallCanonicalizationPass(function).run();
     FunctionCallCanonicalizationPass(function).run();
+    IfBranchMergingPass(function).run();
 
 
     out("{}", function->m_ast);
     out("{}", function->m_ast);
     return 0;
     return 0;