#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_all.hpp>

#include <language/ast/ASTBuilder.hpp>
#include <language/ast/ASTModulesImporter.hpp>
#include <language/ast/ASTNodeDataTypeBuilder.hpp>
#include <language/ast/ASTNodeTypeCleaner.hpp>
#include <language/ast/ASTSymbolTableBuilder.hpp>
#include <language/utils/ASTNodeDataTypeTraits.hpp>
#include <language/utils/ASTPrinter.hpp>
#include <language/utils/ParseError.hpp>
#include <language/utils/TypeDescriptor.hpp>
#include <utils/Exceptions.hpp>

#include <pegtl/string_input.hpp>

#define CHECK_AST(data, expected_output)                                                            \
  {                                                                                                 \
    static_assert(std::is_same_v<std::decay_t<decltype(data)>, std::string_view>);                  \
    static_assert(std::is_same_v<std::decay_t<decltype(expected_output)>, std::string_view>);       \
                                                                                                    \
    TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};                                      \
    auto ast = ASTBuilder::build(input);                                                            \
                                                                                                    \
    ASTModulesImporter{*ast};                                                                       \
    ASTNodeTypeCleaner<language::import_instruction>{*ast};                                         \
                                                                                                    \
    ASTSymbolTableBuilder{*ast};                                                                    \
    ASTNodeDataTypeBuilder{*ast};                                                                   \
                                                                                                    \
    std::stringstream ast_output;                                                                   \
    ast_output << '\n' << ASTPrinter{*ast, ASTPrinter::Format::raw, {ASTPrinter::Info::data_type}}; \
                                                                                                    \
    REQUIRE(ast_output.str() == expected_output);                                                   \
  }

template <>
inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const double>> =
  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("builtin_t");
const auto builtin_data_type = ast_node_data_type_from<std::shared_ptr<const double>>;

#define CHECK_AST_WITH_BUILTIN(data, expected_output)                                                         \
  {                                                                                                           \
    static_assert(std::is_same_v<std::decay_t<decltype(data)>, std::string_view>);                            \
    static_assert(std::is_same_v<std::decay_t<decltype(expected_output)>, std::string_view>);                 \
                                                                                                              \
    TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};                                                \
    auto ast = ASTBuilder::build(input);                                                                      \
                                                                                                              \
    SymbolTable& symbol_table = *ast->m_symbol_table;                                                         \
    auto [i_symbol, success]  = symbol_table.add(builtin_data_type.nameOfTypeId(), ast->begin());             \
    if (not success) {                                                                                        \
      throw UnexpectedError("cannot add '" + builtin_data_type.nameOfTypeId() + "' type for testing");        \
    }                                                                                                         \
                                                                                                              \
    i_symbol->attributes().setDataType(ASTNodeDataType::build<ASTNodeDataType::type_name_id_t>());            \
    i_symbol->attributes().setIsInitialized();                                                                \
    i_symbol->attributes().value() = symbol_table.typeEmbedderTable().size();                                 \
    symbol_table.typeEmbedderTable().add(std::make_shared<TypeDescriptor>(builtin_data_type.nameOfTypeId())); \
                                                                                                              \
    ASTSymbolTableBuilder{*ast};                                                                              \
    ASTNodeDataTypeBuilder{*ast};                                                                             \
                                                                                                              \
    std::stringstream ast_output;                                                                             \
    ast_output << '\n' << ASTPrinter{*ast, ASTPrinter::Format::raw, {ASTPrinter::Info::data_type}};           \
                                                                                                              \
    REQUIRE(ast_output.str() == expected_output);                                                             \
  }

// clazy:excludeall=non-pod-global-static

TEST_CASE("ASTNodeDataTypeBuilder", "[language]")
{
  SECTION("module")
  {
    std::string_view data = R"(
import a_module_name;
)";

    std::string_view result = R"(
(root:void)
 `-(language::import_instruction:void)
     `-(language::module_name:string)
)";

    TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
    auto ast = ASTBuilder::build(input);

    ASTSymbolTableBuilder{*ast};
    ASTNodeDataTypeBuilder{*ast};

    std::stringstream ast_output;
    ast_output << '\n' << ASTPrinter{*ast, ASTPrinter::Format::raw, {ASTPrinter::Info::data_type}};

    REQUIRE(ast_output.str() == result);
  }

  SECTION("integer")
  {
    std::string_view data = R"(
1;
)";

    std::string_view result = R"(
(root:void)
 `-(language::integer:1:Z)
)";

    CHECK_AST(data, result);
  }

  SECTION("real 1")
  {
    std::string_view data = R"(
1.3;
)";

    std::string_view result = R"(
(root:void)
 `-(language::real:1.3:R)
)";

    CHECK_AST(data, result);
  }

  SECTION("real 2")
  {
    std::string_view data = R"(
.5;
)";

    std::string_view result = R"(
(root:void)
 `-(language::real:.5:R)
)";

    CHECK_AST(data, result);
  }

  SECTION("real 3")
  {
    std::string_view data = R"(
5e-1;
)";

    std::string_view result = R"(
(root:void)
 `-(language::real:5e-1:R)
)";

    CHECK_AST(data, result);
  }

  SECTION("real 4")
  {
    std::string_view data = R"(
2e+1;
)";

    std::string_view result = R"(
(root:void)
 `-(language::real:2e+1:R)
)";

    CHECK_AST(data, result);
  }

  SECTION("real 5")
  {
    std::string_view data = R"(
2e1;
)";

    std::string_view result = R"(
(root:void)
 `-(language::real:2e1:R)
)";

    CHECK_AST(data, result);
  }

  SECTION("real 6")
  {
    std::string_view data = R"(
5.e-1;
)";

    std::string_view result = R"(
(root:void)
 `-(language::real:5.e-1:R)
)";

    CHECK_AST(data, result);
  }

  SECTION("real 7")
  {
    std::string_view data = R"(
5.e+1;
)";

    std::string_view result = R"(
(root:void)
 `-(language::real:5.e+1:R)
)";

    CHECK_AST(data, result);
  }

  SECTION("real 8")
  {
    std::string_view data = R"(
3.4e+1;
)";

    std::string_view result = R"(
(root:void)
 `-(language::real:3.4e+1:R)
)";

    CHECK_AST(data, result);
  }

  SECTION("real 9")
  {
    std::string_view data = R"(
.231e1;
)";

    std::string_view result = R"(
(root:void)
 `-(language::real:.231e1:R)
)";

    CHECK_AST(data, result);
  }

  SECTION("true")
  {
    std::string_view data = R"(
true;
)";

    std::string_view result = R"(
(root:void)
 `-(language::true_kw:B)
)";

    CHECK_AST(data, result);
  }

  SECTION("false")
  {
    std::string_view data = R"(
false;
)";

    std::string_view result = R"(
(root:void)
 `-(language::false_kw:B)
)";

    CHECK_AST(data, result);
  }

  SECTION("block")
  {
    std::string_view data = R"(
{
  1;
  2.3;
}
)";

    std::string_view result = R"(
