#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/ASTNodeExpressionBuilder.hpp>
#include <language/ast/ASTNodeFunctionEvaluationExpressionBuilder.hpp>
#include <language/ast/ASTNodeFunctionExpressionBuilder.hpp>
#include <language/ast/ASTNodeTypeCleaner.hpp>
#include <language/ast/ASTSymbolTableBuilder.hpp>
#include <language/utils/ASTPrinter.hpp>
#include <stdexcept>
#include <utils/Demangle.hpp>

#include <test_BuiltinFunctionRegister.hpp>

#include <pegtl/string_input.hpp>

#include <memory>
#include <unordered_map>

#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>) or    \
                  (std::is_same_v<std::decay_t<decltype(expected_output)>, std::string>));          \
                                                                                                    \
    TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};                                      \
    auto ast = ASTBuilder::build(input);                                                            \
                                                                                                    \
    test_only::test_BuiltinFunctionRegister{*ast};                                                  \
                                                                                                    \
    ASTSymbolTableBuilder{*ast};                                                                    \
    ASTNodeDataTypeBuilder{*ast};                                                                   \
                                                                                                    \
    ASTNodeTypeCleaner<language::var_declaration>{*ast};                                            \
    ASTNodeTypeCleaner<language::fct_declaration>{*ast};                                            \
    ASTNodeExpressionBuilder{*ast};                                                                 \
                                                                                                    \
    std::stringstream ast_output;                                                                   \
    ast_output << '\n' << ASTPrinter{*ast, ASTPrinter::Format::raw, {ASTPrinter::Info::exec_type}}; \
                                                                                                    \
    REQUIRE(ast_output.str() == expected_output);                                                   \
  }

#define CHECK_AST_THROWS_WITH(data, expected_error)                                             \
  {                                                                                             \
    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_error)>, std::string_view>) or \
                  (std::is_same_v<std::decay_t<decltype(expected_error)>, std::string>));       \
                                                                                                \
    TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};                                  \
    auto ast = ASTBuilder::build(input);                                                        \
                                                                                                \
    test_only::test_BuiltinFunctionRegister{*ast};                                              \
                                                                                                \
    ASTSymbolTableBuilder{*ast};                                                                \
    ASTNodeDataTypeBuilder{*ast};                                                               \
                                                                                                \
    ASTNodeTypeCleaner<language::var_declaration>{*ast};                                        \
    ASTNodeTypeCleaner<language::fct_declaration>{*ast};                                        \
    REQUIRE_THROWS_WITH(ASTNodeExpressionBuilder{*ast}, expected_error);                        \
  }

#define CHECK_AST_THROWS_AT_BUILD_WITH(data, expected_error)                                    \
  {                                                                                             \
    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_error)>, std::string_view>) or \
                  (std::is_same_v<std::decay_t<decltype(expected_error)>, std::string>));       \
                                                                                                \
    TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};                                  \
    auto ast = ASTBuilder::build(input);                                                        \
                                                                                                \
    test_only::test_BuiltinFunctionRegister{*ast};                                              \
                                                                                                \
    ASTSymbolTableBuilder{*ast};                                                                \
    REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, expected_error);                          \
  }

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

