#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 <utils/Demangle.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>) 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);                                                            \
                                                                                                    \
    ASTModulesImporter{*ast};                                                                       \
    ASTNodeTypeCleaner<language::import_instruction>{*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(data)                                                     \
  {                                                                                \
    static_assert(std::is_same_v<std::decay_t<decltype(data)>, 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};                                                  \
                                                                                   \
    ASTNodeTypeCleaner<language::var_declaration>{*ast};                           \
    ASTNodeTypeCleaner<language::fct_declaration>{*ast};                           \
    REQUIRE_THROWS_AS(ASTNodeExpressionBuilder{*ast}, ParseError);                 \
  }

#define CHECK_TYPE_BUILDER_THROWS_WITH(data, 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(error)>, std::string>);     \
                                                                                   \
    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}, error);                      \
  }

#define CHECK_EXPRESSION_BUILDER_THROWS_WITH(data, 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(error)>, std::string>);     \
                                                                                   \
    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};                                                  \
                                                                                   \
    ASTNodeTypeCleaner<language::var_declaration>{*ast};                           \
    ASTNodeTypeCleaner<language::fct_declaration>{*ast};                           \
    REQUIRE_THROWS_WITH(ASTNodeExpressionBuilder{*ast}, error);                    \
  }

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

TEST_CASE("ASTNodeFunctionExpressionBuilder", "[language]")
{
  SECTION("return a B")
  {
    SECTION("B argument")
    {
      SECTION("B parameter")
      {
        std::string_view data = R"(
let not_v : B -> B, a -> not a;
not_v(true);
)";

        std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:FunctionProcessor)
     +-(language::name:not_v:NameProcessor)
     `-(language::true_kw:ValueProcessor)
)";

        CHECK_AST(data, result);
      }
    }

    SECTION("N argument")
    {
      std::string_view data = R"(
let test : N -> B, n -> n<10;
test(2);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:FunctionProcessor)
     +-(language::name:test:NameProcessor)
     `-(language::integer:2:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("Z argument")
    {
      std::string_view data = R"(
let test : Z -> B, z -> z>3;
test(2);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:FunctionProcessor)
     +-(language::name:test:NameProcessor)
     `-(language::integer:2:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("R argument")
    {
      std::string_view data = R"(
let test : R -> B, x -> x>2.3;
test(2.1);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:FunctionProcessor)
     +-(language::name:test:NameProcessor)
     `-(language::real:2.1:ValueProcessor)
)";

      CHECK_AST(data, result);
    }
  }

  SECTION("return a N")
  {
    SECTION("N argument")
    {
      std::string_view data = R"(
let test : N -> N, n -> n+2;
test(2);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:FunctionProcessor)
     +-(language::name:test:NameProcessor)
     `-(language::integer:2:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("Z argument")
    {
      std::string_view data = R"(
let absolute : Z -> N, z -> (z>0)*z -(z<=0)*z;
absolute(-2);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:FunctionProcessor)
     +-(language::name:absolute:NameProcessor)
     `-(language::unary_minus:UnaryExpressionProcessor<language::unary_minus, long, long>)
         `-(language::integer:2:ValueProcessor)
)";

      CHECK_AST(data, result);
    }
  }

  SECTION("return a Z")
  {
    SECTION("N argument")
    {
      std::string_view data = R"(
let minus : N -> Z, n -> -n;
minus(true);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:FunctionProcessor)
     +-(language::name:minus:NameProcessor)
     `-(language::true_kw:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("Z argument")
    {
      std::string_view data = R"(
let times_2 : Z -> Z, z -> z*2;
times_2(-2);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:FunctionProcessor)
     +-(language::name:times_2:NameProcessor)
     `-(language::unary_minus:UnaryExpressionProcessor<language::unary_minus, long, long>)
         `-(language::integer:2:ValueProcessor)
)";

      CHECK_AST(data, result);
    }
  }

  SECTION("return a string")
  {
    SECTION("string argument")
    {
      std::string_view data = R"(
let cat : string*string -> string, (s1,s2) -> s1+s2;
cat("foo", "bar");
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:FunctionProcessor)
     +-(language::name:cat:NameProcessor)
     `-(language::function_argument_list:ASTNodeExpressionListProcessor)
         +-(language::literal:"foo":ValueProcessor)
         `-(language::literal:"bar":ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("B argument conversion")
    {
      std::string_view data = R"(
let cat : string*string -> string, (s1,s2) -> s1+s2;
cat("foo", true);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:FunctionProcessor)
     +-(language::name:cat:NameProcessor)
     `-(language::function_argument_list:ASTNodeExpressionListProcessor)
         +-(language::literal:"foo":ValueProcessor)
         `-(language::true_kw:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("N argument conversion")
    {
      std::string_view data = R"(
let cat : string*string -> string, (s1,s2) -> s1+s2;
let n : N, n = 2;
cat("foo", n);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:FunctionProcessor)
     +-(language::name:cat:NameProcessor)
     `-(language::function_argument_list:ASTNodeExpressionListProcessor)
         +-(language::literal:"foo":ValueProcessor)
         `-(language::name:n:NameProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("Z argument conversion")
    {
      std::string_view data = R"(
let cat : string*string -> string, (s1,s2) -> s1+s2;
cat("foo", -1);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:FunctionProcessor)
     +-(language::name:cat:NameProcessor)
     `-(language::function_argument_list:ASTNodeExpressionListProcessor)
         +-(language::literal:"foo":ValueProcessor)
         `-(language::unary_minus:UnaryExpressionProcessor<language::unary_minus, long, long>)
             `-(language::integer:1:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("R argument conversion")
    {
      std::string_view data = R"(
let cat : string*string -> string, (s1,s2) -> s1+s2;
cat("foo", 2.5e-3);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:FunctionProcessor)
     +-(language::name:cat:NameProcessor)
     `-(language::function_argument_list:ASTNodeExpressionListProcessor)
         +-(language::literal:"foo":ValueProcessor)
         `-(language::real:2.5e-3:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

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

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

      CHECK_AST(data, result);
    }

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

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

      CHECK_AST(data, result);
    }

    SECTION("Return R^3 -> R^3")
    {
      std::string_view data = R"(
let f : R^3 -> R^3, x -> x+x;
let x : R^3, x = (1,2,3);
f(x);
)";

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

      CHECK_AST(data, result);
    }

    SECTION("Return R^1x1 -> R^1x1")
    {
      std::string_view data = R"(
let f : R^1x1 -> R^1x1, x -> x+x;
let x : R^1x1, x = 1;
f(x);
)";

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

      CHECK_AST(data, result);
    }

    SECTION("Return R^2x2 -> R^2x2")
    {
      std::string_view data = R"(
let f : R^2x2 -> R^2x2, x -> x+x;
let x : R^2x2, x = (1,2,3,4);
f(x);
)";

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

      CHECK_AST(data, result);
    }

    SECTION("Return R^3x3 -> R^3x3")
    {
      std::string_view data = R"(
let f : R^3x3 -> R^3x3, x -> x+x;
let x : R^3x3, x = (1,2,3,4,5,6,7,8,9);
f(x);
)";

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

      CHECK_AST(data, result);
    }

    SECTION("Return scalar -> R^1")
    {
      std::string_view data = R"(
let f : R -> R^1, x -> x+1;
f(1);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:FunctionProcessor)
     +-(language::name:f:NameProcessor)
     `-(language::integer:1:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("Return tuple -> R^2")
    {
      std::string_view data = R"(
let f : R*R -> R^2, (x,y) -> (x,y);
f(1,2);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:TupleToTinyVectorProcessor<FunctionProcessor, 2ul>)
     +-(language::name:f:NameProcessor)
     `-(language::function_argument_list:ASTNodeExpressionListProcessor)
         +-(language::integer:1:ValueProcessor)
         `-(language::integer:2:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("Return tuple -> R^3")
    {
      std::string_view data = R"(
let f : R*R*R -> R^3, (x,y,z) -> (x,y,z);
f(1,2,3);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:TupleToTinyVectorProcessor<FunctionProcessor, 3ul>)
     +-(language::name:f:NameProcessor)
     `-(language::function_argument_list:ASTNodeExpressionListProcessor)
         +-(language::integer:1:ValueProcessor)
         +-(language::integer:2:ValueProcessor)
         `-(language::integer:3:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("Return scalar -> R^1x1")
    {
      std::string_view data = R"(
let f : R -> R^1x1, x -> x+1;
f(1);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:FunctionProcessor)
     +-(language::name:f:NameProcessor)
     `-(language::integer:1:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("Return tuple -> R^2x2")
    {
      std::string_view data = R"(
let f : R*R*R*R -> R^2x2, (x,y,z,t) -> (x,y,z,t);
f(1,2,3,4);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:TupleToTinyMatrixProcessor<FunctionProcessor, 2ul>)
     +-(language::name:f:NameProcessor)
     `-(language::function_argument_list:ASTNodeExpressionListProcessor)
         +-(language::integer:1:ValueProcessor)
         +-(language::integer:2:ValueProcessor)
         +-(language::integer:3:ValueProcessor)
         `-(language::integer:4:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("Return tuple -> R^3x3")
    {
      std::string_view data = R"(
let f : R^3*R^3*R^3 -> R^3x3, (x,y,z) -> (x[0],x[1],x[2],y[0],y[1],y[2],z[0],z[1],z[2]);
f((1,2,3),(4,5,6),(7,8,9));
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:TupleToTinyMatrixProcessor<FunctionProcessor, 3ul>)
     +-(language::name:f:NameProcessor)
     `-(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:4:ValueProcessor)
         |   +-(language::integer:5:ValueProcessor)
         |   `-(language::integer:6:ValueProcessor)
         `-(language::tuple_expression:TupleToVectorProcessor<ASTNodeExpressionListProcessor>)
             +-(language::integer:7:ValueProcessor)
             +-(language::integer:8:ValueProcessor)
             `-(language::integer:9:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("Return '0' -> R^1")
    {
      std::string_view data = R"(
let f : R -> R^1, x -> 0;
f(1);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:FunctionExpressionProcessor<TinyVector<1ul, double>, ZeroType>)
     +-(language::name:f:NameProcessor)
     `-(language::integer:1:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("Return '0' -> R^2")
    {
      std::string_view data = R"(
let f : R -> R^2, x -> 0;
f(1);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:FunctionExpressionProcessor<TinyVector<2ul, double>, ZeroType>)
     +-(language::name:f:NameProcessor)
     `-(language::integer:1:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

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

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:FunctionExpressionProcessor<TinyVector<3ul, double>, ZeroType>)
     +-(language::name:f:NameProcessor)
     `-(language::integer:1:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("Return '0' -> R^1x1")
    {
      std::string_view data = R"(
let f : R -> R^1x1, x -> 0;
f(1);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:FunctionExpressionProcessor<TinyMatrix<1ul, double>, ZeroType>)
     +-(language::name:f:NameProcessor)
     `-(language::integer:1:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("Return '0' -> R^2x2")
    {
      std::string_view data = R"(
let f : R -> R^2x2, x -> 0;
f(1);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:FunctionExpressionProcessor<TinyMatrix<2ul, double>, ZeroType>)
     +-(language::name:f:NameProcessor)
     `-(language::integer:1:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("Return '0' -> R^3x3")
    {
      std::string_view data = R"(
let f : R -> R^3x3, x -> 0;
f(1);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:FunctionExpressionProcessor<TinyMatrix<3ul, double>, ZeroType>)
     +-(language::name:f:NameProcessor)
     `-(language::integer:1:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("Return embedded R^d compound")
    {
      std::string_view data = R"(
let f : R*R*R*R -> R*R^1*R^2*R^3, (x,y,z,t) -> (t, (x), (x,y), (x,y,z));
f(1,2,3,4);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:FunctionProcessor)
     +-(language::name:f:NameProcessor)
     `-(language::function_argument_list:ASTNodeExpressionListProcessor)
         +-(language::integer:1:ValueProcessor)
         +-(language::integer:2:ValueProcessor)
         +-(language::integer:3:ValueProcessor)
         `-(language::integer:4:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("Return embedded R^dxd compound")
    {
      std::string_view data = R"(
let f : R*R*R*R -> R*R^1x1*R^2x2*R^3x3, (x,y,z,t) -> (t, (x), (x,y,z,t), (x,y,z, x,x,x, t,t,t));
f(1,2,3,4);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:FunctionProcessor)
     +-(language::name:f:NameProcessor)
     `-(language::function_argument_list:ASTNodeExpressionListProcessor)
         +-(language::integer:1:ValueProcessor)
         +-(language::integer:2:ValueProcessor)
         +-(language::integer:3:ValueProcessor)
         `-(language::integer:4:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("Return embedded R^d compound with '0'")
    {
      std::string_view data = R"(
let f : R*R*R*R -> R*R^1*R^2*R^3, (x,y,z,t) -> (t, 0, 0, (x,y,z));
f(1,2,3,4);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:FunctionProcessor)
     +-(language::name:f:NameProcessor)
     `-(language::function_argument_list:ASTNodeExpressionListProcessor)
         +-(language::integer:1:ValueProcessor)
         +-(language::integer:2:ValueProcessor)
         +-(language::integer:3:ValueProcessor)
         `-(language::integer:4:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("Return embedded R^dxd compound with '0'")
    {
      std::string_view data = R"(
let f : R*R*R*R -> R*R^1x1*R^2x2*R^3x3, (x,y,z,t) -> (t, 0, 0, (x, y, z, t, x, y, z, t, x));
f(1,2,3,4);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:FunctionProcessor)
     +-(language::name:f:NameProcessor)
     `-(language::function_argument_list:ASTNodeExpressionListProcessor)
         +-(language::integer:1:ValueProcessor)
         +-(language::integer:2:ValueProcessor)
         +-(language::integer:3:ValueProcessor)
         `-(language::integer:4:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("Arguments '0' -> R^1")
    {
      std::string_view data = R"(
let f : R^1 -> R^1, x -> x;
f(0);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:FunctionProcessor)
     +-(language::name:f:NameProcessor)
     `-(language::integer:0:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("Arguments '0' -> R^2")
    {
      std::string_view data = R"(
let f : R^2 -> R^2, x -> x;
f(0);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:FunctionProcessor)
     +-(language::name:f:NameProcessor)
     `-(language::integer:0:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

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

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:FunctionProcessor)
     +-(language::name:f:NameProcessor)
     `-(language::integer:0:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("Arguments '0' -> R^1x1")
    {
      std::string_view data = R"(
let f : R^1x1 -> R^1x1, x -> x;
f(0);
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:FunctionProcessor)
     +-(language::name:f:NameProcessor)
     `-(language::integer:0:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

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

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:FunctionProcessor)
     +-(language::name:f:NameProcessor)
     `-(language::integer:0:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

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

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:FunctionProcessor)
     +-(language::name:f:NameProcessor)
     `-(language::integer:0:ValueProcessor)
)";

      CHECK_AST(data, result);
    }

    SECTION("Arguments tuple -> R^d")
    {
      std::string_view data = R"(
let f: R^3 -> R, x -> x[0]+x[1]+x[2];
f((1,2,3));
)";

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

      CHECK_AST(data, result);
    }

    SECTION("Arguments tuple -> R^dxd")
    {
      std::string_view data = R"(
let f: R^3x3 -> R, x -> x[0,0]+x[0,1]+x[0,2];
f((1,2,3,4,5,6,7,8,9));
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:FunctionProcessor)
     +-(language::name:f:NameProcessor)
     `-(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("Arguments compound with tuple")
    {
      std::string_view data = R"(
let f: R*R^3*R^2x2->R, (t,x,y) -> t*(x[0]+x[1]+x[2])*y[0,0]+y[1,1];
f(2,(1,2,3),(2,3,-1,1.3));
)";

      std::string_view result = R"(
(root:ASTNodeListProcessor)
 `-(language::function_evaluation:FunctionProcessor)
     +-(language::name:f:NameProcessor)
     `-(language::function_argument_list:ASTNodeExpressionListProcessor)
         +-(language::integer:2:ValueProcessor)
         +-(language::tuple_expression:TupleToVectorProcessor<ASTNodeExpressionListProcessor>)
         |   +-(language::integer:1:ValueProcessor)
         |   +-(language::integer:2:ValueProcessor)
         |   `-(language::integer:3:ValueProcessor)
         `-(language::tuple_expression:TupleToVectorProcessor<ASTNodeExpressionListProcessor>)
             +-(language::integer:2:ValueProcessor)
             +-(language::integer:3:ValueProcessor)
             +-(language::unary_minus:UnaryExpressionProcessor<language::unary_minus, long, long>)
             |   `-(language::integer:1:ValueProcessor)
             `-(language::real:1.3:ValueProcessor)
)";

      CHECK_AST(data, result);
    }
  }

  SECTION("errors")
  {
    SECTION("wrong argument number")
    {
      std::string_view data = R"(
let Id : Z -> Z, z -> z;
Id(2,3);
)";

      CHECK_AST_THROWS(data);
    }

    SECTION("wrong argument number 2")
    {
      std::string_view data = R"(
let sum : R*R -> R, (x,y) -> x+y;
sum(2);
)";

      CHECK_AST_THROWS(data);
    }

    SECTION("invalid return implicit conversion")
    {
      SECTION("string -> R")
      {
        std::string_view data = R"(
let bad_conv : string -> R, s -> s;
)";

        CHECK_TYPE_BUILDER_THROWS_WITH(data, std::string{"invalid implicit conversion: string -> R"});
      }

      SECTION("R -> B")
      {
        std::string_view data = R"(
let bad_B : R -> B, x -> x;
)";

        CHECK_TYPE_BUILDER_THROWS_WITH(data, std::string{"invalid implicit conversion: R -> B"});
      }

      SECTION("R -> N")
      {
        std::string_view data = R"(
let next : R -> N, x -> x;
)";

        CHECK_TYPE_BUILDER_THROWS_WITH(data, std::string{"invalid implicit conversion: R -> N"});
      }

      SECTION("R -> Z")
      {
        std::string_view data = R"(
let prev : R -> Z, x -> x;
)";

        CHECK_TYPE_BUILDER_THROWS_WITH(data, std::string{"invalid implicit conversion: R -> Z"});
      }

      SECTION("N -> B")
      {
        std::string_view data = R"(
let bad_B : N -> B, n -> n;
)";

        CHECK_TYPE_BUILDER_THROWS_WITH(data, std::string{"invalid implicit conversion: N -> B"});
      }

      SECTION("Z -> B")
      {
        std::string_view data = R"(
let bad_B : Z -> B, n -> n;
)";

        CHECK_TYPE_BUILDER_THROWS_WITH(data, std::string{"invalid implicit conversion: Z -> B"});
      }
    }

    SECTION("invalid argument implicit conversion")
    {
      SECTION("N -> B")
      {
        std::string_view data = R"(
let negate : B -> B, b -> not b;
let n : N, n = 2;
negate(n);
)";

        CHECK_EXPRESSION_BUILDER_THROWS_WITH(data, std::string{"invalid implicit conversion: N -> B"});
      }

      SECTION("Z -> B")
      {
        std::string_view data = R"(
let negate : B -> B, b -> not b;
negate(3-4);
)";

        CHECK_EXPRESSION_BUILDER_THROWS_WITH(data, std::string{"invalid implicit conversion: Z -> B"});
      }

      SECTION("R -> B")
      {
        std::string_view data = R"(
let negate : B -> B, b -> not b;
negate(3.24);
)";

        CHECK_EXPRESSION_BUILDER_THROWS_WITH(data, std::string{"invalid implicit conversion: R -> B"});
      }

      SECTION("R -> N")
      {
        std::string_view data = R"(
let next : N -> N, n -> n+1;
next(3.24);
)";

        CHECK_EXPRESSION_BUILDER_THROWS_WITH(data, std::string{"invalid implicit conversion: R -> N"});
      }

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

        CHECK_EXPRESSION_BUILDER_THROWS_WITH(data, std::string{"invalid implicit conversion: R -> Z"});
      }
    }

    SECTION("arguments invalid tuple -> R^d conversion")
    {
      SECTION("tuple[3] -> R^2")
      {
        std::string_view data = R"(
let f : R^2 -> R, x->x[0];
f((1,2,3));
)";

        CHECK_EXPRESSION_BUILDER_THROWS_WITH(data,
                                             std::string{
                                               "incompatible dimensions in affectation: expecting 2, but provided 3"});
      }

      SECTION("tuple[2] -> R^3")
      {
        std::string_view data = R"(
let f : R^3 -> R, x->x[0];
f((1,2));
)";

        CHECK_EXPRESSION_BUILDER_THROWS_WITH(data,
                                             std::string{
                                               "incompatible dimensions in affectation: expecting 3, but provided 2"});
      }

      SECTION("compound tuple[3] -> R^2")
      {
        std::string_view data = R"(
let f : R*R^2 -> R, (t,x)->x[0];
f(1,(1,2,3));
)";

        CHECK_EXPRESSION_BUILDER_THROWS_WITH(data,
                                             std::string{
                                               "incompatible dimensions in affectation: expecting 2, but provided 3"});
      }

      SECTION("compound tuple[2] -> R^3")
      {
        std::string_view data = R"(
let f : R^3*R^2 -> R, (x,y)->x[0]*y[1];
f((1,2),(3,4));
)";

        CHECK_EXPRESSION_BUILDER_THROWS_WITH(data,
                                             std::string{
                                               "incompatible dimensions in affectation: expecting 3, but provided 2"});
      }

      SECTION("list instead of tuple -> R^3")
      {
        std::string_view data = R"(
let f : R^3 -> R, x -> x[0]*x[1];
f(1,2,3);
)";

        CHECK_EXPRESSION_BUILDER_THROWS_WITH(data, std::string{"bad number of arguments: expecting 1, provided 3"});
      }

      SECTION("list instead of tuple -> R^3*R^2")
      {
        std::string_view data = R"(
let f : R^3*R^2 -> R, (x,y) -> x[0]*x[1]-y[0];
f((1,2,3),2,3);
)";

        CHECK_EXPRESSION_BUILDER_THROWS_WITH(data, std::string{"bad number of arguments: expecting 2, provided 3"});
      }
    }
  }
}