(root:void)
 `-(language::block:void)
     +-(language::integer:1:Z)
     `-(language::real:2.3:R)
)";

    CHECK_AST(data, result);
  }

  SECTION("compound")
  {
    SECTION("declaration")
    {
      std::string_view data = R"(
let (x,b,n,s) : R*B*N*string;
)";

      std::string_view result = R"(
(root:void)
 `-(language::var_declaration:void)
     +-(language::name_list:R*B*N*string)
     |   +-(language::name:x:R)
     |   +-(language::name:b:B)
     |   +-(language::name:n:N)
     |   `-(language::name:s:string)
     `-(language::type_expression:R*B*N*string)
         +-(language::R_set:R)
         +-(language::B_set:B)
         +-(language::N_set:N)
         `-(language::string_type:string)
)";

      CHECK_AST(data, result);
    }

    SECTION("errors")
    {
      SECTION("invalid array subscript")
      {
        std::string_view data = R"(
let x : R; x[2];
)";

        TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
        auto ast = ASTBuilder::build(input);
        ASTSymbolTableBuilder{*ast};

        REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, "invalid subscript expression: R cannot be indexed");
      }

      SECTION("invalid R^d subscript index list")
      {
        std::string_view data = R"(
let x : R^2; x[2,2];
)";

        TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
        auto ast = ASTBuilder::build(input);
        ASTSymbolTableBuilder{*ast};

        REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, "invalid index type: R^2 requires a single integer");
      }

      SECTION("invalid R^dxd subscript index list 1")
      {
        std::string_view data = R"(
let x : R^2x2; x[2];
)";

        TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
        auto ast = ASTBuilder::build(input);
        ASTSymbolTableBuilder{*ast};

        REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, "invalid index type: R^2x2 requires two integers");
      }

      SECTION("invalid R^dxd subscript index list 2")
      {
        std::string_view data = R"(
let x : R^2x2; x[2,3,1];
)";

        TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
        auto ast = ASTBuilder::build(input);
        ASTSymbolTableBuilder{*ast};

        REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, "invalid index type: R^2x2 requires two integers");
      }

      SECTION("too many variables")
      {
        std::string_view data = R"(
let (x,b,n,s,t) : R*B*N*string;
)";

        TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
        auto ast = ASTBuilder::build(input);
        ASTSymbolTableBuilder{*ast};

        REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, "number of product spaces (4) R*B*N*string differs from "
                                                          "number of variables (5) (x,b,n,s,t) ");
      }

      SECTION("too few variables")
      {
        std::string_view data = R"(
let (x,b,n) : R*B*N*string;
)";

        TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
        auto ast = ASTBuilder::build(input);
        ASTSymbolTableBuilder{*ast};

        REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, "number of product spaces (4) R*B*N*string differs from "
                                                          "number of variables (3) (x,b,n) ");
      }

      SECTION("unexpected variable list")
      {
        std::string_view data = R"(
let  (x,y) : R;
)";

        TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
        auto ast = ASTBuilder::build(input);
        ASTSymbolTableBuilder{*ast};

        REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, "unexpected variable list for single space");
      }

      SECTION("invalid R-list -> R^d")
      {
        std::string_view data = R"(
let square : R -> R^3, x -> (x, 2);
)";

        TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
        auto ast = ASTBuilder::build(input);
        ASTSymbolTableBuilder{*ast};

        REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast},
                            "expecting 3 scalar expressions or an R^3, found 2 scalar expressions");
      }
    }
  }

  SECTION("let declaration")
  {
    SECTION("tuples")
    {
      SECTION("B tuples")
      {
        std::string_view data = R"(
let t : (B), t = (true, false);
)";

        std::string_view result = R"(
(root:void)
 `-(language::var_declaration:void)
     +-(language::name:t:(B...))
     +-(language::tuple_type_specifier:(B...))
     |   `-(language::B_set:B)
     +-(language::name:t:(B...))
     `-(language::expression_list:B*B)
         +-(language::true_kw:B)
         `-(language::false_kw:B)
)";

        CHECK_AST(data, result);
      }

      SECTION("N tuples")
      {
        std::string_view data = R"(
let t : (N), t = (1, 2, 3, 5);
)";

        std::string_view result = R"(
(root:void)
 `-(language::var_declaration:void)
     +-(language::name:t:(N...))
     +-(language::tuple_type_specifier:(N...))
     |   `-(language::N_set:N)
     +-(language::name:t:(N...))
     `-(language::expression_list:Z*Z*Z*Z)
         +-(language::integer:1:Z)
         +-(language::integer:2:Z)
         +-(language::integer:3:Z)
         `-(language::integer:5:Z)
)";

        CHECK_AST(data, result);
      }

      SECTION("Z tuples")
      {
        std::string_view data = R"(
let n : N, n = 3;
let t : (Z), t = (2, n, true);
)";

        std::string_view result = R"(
(root:void)
 +-(language::var_declaration:void)
 |   +-(language::name:n:N)
 |   +-(language::N_set:N)
 |   +-(language::name:n:N)
 |   `-(language::integer:3:Z)
 `-(language::var_declaration:void)
     +-(language::name:t:(Z...))
     +-(language::tuple_type_specifier:(Z...))
     |   `-(language::Z_set:Z)
     +-(language::name:t:(Z...))
     `-(language::expression_list:Z*N*B)
         +-(language::integer:2:Z)
         +-(language::name:n:N)
         `-(language::true_kw:B)
)";

        CHECK_AST(data, result);
      }

      SECTION("R tuples")
      {
        std::string_view data = R"(
let t : (R), t = (2, 3.1, 5);
)";

        std::string_view result = R"(
(root:void)
 `-(language::var_declaration:void)
     +-(language::name:t:(R...))
     +-(language::tuple_type_specifier:(R...))
     |   `-(language::R_set:R)
     +-(language::name:t:(R...))
     `-(language::expression_list:Z*R*Z)
         +-(language::integer:2:Z)
         +-(language::real:3.1:R)
         `-(language::integer:5:Z)
)";

        CHECK_AST(data, result);
      }

      SECTION("R^d tuples")
      {
        std::string_view data = R"(
let a : R^2, a = (2,3.1);
let t1 : (R^2), t1 = (a, (1,2), 0);
let t2 : (R^3), t2 = (0, 0);
)";

        std::string_view result = R"(
(root:void)
 +-(language::var_declaration:void)
 |   +-(language::name:a:R^2)
 |   +-(language::vector_type:R^2)
 |   |   +-(language::R_set:R)
 |   |   `-(language::integer:2:Z)
 |   +-(language::name:a:R^2)
 |   `-(language::expression_list:Z*R)
 |       +-(language::integer:2:Z)
 |       `-(language::real:3.1:R)
 +-(language::var_declaration:void)
 |   +-(language::name:t1:(R^2...))
 |   +-(language::tuple_type_specifier:(R^2...))
 |   |   `-(language::vector_type:R^2)
 |   |       +-(language::R_set:R)
 |   |       `-(language::integer:2:Z)
 |   +-(language::name:t1:(R^2...))
 |   `-(language::expression_list:R^2*(Z*Z)*Z)
 |       +-(language::name:a:R^2)
 |       +-(language::tuple_expression:Z*Z)
 |       |   +-(language::integer:1:Z)
 |       |   `-(language::integer:2:Z)
 |       `-(language::integer:0:Z)
 `-(language::var_declaration:void)
     +-(language::name:t2:(R^3...))
     +-(language::tuple_type_specifier:(R^3...))
     |   `-(language::vector_type:R^3)
     |       +-(language::R_set:R)
     |       `-(language::integer:3:Z)
     +-(language::name:t2:(R^3...))
     `-(language::expression_list:Z*Z)
         +-(language::integer:0:Z)
         `-(language::integer:0:Z)
)";

        CHECK_AST(data, result);
      }

      SECTION("R^dxd tuples")
      {
        std::string_view data = R"(
let a : R^2x2, a = (2, 3.1, -1.2, 4);
let t1 : (R^2x2), t1 = (a, (1,2,1,3), 0);
let t2 : (R^3x3), t2 = (0, 0);
)";

        std::string_view result = R"(