TEST_CASE("ASTNodeBuiltinFunctionExpressionBuilder", "[language]")
{
  SECTION("R -> R")
  {
    SECTION("from R")
    {
      std::string_view data = R"(
RtoR(1.);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:RtoR:FakeProcessor)
     `-(language::real:1.:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("from Z")
    {
      std::string_view data = R"(
RtoR(1);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:RtoR:FakeProcessor)
     `-(language::integer:1:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("from N")
    {
      std::string_view data = R"(
let n : N, n = 1;
RtoR(n);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:RtoR:FakeProcessor)
     `-(language::name:n:NameProcessor)
)";

      CHECK_AST(data, result);
    }

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

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:RtoR:FakeProcessor)
     `-(language::true_kw:ValueProcessor)
)";

      CHECK_AST(data, result);
    }
  }

  SECTION("R -> R^1")
  {
    SECTION("from R")
    {
      std::string_view data = R"(
RtoR1(1.);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:RtoR1:FakeProcessor)
     `-(language::real:1.:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("from Z")
    {
      std::string_view data = R"(
RtoR1(1);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:RtoR1:FakeProcessor)
     `-(language::integer:1:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("from N")
    {
      std::string_view data = R"(
let n : N, n = 1;
RtoR1(n);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:RtoR1:FakeProcessor)
     `-(language::name:n:NameProcessor)
)";

      CHECK_AST(data, result);
    }

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

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:RtoR1:FakeProcessor)
     `-(language::true_kw:ValueProcessor)
)";

      CHECK_AST(data, result);
    }
  }

  SECTION("R -> R^1x1")
  {
    SECTION("from R")
    {
      std::string_view data = R"(
RtoR11(1.);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:RtoR11:FakeProcessor)
     `-(language::real:1.:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("from Z")
    {
      std::string_view data = R"(
RtoR11(1);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:RtoR11:FakeProcessor)
     `-(language::integer:1:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("from N")
    {
      std::string_view data = R"(
let n : N, n = 1;
RtoR11(n);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:RtoR11:FakeProcessor)
     `-(language::name:n:NameProcessor)
)";

      CHECK_AST(data, result);
    }

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

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:RtoR11:FakeProcessor)
     `-(language::true_kw:ValueProcessor)
)";

      CHECK_AST(data, result);
    }
  }

  SECTION("R^1 -> R")
  {
    SECTION("from R^1")
    {
      std::string_view data = R"(
let x : R^1, x = 2;
R1toR(x);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:R1toR:FakeProcessor)
     `-(language::name:x:NameProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("from R")
    {
      std::string_view data = R"(
R1toR(1.);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:R1toR:FakeProcessor)
     `-(language::real:1.:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("from Z")
    {
      std::string_view data = R"(
R1toR(1);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:R1toR:FakeProcessor)
     `-(language::integer:1:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("from N")
    {
      std::string_view data = R"(
let n : N, n = 1;
R1toR(n);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:R1toR:FakeProcessor)
     `-(language::name:n:NameProcessor)
)";

      CHECK_AST(data, result);
    }

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

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:R1toR:FakeProcessor)
     `-(language::true_kw:ValueProcessor)
)";

      CHECK_AST(data, result);
    }
  }

  SECTION("R^1x1 -> R")
  {
    SECTION("from R^1x1")
    {
      std::string_view data = R"(
let x : R^1x1, x = 2;
R11toR(x);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:R11toR:FakeProcessor)
     `-(language::name:x:NameProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("from R")
    {
      std::string_view data = R"(
R11toR(1.);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:R11toR:FakeProcessor)
     `-(language::real:1.:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("from Z")
    {
      std::string_view data = R"(
R11toR(1);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:R11toR:FakeProcessor)
     `-(language::integer:1:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("from N")
    {
      std::string_view data = R"(
let n : N, n = 1;
R11toR(n);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:R11toR:FakeProcessor)
     `-(language::name:n:NameProcessor)
)";

      CHECK_AST(data, result);
    }

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

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:R11toR:FakeProcessor)
     `-(language::true_kw:ValueProcessor)
)";

      CHECK_AST(data, result);
    }
  }

  SECTION("R^2 -> R")
  {
    SECTION("from 0")
    {
      std::string_view data = R"(
R2toR(0);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:R2toR:FakeProcessor)
     `-(language::integer:0:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("from R^2")
    {
      std::string_view data = R"(
let x:R^2, x = (1,2);
R2toR(x);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:R2toR:FakeProcessor)
     `-(language::name:x:NameProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("from list")
    {
      std::string_view data = R"(
R2toR((1,2));
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:R2toR:FakeProcessor)
     `-(language::tuple_expression:TupleToVectorProcessor<ASTNodeExpressionListProcessor>)
         +-(language::integer:1:ValueProcessor)
         `-(language::integer:2:ValueProcessor)
)";

      CHECK_AST(data, result);
    }
  }

  SECTION("R^2x2 -> R")
  {
    SECTION("from 0")
    {
      std::string_view data = R"(
R22toR(0);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:R22toR:FakeProcessor)
     `-(language::integer:0:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("from R^2x2")
    {
      std::string_view data = R"(
let x:R^2x2, x = (1,2,3,4);
R22toR(x);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:R22toR:FakeProcessor)
     `-(language::name:x:NameProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("from list")
    {
      std::string_view data = R"(
R22toR((1,2,3,4));
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:R22toR:FakeProcessor)
     `-(language::tuple_expression:TupleToVectorProcessor<ASTNodeExpressionListProcessor>)
         +-(language::integer:1:ValueProcessor)
         +-(language::integer:2:ValueProcessor)
         +-(language::integer:3:ValueProcessor)
         `-(language::integer:4:ValueProcessor)
)";

      CHECK_AST(data, result);
    }
  }

  SECTION("R^3 -> R")
  {
    SECTION("from 0")
    {
      std::string_view data = R"(
R3toR(0);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:R3toR:FakeProcessor)
     `-(language::integer:0:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("from R^3")
    {
      std::string_view data = R"(
let x:R^3, x = (1,2,4);
R3toR(x);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:R3toR:FakeProcessor)
     `-(language::name:x:NameProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("from list")
    {
      std::string_view data = R"(
R3toR((1,2,3));
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:R3toR:FakeProcessor)
     `-(language::tuple_expression:TupleToVectorProcessor<ASTNodeExpressionListProcessor>)
         +-(language::integer:1:ValueProcessor)
         +-(language::integer:2:ValueProcessor)
         `-(language::integer:3:ValueProcessor)
)";

      CHECK_AST(data, result);
    }
  }

  SECTION("R^3x3 -> R")
  {
    SECTION("from 0")
    {
      std::string_view data = R"(
R33toR(0);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:R33toR:FakeProcessor)
     `-(language::integer:0:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("from R^3x3")
    {
      std::string_view data = R"(
let x:R^3x3, x = (1,2,3,4,5,6,7,8,9);
R33toR(x);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:R33toR:FakeProcessor)
     `-(language::name:x:NameProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("from list")
    {
      std::string_view data = R"(
R33toR((1,2,3,4,5,6,7,8,9));
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:R33toR:FakeProcessor)
     `-(language::tuple_expression:TupleToVectorProcessor<ASTNodeExpressionListProcessor>)
         +-(language::integer:1:ValueProcessor)
         +-(language::integer:2:ValueProcessor)
         +-(language::integer:3:ValueProcessor)
         +-(language::integer:4:ValueProcessor)
         +-(language::integer:5:ValueProcessor)
         +-(language::integer:6:ValueProcessor)
         +-(language::integer:7:ValueProcessor)
         +-(language::integer:8:ValueProcessor)
         `-(language::integer:9:ValueProcessor)
)";

      CHECK_AST(data, result);
    }
  }

  SECTION("Z -> R")
  {
    SECTION("from Z")
    {
      std::string_view data = R"(
ZtoR(1);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:ZtoR:FakeProcessor)
     `-(language::integer:1:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("from N")
    {
      std::string_view data = R"(
let n : N, n = 1;
ZtoR(n);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:ZtoR:FakeProcessor)
     `-(language::name:n:NameProcessor)
)";

      CHECK_AST(data, result);
    }

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

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:ZtoR:FakeProcessor)
     `-(language::true_kw:ValueProcessor)
)";

      CHECK_AST(data, result);
    }
  }

  SECTION("N -> R")
  {
    SECTION("from Z")
    {
      std::string_view data = R"(
NtoR(1);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:NtoR:FakeProcessor)
     `-(language::integer:1:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("from N")
    {
      std::string_view data = R"(
let n : N, n = 1;
NtoR(n);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:NtoR:FakeProcessor)
     `-(language::name:n:NameProcessor)
)";

      CHECK_AST(data, result);
    }

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

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:NtoR:FakeProcessor)
     `-(language::true_kw:ValueProcessor)
)";

      CHECK_AST(data, result);
    }
  }

  SECTION("B -> R")
  {
    SECTION("from B")
    {
      std::string_view data = R"(
BtoR(true);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:BtoR:FakeProcessor)
     `-(language::true_kw:ValueProcessor)
)";

      CHECK_AST(data, result);
    }
  }

  SECTION("R*R -> B")
  {
    std::string_view data = R"(
RRtoB(1., 0.);
)";

    std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:RRtoB:FakeProcessor)
     `-(language::function_argument_list:ASTNodeExpressionListProcessor)
         +-(language::real:1.:ValueProcessor)
         `-(language::real:0.:ValueProcessor)
)";

    CHECK_AST(data, result);
  }

  SECTION("R^3*R^2 -> R")
  {
    SECTION("from R^3*R^2")
    {
      std::string_view data = R"(
let x : R^3, x = (1,2,3);
let y : R^2, y = (2,3);
R3R2toR(x,y);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:R3R2toR:FakeProcessor)
     `-(language::function_argument_list:ASTNodeExpressionListProcessor)
         +-(language::name:x:NameProcessor)
         `-(language::name:y:NameProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("from (R,R,R)*(R,R)")
    {
      std::string_view data = R"(
R3R2toR((1,2,3),(3,4));
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:R3R2toR:FakeProcessor)
     `-(language::function_argument_list:ASTNodeExpressionListProcessor)
         +-(language::tuple_expression:TupleToVectorProcessor<ASTNodeExpressionListProcessor>)
         |   +-(language::integer:1:ValueProcessor)
         |   +-(language::integer:2:ValueProcessor)
         |   `-(language::integer:3:ValueProcessor)
         `-(language::tuple_expression:TupleToVectorProcessor<ASTNodeExpressionListProcessor>)
             +-(language::integer:3:ValueProcessor)
             `-(language::integer:4:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("from (R,R,R)*(0)")
    {
      std::string_view data = R"(
R3R2toR((1,2,3),0);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:R3R2toR:FakeProcessor)
     `-(language::function_argument_list:ASTNodeExpressionListProcessor)
         +-(language::tuple_expression:TupleToVectorProcessor<ASTNodeExpressionListProcessor>)
         |   +-(language::integer:1:ValueProcessor)
         |   +-(language::integer:2:ValueProcessor)
         |   `-(language::integer:3:ValueProcessor)
         `-(language::integer:0:ValueProcessor)
)";

      CHECK_AST(data, result);
    }
  }

  SECTION("R^3x3*R^2x2 -> R")
  {
    SECTION("from R^3x3*R^2x2")
    {
      std::string_view data = R"(
let x : R^3x3, x = (1,2,3,4,5,6,7,8,9);
let y : R^2x2, y = (1,2,3,4);
R33R22toR(x,y);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:R33R22toR:FakeProcessor)
     `-(language::function_argument_list:ASTNodeExpressionListProcessor)
         +-(language::name:x:NameProcessor)
         `-(language::name:y:NameProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("from (R,R,R,R,R,R,R,R,R)*(R,R,R,R)")
    {
      std::string_view data = R"(
R33R22toR((1,2,3,4,5,6,7,8,9),(1,2,3,4));
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:R33R22toR:FakeProcessor)
     `-(language::function_argument_list:ASTNodeExpressionListProcessor)
         +-(language::tuple_expression:TupleToVectorProcessor<ASTNodeExpressionListProcessor>)
         |   +-(language::integer:1:ValueProcessor)
         |   +-(language::integer:2:ValueProcessor)
         |   +-(language::integer:3:ValueProcessor)
         |   +-(language::integer:4:ValueProcessor)
         |   +-(language::integer:5:ValueProcessor)
         |   +-(language::integer:6:ValueProcessor)
         |   +-(language::integer:7:ValueProcessor)
         |   +-(language::integer:8:ValueProcessor)
         |   `-(language::integer:9:ValueProcessor)
         `-(language::tuple_expression:TupleToVectorProcessor<ASTNodeExpressionListProcessor>)
             +-(language::integer:1:ValueProcessor)
             +-(language::integer:2:ValueProcessor)
             +-(language::integer:3:ValueProcessor)
             `-(language::integer:4:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("from (R,R,R,R,R,R,R,R,R)*(0)")
    {
      std::string_view data = R"(
R33R22toR((1,2,3,4,5,6,7,8,9),0);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:R33R22toR:FakeProcessor)
     `-(language::function_argument_list:ASTNodeExpressionListProcessor)
         +-(language::tuple_expression:TupleToVectorProcessor<ASTNodeExpressionListProcessor>)
         |   +-(language::integer:1:ValueProcessor)
         |   +-(language::integer:2:ValueProcessor)
         |   +-(language::integer:3:ValueProcessor)
         |   +-(language::integer:4:ValueProcessor)
         |   +-(language::integer:5:ValueProcessor)
         |   +-(language::integer:6:ValueProcessor)
         |   +-(language::integer:7:ValueProcessor)
         |   +-(language::integer:8:ValueProcessor)
         |   `-(language::integer:9:ValueProcessor)
         `-(language::integer:0:ValueProcessor)
)";

      CHECK_AST(data, result);
    }
  }

  SECTION("string -> B")
  {
    std::string_view data = R"(
StoB("foo");
)";

    std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:StoB:FakeProcessor)
     `-(language::literal:"foo":ValueProcessor)
)";

    CHECK_AST(data, result);
  }

  SECTION("builtin_t -> builtin_t")
  {
    std::string_view data = R"(
builtinToBuiltin(a);
)";

    std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:builtinToBuiltin:FakeProcessor)
     `-(language::name:a:NameProcessor)
)";

    CHECK_AST(data, result);
  }

  SECTION("-> tuple")
  {
    SECTION("B -> tuple(Z)")
    {
      std::string_view data = R"(
tuple_ZtoR(true);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:tuple_ZtoR:FakeProcessor)
     `-(language::true_kw:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("tuple(B) -> tuple(Z)")
    {
      std::string_view data = R"(
let t:(B), t =(true, false);
tuple_ZtoR(t);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:tuple_ZtoR:FakeProcessor)
     `-(language::name:t:NameProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("N -> tuple(Z)")
    {
      std::string_view data = R"(
let n:N, n=1;
tuple_ZtoR(n);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:tuple_ZtoR:FakeProcessor)
     `-(language::name:n:NameProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("tuple(B) -> tuple(B)")
    {
      std::string_view data = R"(
let t:(B), t=(true,false);
tuple_BtoR(t);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:tuple_BtoR:FakeProcessor)
     `-(language::name:t:NameProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("tuple(N) -> tuple(N)")
    {
      std::string_view data = R"(
let t:(N), t=(1,3,7);
tuple_NtoR(t);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:tuple_NtoR:FakeProcessor)
     `-(language::name:t:NameProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("tuple(N) -> tuple(Z)")
    {
      std::string_view data = R"(
let t:(N), t=(1,3,7);
tuple_ZtoR(t);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:tuple_ZtoR:FakeProcessor)
     `-(language::name:t:NameProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("Z -> tuple(Z)")
    {
      std::string_view data = R"(
tuple_ZtoR(1);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:tuple_ZtoR:FakeProcessor)
     `-(language::integer:1:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("tuple(Z) -> tuple(Z)")
    {
      std::string_view data = R"(
let t:(Z), t=(1,3,7);
tuple_ZtoR(t);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:tuple_ZtoR:FakeProcessor)
     `-(language::name:t:NameProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("Z -> tuple(R)")
    {
      std::string_view data = R"(
tuple_RtoB(1);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:tuple_RtoB:FakeProcessor)
     `-(language::integer:1:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("tuple(Z) -> tuple(R)")
    {
      std::string_view data = R"(
let t:(Z), t = (1,2,4,6);
tuple_RtoB(t);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:tuple_RtoB:FakeProcessor)
     `-(language::name:t:NameProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("R -> tuple(R)")
    {
      std::string_view data = R"(
tuple_RtoB(1.2);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:tuple_RtoB:FakeProcessor)
     `-(language::real:1.2:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("tuple(R) -> tuple(R)")
    {
      std::string_view data = R"(
let t:(R), t = (1,2,4,6);
tuple_RtoB(t);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:tuple_RtoB:FakeProcessor)
     `-(language::name:t:NameProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("list -> tuple(R)")
    {
      std::string_view data = R"(
let n:N, n = 3;
tuple_RtoB((1.2, 2, true, n));
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:tuple_RtoB:FakeProcessor)
     `-(language::tuple_expression:TupleToVectorProcessor<ASTNodeExpressionListProcessor>)
         +-(language::real:1.2:ValueProcessor)
         +-(language::integer:2:ValueProcessor)
         +-(language::true_kw:ValueProcessor)
         `-(language::name:n:NameProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("R -> tuple(string)")
    {
      std::string_view data = R"(
tuple_stringtoB(1.2);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:tuple_stringtoB:FakeProcessor)
     `-(language::real:1.2:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("string -> tuple(string)")
    {
      std::string_view data = R"(
let s:string, s = "foo";
tuple_stringtoB(s);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:tuple_stringtoB:FakeProcessor)
     `-(language::name:s:NameProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("tuple(R) -> tuple(string)")
    {
      std::string_view data = R"(
let t:(R), t = (1,2,4,6);
tuple_stringtoB(t);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:tuple_stringtoB:FakeProcessor)
     `-(language::name:t:NameProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("tuple(string) -> tuple(string)")
    {
      std::string_view data = R"(
let t:(string), t = ("foo", "bar", "foobar");
tuple_stringtoB(t);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:tuple_stringtoB:FakeProcessor)
     `-(language::name:t:NameProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("literal -> tuple(string)")
    {
      std::string_view data = R"(
tuple_stringtoB("foo");
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:tuple_stringtoB:FakeProcessor)
     `-(language::literal:"foo":ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("list -> tuple(string)")
    {
      std::string_view data = R"(
tuple_stringtoB(("foo",2,"bar"));
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:tuple_stringtoB:FakeProcessor)
     `-(language::tuple_expression:TupleToVectorProcessor<ASTNodeExpressionListProcessor>)
         +-(language::literal:"foo":ValueProcessor)
         +-(language::integer:2:ValueProcessor)
         `-(language::literal:"bar":ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("builtin -> tuple(builtin)")
    {
      std::string_view data = R"(
tuple_builtinToB(a);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:tuple_builtinToB:FakeProcessor)
     `-(language::name:a:NameProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("list -> tuple(builtin)")
    {
      std::string_view data = R"(
tuple_builtinToB((a,b,a));
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:tuple_builtinToB:FakeProcessor)
     `-(language::tuple_expression:TupleToVectorProcessor<ASTNodeExpressionListProcessor>)
         +-(language::name:a:NameProcessor)
         +-(language::name:b:NameProcessor)
         `-(language::name:a:NameProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("tuple(builtin) -> tuple(builtin)")
    {
      std::string_view data = R"(
let t:(builtin_t), t = (a,b,a);
tuple_builtinToB(t);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:tuple_builtinToB:FakeProcessor)
     `-(language::name:t:NameProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("Z -> tuple(R^1)")
    {
      std::string_view data = R"(
tuple_R1ToR(1);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:tuple_R1ToR:FakeProcessor)
     `-(language::integer:1:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("R -> tuple(R^1)")
    {
      std::string_view data = R"(
tuple_R1ToR(1.2);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:tuple_R1ToR:FakeProcessor)
     `-(language::real:1.2:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("R^1 -> tuple(R^1)")
    {
      std::string_view data = R"(
let r:R^1, r = 3;
tuple_R1ToR(r);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:tuple_R1ToR:FakeProcessor)
     `-(language::name:r:NameProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("Z -> tuple(R^1x1)")
    {
      std::string_view data = R"(
tuple_R11ToR(1);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:tuple_R11ToR:FakeProcessor)
     `-(language::integer:1:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("R -> tuple(R^1x1)")
    {
      std::string_view data = R"(
tuple_R11ToR(1.2);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:tuple_R11ToR:FakeProcessor)
     `-(language::real:1.2:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("R^1x1 -> tuple(R^1x1)")
    {
      std::string_view data = R"(
let r:R^1x1, r = 3;
tuple_R11ToR(r);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:tuple_R11ToR:FakeProcessor)
     `-(language::name:r:NameProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("0 -> tuple(R^2)")
    {
      std::string_view data = R"(
tuple_R2ToR(0);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:tuple_R2ToR:FakeProcessor)
     `-(language::integer:0:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("R^2 -> tuple(R^2)")
    {
      std::string_view data = R"(
let r:R^2, r = (1,2);
tuple_R2ToR(r);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:tuple_R2ToR:FakeProcessor)
     `-(language::name:r:NameProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("compound_list -> tuple(R^2)")
    {
      std::string_view data = R"(
let r:R^2, r = (1,2);
tuple_R2ToR((r,r));
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:tuple_R2ToR:FakeProcessor)
     `-(language::tuple_expression:TupleToVectorProcessor<ASTNodeExpressionListProcessor>)
         +-(language::name:r:NameProcessor)
         `-(language::name:r:NameProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("0 -> tuple(R^2x2)")
    {
      std::string_view data = R"(
tuple_R22ToR(0);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:tuple_R22ToR:FakeProcessor)
     `-(language::integer:0:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("R^2x2 -> tuple(R^2x2)")
    {
      std::string_view data = R"(
let r:R^2x2, r = (1,2,3,4);
tuple_R22ToR(r);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:tuple_R22ToR:FakeProcessor)
     `-(language::name:r:NameProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("compound_list -> tuple(R^2x2)")
    {
      std::string_view data = R"(
let r:R^2x2, r = (1,2,3,4);
tuple_R22ToR((r,r));
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:tuple_R22ToR:FakeProcessor)
     `-(language::tuple_expression:TupleToVectorProcessor<ASTNodeExpressionListProcessor>)
         +-(language::name:r:NameProcessor)
         `-(language::name:r:NameProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("0 -> tuple(R^3)")
    {
      std::string_view data = R"(
tuple_R3ToR(0);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:tuple_R3ToR:FakeProcessor)
     `-(language::integer:0:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("R^3 -> tuple(R^3)")
    {
      std::string_view data = R"(
let r:R^3, r = (1,2,3);
tuple_R3ToR(r);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:tuple_R3ToR:FakeProcessor)
     `-(language::name:r:NameProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("0 -> tuple(R^3x3)")
    {
      std::string_view data = R"(
tuple_R33ToR(0);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:tuple_R33ToR:FakeProcessor)
     `-(language::integer:0:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("R^3x3 -> tuple(R^3x3)")
    {
      std::string_view data = R"(
let r:R^3x3, r = (1,2,3,4,5,6,7,8,9);
tuple_R33ToR(r);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:tuple_R33ToR:FakeProcessor)
     `-(language::name:r:NameProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("FunctionSymbolId -> R")
    {
      std::string_view data = R"(
let f : R^3 -> R, x -> 0;
fidToR(f);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:fidToR:FakeProcessor)
     `-(language::name:f:NameProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("tuple(FunctionSymbolId) -> R")
    {
      std::string_view data = R"(
let f : R^3 -> R, x -> 0;
fidTupleToR((f,f));
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:fidTupleToR:FakeProcessor)
     `-(language::tuple_expression:TupleToVectorProcessor<ASTNodeExpressionListProcessor>)
         +-(language::name:f:NameProcessor)
         `-(language::name:f:NameProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("tuple(FunctionSymbolId) -> R  [with single value tuple]")
    {
      std::string_view data = R"(
let f : R^3 -> R, x -> 0;
fidTupleToR(f);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:BuiltinFunctionProcessor)
     +-(language::name:fidTupleToR:FakeProcessor)
     `-(language::name:f:NameProcessor)
)";

      CHECK_AST(data, result);
    }
  }

  SECTION("errors")
  {
    SECTION("bad number of arguments")
    {
      std::string_view data = R"(
BtoR(true, false);
)";

      const std::string error_msg = "no matching function to call BtoR: B*B\n"
                                    "note: candidates are\n"
                                    " BtoR: B -> R";

      CHECK_AST_THROWS_AT_BUILD_WITH(data, error_msg);
    }

    SECTION("bad number of arguments 2")
    {
      std::string_view data = R"(
RRtoB(3);
)";

      const std::string error_msg = "no matching function to call RRtoB: Z\n"
                                    "note: candidates are\n"
                                    " RRtoB: R*R -> B";

      CHECK_AST_THROWS_AT_BUILD_WITH(data, error_msg);
    }

    SECTION("invalid argument type")
    {
      std::string_view data = R"(
RtoR("foo");
)";

      const std::string error_msg = "no matching function to call RtoR: string\n"
                                    "note: candidates are\n"
                                    " RtoR: R -> R";
      CHECK_AST_THROWS_AT_BUILD_WITH(data, error_msg);
    }
  }
}