(root:void)
 +-(language::var_declaration:void)
 |   +-(language::name:a:R^2x2)
 |   +-(language::matrix_type:R^2x2)
 |   |   +-(language::R_set:R)
 |   |   +-(language::integer:2:Z)
 |   |   `-(language::integer:2:Z)
 |   +-(language::name:a:R^2x2)
 |   `-(language::expression_list:Z*R*R*Z)
 |       +-(language::integer:2:Z)
 |       +-(language::real:3.1:R)
 |       +-(language::unary_minus:R)
 |       |   `-(language::real:1.2:R)
 |       `-(language::integer:4:Z)
 +-(language::var_declaration:void)
 |   +-(language::name:t1:(R^2x2...))
 |   +-(language::tuple_type_specifier:(R^2x2...))
 |   |   `-(language::matrix_type:R^2x2)
 |   |       +-(language::R_set:R)
 |   |       +-(language::integer:2:Z)
 |   |       `-(language::integer:2:Z)
 |   +-(language::name:t1:(R^2x2...))
 |   `-(language::expression_list:R^2x2*(Z*Z*Z*Z)*Z)
 |       +-(language::name:a:R^2x2)
 |       +-(language::tuple_expression:Z*Z*Z*Z)
 |       |   +-(language::integer:1:Z)
 |       |   +-(language::integer:2:Z)
 |       |   +-(language::integer:1:Z)
 |       |   `-(language::integer:3:Z)
 |       `-(language::integer:0:Z)
 `-(language::var_declaration:void)
     +-(language::name:t2:(R^3x3...))
     +-(language::tuple_type_specifier:(R^3x3...))
     |   `-(language::matrix_type:R^3x3)
     |       +-(language::R_set:R)
     |       +-(language::integer:3:Z)
     |       `-(language::integer:3:Z)
     +-(language::name:t2:(R^3x3...))
     `-(language::expression_list:Z*Z)
         +-(language::integer:0:Z)
         `-(language::integer:0:Z)
)";

        CHECK_AST(data, result);
      }

      SECTION("string tuples")
      {
        std::string_view data = R"(
let t : (string), t = ("foo", "bar");
)";

        std::string_view result = R"(
(root:void)
 `-(language::var_declaration:void)
     +-(language::name:t:(string...))
     +-(language::tuple_type_specifier:(string...))
     |   `-(language::string_type:string)
     +-(language::name:t:(string...))
     `-(language::expression_list:string*string)
         +-(language::literal:"foo":string)
         `-(language::literal:"bar":string)
)";

        CHECK_AST(data, result);
      }

      SECTION("type_id tuples")
      {
        std::string_view data = R"(
// invalid conversion just checking grammar
let t : (builtin_t), t= (1,2,3);
)";

        std::string_view result = R"(
(root:void)
 `-(language::var_declaration:void)
     +-(language::name:t:(builtin_t...))
     +-(language::tuple_type_specifier:(builtin_t...))
     |   `-(language::type_name_id:builtin_t)
     +-(language::name:t:(builtin_t...))
     `-(language::expression_list:Z*Z*Z)
         +-(language::integer:1:Z)
         +-(language::integer:2:Z)
         `-(language::integer:3:Z)
)";

        CHECK_AST_WITH_BUILTIN(data, result);
      }

      SECTION("errors")
      {
        SECTION("type_id")
        {
          std::string_view data = R"(
let t : builtin_t;
)";

          TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
          auto ast = ASTBuilder::build(input);
          ASTSymbolTableBuilder{*ast};

          REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, "undefined type identifier");
        }

        SECTION("type_id 2")
        {
          std::string_view data = R"(
let a: R, a = 3;
let t : a;
)";

          TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
          auto ast = ASTBuilder::build(input);
          ASTSymbolTableBuilder{*ast};

          REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast},
                              "invalid type identifier, 'a' was previously defined as a 'R'");
        }

        SECTION("type_id tuples")
        {
          std::string_view data = R"(
let t : (builtin_t);
)";

          TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
          auto ast = ASTBuilder::build(input);
          ASTSymbolTableBuilder{*ast};

          REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, "undefined type identifier");
        }

        SECTION("type_id tuples 2")
        {
          std::string_view data = R"(
let a: R, a = 3;
let t : (a);
)";

          TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
          auto ast = ASTBuilder::build(input);
          ASTSymbolTableBuilder{*ast};

          REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast},
                              "invalid type identifier, 'a' was previously defined as a 'R'");
        }
      }
    }

    SECTION("R^d-functions")
    {
      SECTION("vector function")
      {
        std::string_view data = R"(
let double : R^2 -> R^2, x -> 2*x;
)";

        std::string_view result = R"(
(root:void)
 `-(language::fct_declaration:void)
     `-(language::name:double:function)
)";

        CHECK_AST(data, result);
      }

      SECTION("R-list -> R^d")
      {
        std::string_view data = R"(
let square : R -> R^2, x -> (x, x*x);
)";

        std::string_view result = R"(
(root:void)
 `-(language::fct_declaration:void)
     `-(language::name:square:function)
)";

        CHECK_AST(data, result);
      }
    }

    SECTION("R^dxd-functions")
    {
      SECTION("matrix function")
      {
        std::string_view data = R"(
let double : R^2x2 -> R^2x2, x -> 2*x;
)";

        std::string_view result = R"(
(root:void)
 `-(language::fct_declaration:void)
     `-(language::name:double:function)
)";

        CHECK_AST(data, result);
      }

      SECTION("matrix vector product")
      {
        std::string_view data = R"(
let prod : R^2x2*R^2 -> R^2, (A,x) -> A*x;
)";

        std::string_view result = R"(
(root:void)
 `-(language::fct_declaration:void)
     `-(language::name:prod:function)
)";

        CHECK_AST(data, result);
      }

      SECTION("matrix function")
      {
        std::string_view data = R"(
let det : R^2x2 -> R, x -> x[0,0]*x[1,1]-x[1,0]*x[0,1];
)";

        std::string_view result = R"(
(root:void)
 `-(language::fct_declaration:void)
     `-(language::name:det:function)
)";

        CHECK_AST(data, result);
      }

      SECTION("R-list -> R^dxd")
      {
        std::string_view data = R"(
let f : R -> R^2x2, x -> (x, x*x, 2-x, 0);
)";

        std::string_view result = R"(
(root:void)
 `-(language::fct_declaration:void)
     `-(language::name:f:function)
)";

        CHECK_AST(data, result);
      }

      SECTION("R^d*R^d -> R^dxd")
      {
        std::string_view data = R"(
let f : R^2*R^2 -> R^2x2, (x,y) -> (x[0], y[0], x[1], y[1]);
)";

        std::string_view result = R"(
(root:void)
 `-(language::fct_declaration:void)
     `-(language::name:f:function)
)";

        CHECK_AST(data, result);
      }
    }

    SECTION("R-functions")
    {
      SECTION("multiple variable")
      {
        std::string_view data = R"(
let weird : R^2*R -> R, (x,y) -> x[0]-y*x[1];
)";

        std::string_view result = R"(
(root:void)
 `-(language::fct_declaration:void)
     `-(language::name:weird:function)
)";

        CHECK_AST(data, result);
      }

      SECTION("multiple variable")
      {
        std::string_view data = R"(
let substract : R*R -> R, (x,y) -> x-y;
)";

        std::string_view result = R"(
(root:void)
 `-(language::fct_declaration:void)
     `-(language::name:substract:function)
)";

        CHECK_AST(data, result);
      }

      SECTION("multiple values")
      {
        std::string_view data = R"(
let square : R -> R*R, x -> (x,x*x);
)";

        std::string_view result = R"(
(root:void)
 `-(language::fct_declaration:void)
     `-(language::name:square:function)
)";

        CHECK_AST(data, result);
      }

      SECTION("name -> expression")
      {
        std::string_view data = R"(
let f : R -> R, x -> x;
)";

        std::string_view result = R"(
(root:void)
 `-(language::fct_declaration:void)
     `-(language::name:f:function)
)";

        CHECK_AST(data, result);
      }

      SECTION("name list -> expression")
      {
        std::string_view data = R"(
let f : R -> R, (x) -> (x+2)/2;
)";

        std::string_view result = R"(
(root:void)
 `-(language::fct_declaration:void)
     `-(language::name:f:function)
)";

        CHECK_AST(data, result);
      }

      SECTION("name -> expression list")
      {
        std::string_view data = R"(
let f : R -> R, x -> (x*x);
)";

        std::string_view result = R"(
(root:void)
 `-(language::fct_declaration:void)
     `-(language::name:f:function)
)";

        CHECK_AST(data, result);
      }

      SECTION("name list -> expression list")
      {
        std::string_view data = R"(
let f : R -> R, (x) -> (x+1);
)";

        std::string_view result = R"(
(root:void)
 `-(language::fct_declaration:void)
     `-(language::name:f:function)
)";

        CHECK_AST(data, result);
      }
    }

    SECTION("Z-functions")
    {
      std::string_view data = R"(
let f : Z -> Z, z -> z-1;
)";

      std::string_view result = R"(
(root:void)
 `-(language::fct_declaration:void)
     `-(language::name:f:function)
)";

      CHECK_AST(data, result);
    }

    SECTION("N-functions")
    {
      std::string_view data = R"(
let f : N -> N, x -> (x+1)/2;
)";

      std::string_view result = R"(
(root:void)
 `-(language::fct_declaration:void)
     `-(language::name:f:function)
)";

      CHECK_AST(data, result);
    }

    SECTION("B-functions")
    {
      std::string_view data = R"(
let f : N*B -> B, (n,b) -> (n>3) and b;
)";

      std::string_view result = R"(
(root:void)
 `-(language::fct_declaration:void)
     `-(language::name:f:function)
)";

      CHECK_AST(data, result);
    }

    SECTION("string-functions")
    {
      std::string_view data = R"(
let cat : string*N -> string, (s,n) -> s+n;
)";

      std::string_view result = R"(
(root:void)
 `-(language::fct_declaration:void)
     `-(language::name:cat:function)
)";

      CHECK_AST(data, result);
    }

    SECTION("builtin-functions")
    {
      std::string_view data = R"(
let foo : builtin_t*N -> builtin_t, (b,n) -> b;
)";

      std::string_view result = R"(
(root:void)
 `-(language::fct_declaration:void)
     `-(language::name:foo:function)
)";

      CHECK_AST_WITH_BUILTIN(data, result);
    }

    SECTION("errors")
    {
      SECTION("wrong parameter number")
      {
        std::string_view data = R"(
let f : R*Z -> B, x -> 3;
)";
        TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
        auto ast = ASTBuilder::build(input);
        ASTSymbolTableBuilder{*ast};

        REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast},
                            "number of product spaces (2) R*Z  differs from number of variables (1) x");
      }

      SECTION("wrong parameter number 2")
      {
        std::string_view data = R"(
let f : R -> B, (x,y) -> 3;
)";
        TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
        auto ast = ASTBuilder::build(input);
        ASTSymbolTableBuilder{*ast};

        REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast},
                            "number of product spaces (1) R differs from number of variables (2) (x,y) ");
      }

      SECTION("wrong image size")
      {
        std::string_view data = R"(
let f : R*Z -> B, (x,z) -> (3, x);
)";
        TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
        auto ast = ASTBuilder::build(input);
        ASTSymbolTableBuilder{*ast};

        REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast},
                            "number of image spaces (1) B differs from number of expressions (2) (3, x)");
      }

      SECTION("wrong image size 2")
      {
        std::string_view data = R"(
let f : R -> R*R, x -> x*x*x;
)";
        TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
        auto ast = ASTBuilder::build(input);

        ASTModulesImporter{*ast};
        ASTNodeTypeCleaner<language::import_instruction>{*ast};

        ASTSymbolTableBuilder{*ast};

        REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast},
                            "number of image spaces (2) R*R differs from number of expressions (1) x*x*x");
      }

      SECTION("wrong image size 3")
      {
        std::string_view data = R"(
let f : R -> R^2x2, x -> (x, 2*x, 2);
)";
        TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
        auto ast = ASTBuilder::build(input);

        ASTModulesImporter{*ast};
        ASTNodeTypeCleaner<language::import_instruction>{*ast};

        ASTSymbolTableBuilder{*ast};

        REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast},
                            "expecting 4 scalar expressions or an R^2x2, found 3 scalar expressions");
      }

      SECTION("undefined type identifier")
      {
        std::string_view data = R"(
let x:X;
)";
        TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
        auto ast = ASTBuilder::build(input);
        ASTSymbolTableBuilder{*ast};

        REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, "undefined type identifier");
      }

      SECTION("undefined type identifier")
      {
        std::string_view data = R"(
let X:R, X = 3;
let x:X;
)";
        TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
        auto ast = ASTBuilder::build(input);
        ASTSymbolTableBuilder{*ast};

        REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast},
                            "invalid type identifier, 'X' was previously defined as a 'R'");
      }

      SECTION("undefined image type identifier")
      {
        std::string_view data = R"(
let f: R -> X, x -> x;
)";
        TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
        auto ast = ASTBuilder::build(input);
        ASTSymbolTableBuilder{*ast};

        REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, "undefined type identifier");
      }

      SECTION("invalid image type identifier")
      {
        std::string_view data = R"(
let X: R, X = 3;
let f: R -> X, x -> x;
)";
        TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
        auto ast = ASTBuilder::build(input);
        ASTSymbolTableBuilder{*ast};

        REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast},
                            "invalid type identifier, 'X' was previously defined as a 'R'");
      }
    }
  }

  SECTION("function evaluation")
  {
    SECTION("R^d-functions")
    {
      SECTION("single argument")
      {
        std::string_view data = R"(
let f : R^2 -> R^2, x -> (x[0]+1, x[1]-2);
let x : R^2, x = (1,2);
x = f(x);
)";

        std::string_view result = R"(
(root:void)
 +-(language::fct_declaration:void)
 |   `-(language::name:f:function)
 +-(language::var_declaration:void)
 |   +-(language::name:x:R^2)
 |   +-(language::vector_type:R^2)
 |   |   +-(language::R_set:R)
 |   |   `-(language::integer:2:Z)
 |   +-(language::name:x:R^2)
 |   `-(language::expression_list:Z*Z)
 |       +-(language::integer:1:Z)
 |       `-(language::integer:2:Z)
 `-(language::eq_op:void)
     +-(language::name:x:R^2)
     `-(language::function_evaluation:R^2)
         +-(language::name:f:function)
         `-(language::name:x:R^2)
)";

        CHECK_AST(data, result);
      }
    }

    SECTION("R-functions")
    {
      SECTION("single argument")
      {
        std::string_view data = R"(
let incr : R -> R, x -> x+1;
let x : R, x = incr(3);
)";

        std::string_view result = R"(
(root:void)
 +-(language::fct_declaration:void)
 |   `-(language::name:incr:function)
 `-(language::var_declaration:void)
     +-(language::name:x:R)
     +-(language::R_set:R)
     +-(language::name:x:R)
     `-(language::function_evaluation:R)
         +-(language::name:incr:function)
         `-(language::integer:3:Z)
)";

        CHECK_AST(data, result);
      }

      SECTION("multiple variable")
      {
        std::string_view data = R"(
let substract : R*R -> R, (x,y) -> x-y;
let  diff : R, diff = substract(3,2);
)";

        std::string_view result = R"(
(root:void)
 +-(language::fct_declaration:void)
 |   `-(language::name:substract:function)
 `-(language::var_declaration:void)
     +-(language::name:diff:R)
     +-(language::R_set:R)
     +-(language::name:diff:R)
     `-(language::function_evaluation:R)
         +-(language::name:substract:function)
         `-(language::function_argument_list:Z*Z)
             +-(language::integer:3:Z)
             `-(language::integer:2:Z)
)";

        CHECK_AST(data, result);
      }
    }

    SECTION("Z-functions")
    {
      std::string_view data = R"(
let incr : Z -> Z, z -> z+1;
let z : Z, z = incr(3);
)";

      std::string_view result = R"(
(root:void)
 +-(language::fct_declaration:void)
 |   `-(language::name:incr:function)
 `-(language::var_declaration:void)
     +-(language::name:z:Z)
     +-(language::Z_set:Z)
     +-(language::name:z:Z)
     `-(language::function_evaluation:Z)
         +-(language::name:incr:function)
         `-(language::integer:3:Z)
)";

      CHECK_AST(data, result);
    }

    SECTION("N-function")
    {
      std::string_view data = R"(
let double : N -> N, n -> 2*n;
let n : N, n = double(3);
)";

      std::string_view result = R"(
(root:void)
 +-(language::fct_declaration:void)
 |   `-(language::name:double:function)
 `-(language::var_declaration:void)
     +-(language::name:n:N)
     +-(language::N_set:N)
     +-(language::name:n:N)
     `-(language::function_evaluation:N)
         +-(language::name:double:function)
         `-(language::integer:3:Z)
)";

      CHECK_AST(data, result);
    }

    SECTION("B-function")
    {
      std::string_view data = R"(
let greater_than_2 : R -> B, x -> x>2;
let b : B, b = greater_than_2(3);
)";

      std::string_view result = R"(
(root:void)
 +-(language::fct_declaration:void)
 |   `-(language::name:greater_than_2:function)
 `-(language::var_declaration:void)
     +-(language::name:b:B)
     +-(language::B_set:B)
     +-(language::name:b:B)
     `-(language::function_evaluation:B)
         +-(language::name:greater_than_2:function)
         `-(language::integer:3:Z)
)";

      CHECK_AST(data, result);
    }

    SECTION("string-function")
    {
      std::string_view data = R"(
let cat : string*string -> string, (s,t) -> s+t;
let s : string, s = cat("foo", "bar");
)";

      std::string_view result = R"(
(root:void)
 +-(language::fct_declaration:void)
 |   `-(language::name:cat:function)
 `-(language::var_declaration:void)
     +-(language::name:s:string)
     +-(language::string_type:string)
     +-(language::name:s:string)
     `-(language::function_evaluation:string)
         +-(language::name:cat:function)
         `-(language::function_argument_list:string*string)
             +-(language::literal:"foo":string)
             `-(language::literal:"bar":string)
)";

      CHECK_AST(data, result);
    }

    SECTION("bultin_t-function")
    {
      std::string_view data = R"(
let foo : builtin_t*N -> builtin_t, (b,n) -> b;
let b0: builtin_t;
let b : builtin_t, b = foo(b0, 1);
)";

      std::string_view result = R"(
(root:void)
 +-(language::fct_declaration:void)
 |   `-(language::name:foo:function)
 +-(language::var_declaration:void)
 |   +-(language::name:b0:builtin_t)
 |   `-(language::type_name_id:builtin_t)
 `-(language::var_declaration:void)
     +-(language::name:b:builtin_t)
     +-(language::type_name_id:builtin_t)
     +-(language::name:b:builtin_t)
     `-(language::function_evaluation:builtin_t)
         +-(language::name:foo:function)
         `-(language::function_argument_list:builtin_t*Z)
             +-(language::name:b0:builtin_t)
             `-(language::integer:1:Z)
)";

      CHECK_AST_WITH_BUILTIN(data, result);
    }

    SECTION("compound return function")
    {
      std::string_view data = R"(
let x_x2 : R -> R*R, x -> (x,x*x);
let (x,x2) : R*R, (x,x2) = x_x2(3);
)";

      std::string_view result = R"(
(root:void)
 +-(language::fct_declaration:void)
 |   `-(language::name:x_x2:function)
 `-(language::var_declaration:void)
     +-(language::name_list:R*R)
     |   +-(language::name:x:R)
     |   `-(language::name:x2:R)
     +-(language::type_expression:R*R)
     |   +-(language::R_set:R)
     |   `-(language::R_set:R)
     +-(language::name_list:R*R)
     |   +-(language::name:x:R)
     |   `-(language::name:x2:R)
     `-(language::function_evaluation:R*R)
         +-(language::name:x_x2:function)
         `-(language::integer:3:Z)
)";

      CHECK_AST(data, result);
    }

    SECTION("errors")
    {
      SECTION("not a function")
      {
        std::string_view data = R"(
let not_a_function : R, not_a_function = 3;
not_a_function(2,3);
)";
        TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
        auto ast = ASTBuilder::build(input);
        ASTSymbolTableBuilder{*ast};

        REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, "invalid function call\n"
                                                          "note: 'not_a_function' (type: R) is not a function!");
      }
    }
  }

  SECTION("ostream")
  {
    std::string_view data = R"(
cout << "cout\n";
cerr << "cerr\n";
clog << "clog\n";
)";

    std::string_view result = R"(
(root:void)
 +-(language::cout_kw:void)
 |   `-(language::literal:"cout\n":string)
 +-(language::cerr_kw:void)
 |   `-(language::literal:"cerr\n":string)
 `-(language::clog_kw:void)
     `-(language::literal:"clog\n":string)
)";

    CHECK_AST(data, result);
  }

  SECTION("for-statement")
  {
    std::string_view data = R"(
for (let i : N, i=0; i<3; ++i){
  cout << i << "\n";
}
)";

    std::string_view result = R"(
(root:void)
 `-(language::for_statement:void)
     +-(language::var_declaration:void)
     |   +-(language::name:i:N)
     |   +-(language::N_set:N)
     |   +-(language::name:i:N)
     |   `-(language::integer:0:Z)
     +-(language::lesser_op:B)
     |   +-(language::name:i:N)
     |   `-(language::integer:3:Z)
     +-(language::unary_plusplus:N)
     |   `-(language::name:i:N)
     `-(language::cout_kw:void)
         +-(language::name:i:N)
         `-(language::literal:"\n":string)
)";

    CHECK_AST(data, result);
  }

  SECTION("B set")
  {
    std::string_view data = R"(
let b:B;
)";

    std::string_view result = R"(
(root:void)
 `-(language::var_declaration:void)
     +-(language::name:b:B)
     `-(language::B_set:B)
)";

    CHECK_AST(data, result);
  }

  SECTION("N set")
  {
    std::string_view data = R"(
let n :N;
)";

    std::string_view result = R"(
(root:void)
 `-(language::var_declaration:void)
     +-(language::name:n:N)
     `-(language::N_set:N)
)";

    CHECK_AST(data, result);
  }

  SECTION("Z set")
  {
    std::string_view data = R"(
let z:Z;
)";

    std::string_view result = R"(
(root:void)
 `-(language::var_declaration:void)
     +-(language::name:z:Z)
     `-(language::Z_set:Z)
)";

    CHECK_AST(data, result);
  }

  SECTION("R set")
  {
    std::string_view data = R"(
let r:R;
)";

    std::string_view result = R"(
(root:void)
 `-(language::var_declaration:void)
     +-(language::name:r:R)
     `-(language::R_set:R)
)";

    CHECK_AST(data, result);
  }

  SECTION("string")
  {
    std::string_view data = R"(
let s: string;
)";

    std::string_view result = R"(
(root:void)
 `-(language::var_declaration:void)
     +-(language::name:s:string)
     `-(language::string_type:string)
)";

    CHECK_AST(data, result);
  }

  SECTION("type_id")
  {
    std::string_view data = R"(
// invalid conversion just checking grammar
let t : builtin_t, t= 1;
)";

    std::string_view result = R"(
(root:void)
 `-(language::var_declaration:void)
     +-(language::name:t:builtin_t)
     +-(language::type_name_id:builtin_t)
     +-(language::name:t:builtin_t)
     `-(language::integer:1:Z)
)";

    CHECK_AST_WITH_BUILTIN(data, result);
  }

  SECTION("continue")
  {
    std::string_view data = R"(
continue;
)";

    std::string_view result = R"(
(root:void)
 `-(language::continue_kw:void)
)";

    CHECK_AST(data, result);
  }

  SECTION("break")
  {
    std::string_view data = R"(
break;
)";

    std::string_view result = R"(
(root:void)
 `-(language::break_kw:void)
)";

    CHECK_AST(data, result);
  }

  SECTION("eq_op")
  {
    std::string_view data = R"(
let a:N;
a = 1;
)";

    std::string_view result = R"(
(root:void)
 +-(language::var_declaration:void)
 |   +-(language::name:a:N)
 |   `-(language::N_set:N)
 `-(language::eq_op:void)
     +-(language::name:a:N)
     `-(language::integer:1:Z)
)";

    CHECK_AST(data, result);
  }

  SECTION("multiplyeq_op")
  {
    std::string_view data = R"(
let a:N, a = 1;
a *= 1.2;
)";

    std::string_view result = R"(
(root:void)
 +-(language::var_declaration:void)
 |   +-(language::name:a:N)
 |   +-(language::N_set:N)
 |   +-(language::name:a:N)
 |   `-(language::integer:1:Z)
 `-(language::multiplyeq_op:void)
     +-(language::name:a:N)
     `-(language::real:1.2:R)
)";

    CHECK_AST(data, result);
  }

  SECTION("divideeq_op")
  {
    std::string_view data = R"(
let a:R, a = 3;
a /= 2;
)";

    std::string_view result = R"(
(root:void)
 +-(language::var_declaration:void)
 |   +-(language::name:a:R)
 |   +-(language::R_set:R)
 |   +-(language::name:a:R)
 |   `-(language::integer:3:Z)
 `-(language::divideeq_op:void)
     +-(language::name:a:R)
     `-(language::integer:2:Z)
)";

    CHECK_AST(data, result);
  }

  SECTION("pluseq_op")
  {
    std::string_view data = R"(
let a :Z, a = 3;
a += 2;
)";

    std::string_view result = R"(
(root:void)
 +-(language::var_declaration:void)
 |   +-(language::name:a:Z)
 |   +-(language::Z_set:Z)
 |   +-(language::name:a:Z)
 |   `-(language::integer:3:Z)
 `-(language::pluseq_op:void)
     +-(language::name:a:Z)
     `-(language::integer:2:Z)
)";

    CHECK_AST(data, result);
  }

  SECTION("minuseq_op")
  {
    std::string_view data = R"(
let a:Z, a = 1;
a -= 2;
)";

    std::string_view result = R"(
(root:void)
 +-(language::var_declaration:void)
 |   +-(language::name:a:Z)
 |   +-(language::Z_set:Z)
 |   +-(language::name:a:Z)
 |   `-(language::integer:1:Z)
 `-(language::minuseq_op:void)
     +-(language::name:a:Z)
     `-(language::integer:2:Z)
)";

    CHECK_AST(data, result);
  }

  SECTION("for simple")
  {
    std::string_view data = R"(
for (;;);
)";

    std::string_view result = R"(
(root:void)
 `-(language::for_statement:void)
     +-(language::for_init:void)
     +-(language::for_test:B)
     +-(language::for_post:void)
     `-(language::for_statement_block:void)
)";

    CHECK_AST(data, result);
  }

  SECTION("for std")
  {
    std::string_view data = R"(
for (let i:Z, i=0; i<3; i += 1) { i += 2; }
)";

    std::string_view result = R"(
(root:void)
 `-(language::for_statement:void)
     +-(language::var_declaration:void)
     |   +-(language::name:i:Z)
     |   +-(language::Z_set:Z)
     |   +-(language::name:i:Z)
     |   `-(language::integer:0:Z)
     +-(language::lesser_op:B)
     |   +-(language::name:i:Z)
     |   `-(language::integer:3:Z)
     +-(language::pluseq_op:void)
     |   +-(language::name:i:Z)
     |   `-(language::integer:1:Z)
     `-(language::pluseq_op:void)
         +-(language::name:i:Z)
         `-(language::integer:2:Z)
)";

    CHECK_AST(data, result);
  }

  SECTION("empty block")
  {
    std::string_view data = R"(
{}
)";

    std::string_view result = R"(
(root:void)
)";

    CHECK_AST(data, result);
  }

  SECTION("block")
  {
    std::string_view data = R"(
{
  3;
}
)";

    std::string_view result = R"(
(root:void)
 `-(language::block:void)
     `-(language::integer:3:Z)
)";

    CHECK_AST(data, result);
  }

  SECTION("if statements")
  {
    SECTION("empty if")
    {
      std::string_view data = R"(
if (true);
)";

      std::string_view result = R"(
(root:void)
 `-(language::if_statement:void)
     +-(language::true_kw:B)
     `-(language::statement_block:void)
)";

      CHECK_AST(data, result);
    }

    SECTION("if else")
    {
      std::string_view data = R"(
if (true)
  1;
else
  2;
)";

      std::string_view result = R"(
(root:void)
 `-(language::if_statement:void)
     +-(language::true_kw:B)
     +-(language::integer:1:Z)
     `-(language::integer:2:Z)
)";

      CHECK_AST(data, result);
    }

    SECTION("if else simplify block")
    {
      std::string_view data = R"(
if (true) {
  1;
} else {
  2;
}
)";

      std::string_view result = R"(
(root:void)
 `-(language::if_statement:void)
     +-(language::true_kw:B)
     +-(language::integer:1:Z)
     `-(language::integer:2:Z)
)";

      CHECK_AST(data, result);
    }

    SECTION("if block")
    {
      std::string_view data = R"(
if (true) {
  1;
  2;
}
)";

      std::string_view result = R"(
(root:void)
 `-(language::if_statement:void)
     +-(language::true_kw:B)
     `-(language::block:void)
         +-(language::integer:1:Z)
         `-(language::integer:2:Z)
)";

      CHECK_AST(data, result);
    }

    SECTION("if invalid condition")
    {
      std::string_view data = R"(
if ("string");
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto ast = ASTBuilder::build(input);
      ASTSymbolTableBuilder{*ast};
      REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, "invalid implicit conversion: string -> B");
    }
  }

  SECTION("while statements")
  {
    SECTION("empty while")
    {
      std::string_view data = R"(
while (true);
)";

      std::string_view result = R"(
(root:void)
 `-(language::while_statement:void)
     +-(language::true_kw:B)
     `-(language::statement_block:void)
)";

      CHECK_AST(data, result);
    }

    SECTION("simple while")
    {
      std::string_view data = R"(
while (true) 1;
)";

      std::string_view result = R"(
(root:void)
 `-(language::while_statement:void)
     +-(language::true_kw:B)
     `-(language::integer:1:Z)
)";

      CHECK_AST(data, result);
    }

    SECTION("while simplified block")
    {
      std::string_view data = R"(
while (true) {
  1;
}
)";

      std::string_view result = R"(
(root:void)
 `-(language::while_statement:void)
     +-(language::true_kw:B)
     `-(language::integer:1:Z)
)";

      CHECK_AST(data, result);
    }

    SECTION("while block_statement")
    {
      std::string_view data = R"(
while (true) {
  1;
  2;
}
)";

      std::string_view result = R"(
(root:void)
 `-(language::while_statement:void)
     +-(language::true_kw:B)
     `-(language::block:void)
         +-(language::integer:1:Z)
         `-(language::integer:2:Z)
)";

      CHECK_AST(data, result);
    }

    SECTION("while invalid condition")
    {
      std::string_view data = R"(
while ("string");
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto ast = ASTBuilder::build(input);
      ASTSymbolTableBuilder{*ast};
      REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, "invalid implicit conversion: string -> B");
    }
  }

  SECTION("do-while statements")
  {
    SECTION("empty do-while")
    {
      std::string_view data = R"(
do ; while (true);
)";

      std::string_view result = R"(
(root:void)
 `-(language::do_while_statement:void)
     +-(language::statement_block:void)
     `-(language::true_kw:B)
)";

      CHECK_AST(data, result);
    }

    SECTION("simple do-while")
    {
      std::string_view data = R"(
do 1; while (true);
)";

      std::string_view result = R"(
(root:void)
 `-(language::do_while_statement:void)
     +-(language::integer:1:Z)
     `-(language::true_kw:B)
)";

      CHECK_AST(data, result);
    }

    SECTION("do-while simplified block")
    {
      std::string_view data = R"(
do {
 1;
} while (true);
)";

      std::string_view result = R"(
(root:void)
 `-(language::do_while_statement:void)
     +-(language::integer:1:Z)
     `-(language::true_kw:B)
)";

      CHECK_AST(data, result);
    }

    SECTION("do-while block")
    {
      std::string_view data = R"(
do {
 1;
 2;
} while (true);
)";

      std::string_view result = R"(
(root:void)
 `-(language::do_while_statement:void)
     +-(language::block:void)
     |   +-(language::integer:1:Z)
     |   `-(language::integer:2:Z)
     `-(language::true_kw:B)
)";

      CHECK_AST(data, result);
    }

    SECTION("do-while invalid condition")
    {
      std::string_view data = R"(
do 1; while ("string");
    )";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto ast = ASTBuilder::build(input);
      ASTSymbolTableBuilder{*ast};
      REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, "invalid implicit conversion: string -> B");
    }
  }

  SECTION("boolean statements")
  {
    SECTION("unary not")
    {
      std::string_view data = R"(
not false;
)";

      std::string_view result = R"(
(root:void)
 `-(language::unary_not:B)
     `-(language::false_kw:B)
)";

      CHECK_AST(data, result);
    }

    SECTION("lesser op")
    {
      std::string_view data = R"(
1<2;
)";

      std::string_view result = R"(
(root:void)
 `-(language::lesser_op:B)
     +-(language::integer:1:Z)
     `-(language::integer:2:Z)
)";

      CHECK_AST(data, result);
    }

    SECTION("lesser_or_eq op")
    {
      std::string_view data = R"(
1<=2;
)";

      std::string_view result = R"(
(root:void)
 `-(language::lesser_or_eq_op:B)
     +-(language::integer:1:Z)
     `-(language::integer:2:Z)
)";

      CHECK_AST(data, result);
    }

    SECTION("greater op")
    {
      std::string_view data = R"(
1>2;
)";

      std::string_view result = R"(
(root:void)
 `-(language::greater_op:B)
     +-(language::integer:1:Z)
     `-(language::integer:2:Z)
)";

      CHECK_AST(data, result);
    }

    SECTION("greater_or_eq op")
    {
      std::string_view data = R"(
1>=2;
)";

      std::string_view result = R"(
(root:void)
 `-(language::greater_or_eq_op:B)
     +-(language::integer:1:Z)
     `-(language::integer:2:Z)
)";

      CHECK_AST(data, result);
    }

    SECTION("eqeq op")
    {
      std::string_view data = R"(
1==2;
)";

      std::string_view result = R"(
(root:void)
 `-(language::eqeq_op:B)
     +-(language::integer:1:Z)
     `-(language::integer:2:Z)
)";

      CHECK_AST(data, result);
    }

    SECTION("not_eq op")
    {
      std::string_view data = R"(
1!=2;
)";

      std::string_view result = R"(
(root:void)
 `-(language::not_eq_op:B)
     +-(language::integer:1:Z)
     `-(language::integer:2:Z)
)";

      CHECK_AST(data, result);
    }

    SECTION("and op")
    {
      std::string_view data = R"(
false and true;
)";

      std::string_view result = R"(
(root:void)
 `-(language::and_op:B)
     +-(language::false_kw:B)
     `-(language::true_kw:B)
)";

      CHECK_AST(data, result);
    }

    SECTION("or op")
    {
      std::string_view data = R"(
false or true;
)";

      std::string_view result = R"(
(root:void)
 `-(language::or_op:B)
     +-(language::false_kw:B)
     `-(language::true_kw:B)
)";

      CHECK_AST(data, result);
    }

    SECTION("xor op")
    {
      std::string_view data = R"(
true xor false;
)";

      std::string_view result = R"(
(root:void)
 `-(language::xor_op:B)
     +-(language::true_kw:B)
     `-(language::false_kw:B)
)";

      CHECK_AST(data, result);
    }
  }

  SECTION("unary operators")
  {
    SECTION("unary minus")
    {
      std::string_view data = R"(
- 1;
)";

      std::string_view result = R"(
(root:void)
 `-(language::unary_minus:Z)
     `-(language::integer:1:Z)
)";

      CHECK_AST(data, result);
    }

    SECTION("unary plusplus")
    {
      std::string_view data = R"(
++1;
)";

      std::string_view result = R"(
(root:void)
 `-(language::unary_plusplus:Z)
     `-(language::integer:1:Z)
)";

      CHECK_AST(data, result);
    }

    SECTION("unary minusminus")
    {
      std::string_view data = R"(
--1;
)";

      std::string_view result = R"(
(root:void)
 `-(language::unary_minusminus:Z)
     `-(language::integer:1:Z)
)";

      CHECK_AST(data, result);
    }

    SECTION("post plusplus")
    {
      std::string_view data = R"(
1++;
)";

      std::string_view result = R"(
(root:void)
 `-(language::post_plusplus:Z)
     `-(language::integer:1:Z)
)";

      CHECK_AST(data, result);
    }

    SECTION("post minusminus")
    {
      std::string_view data = R"(
1--;
)";

      std::string_view result = R"(
(root:void)
 `-(language::post_minusminus:Z)
     `-(language::integer:1:Z)
)";

      CHECK_AST(data, result);
    }
  }

  SECTION("binary operators")
  {
    SECTION("plus")
    {
      std::string_view data = R"(
1+2;
)";

      std::string_view result = R"(
(root:void)
 `-(language::plus_op:Z)
     +-(language::integer:1:Z)
     `-(language::integer:2:Z)
)";

      CHECK_AST(data, result);
    }

    SECTION("minus")
    {
      std::string_view data = R"(
1-2;
)";

      std::string_view result = R"(
(root:void)
 `-(language::minus_op:Z)
     +-(language::integer:1:Z)
     `-(language::integer:2:Z)
)";

      CHECK_AST(data, result);
    }

    SECTION("multiply")
    {
      std::string_view data = R"(
1*2;
)";

      std::string_view result = R"(
(root:void)
 `-(language::multiply_op:Z)
     +-(language::integer:1:Z)
     `-(language::integer:2:Z)
)";

      CHECK_AST(data, result);
    }

    SECTION("divide")
    {
      std::string_view data = R"(
1/2;
)";

      std::string_view result = R"(
(root:void)
 `-(language::divide_op:Z)
     +-(language::integer:1:Z)
     `-(language::integer:2:Z)
)";

      CHECK_AST(data, result);
    }

    SECTION("invalid operands")
    {
      std::string_view data = R"(
1+"string";
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto ast = ASTBuilder::build(input);
      ASTSymbolTableBuilder{*ast};
      REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, "undefined binary operator\n"
                                                        "note: incompatible operand types Z and string");
    }
  }
}