#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/ASTNode.hpp>
#include <language/ast/ASTNodeDataTypeBuilder.hpp>
#include <language/ast/ASTNodeDeclarationToAffectationConverter.hpp>
#include <language/ast/ASTNodeTypeCleaner.hpp>
#include <language/ast/ASTSymbolInitializationChecker.hpp>
#include <language/ast/ASTSymbolTableBuilder.hpp>
#include <language/utils/BuiltinFunctionEmbedder.hpp>
#include <language/utils/BuiltinFunctionEmbedderUtils.hpp>
#include <language/utils/SymbolTable.hpp>
#include <utils/Exceptions.hpp>

#include <pegtl/string_input.hpp>

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

TEST_CASE("BuiltinFunctionEmbedderUtils", "[language]")
{
  using FunctionList      = std::vector<std::pair<std::string, std::shared_ptr<IBuiltinFunctionEmbedder>>>;
  auto register_functions = [](std::unique_ptr<ASTNode>& root_node, const FunctionList& function_list) {
    ASTModulesImporter{*root_node};

    std::shared_ptr root_st = root_node->m_symbol_table;

    auto& embedder_table = root_st->builtinFunctionEmbedderTable();
    for (auto&& [function_name, embedded_function] : function_list) {
      auto [i_symbol, success] =
        root_st->add(function_name + ':' + dataTypeName(embedded_function->getParameterDataTypes()),
                     root_node->begin());

      if (not success) {
        throw UnexpectedError("cannot build test, function already registered");
      }

      i_symbol->attributes().setDataType(ASTNodeDataType::build<ASTNodeDataType::builtin_function_t>());
      i_symbol->attributes().setIsInitialized();
      i_symbol->attributes().value() = embedder_table.size();

      embedder_table.add(embedded_function);
    }

    ASTSymbolTableBuilder{*root_node};
    ASTNodeDataTypeBuilder{*root_node};

    ASTNodeDeclarationToAffectationConverter{*root_node};
    ASTNodeTypeCleaner<language::var_declaration>{*root_node};
    ASTNodeTypeCleaner<language::fct_declaration>{*root_node};
  };

  SECTION("using simple arguments")
  {
    SECTION("builtin function R -> R")
    {
      std::string_view data = R"(
foo(3);
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto root_node = ASTBuilder::build(input);
      register_functions(root_node,
                         FunctionList{std::make_pair("foo", std::make_shared<BuiltinFunctionEmbedder<double(double)>>(
                                                              [](double x) -> double { return x; }))});

      auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
      REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::double_t);
      REQUIRE(function_embedder->getParameterDataTypes().size() == 1);
      REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
    }

    SECTION("builtin function R*string -> R")
    {
      std::string_view data = R"(
foo(3,"bar");
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto root_node = ASTBuilder::build(input);
      register_functions(root_node,
                         FunctionList{std::make_pair("foo", std::make_shared<BuiltinFunctionEmbedder<double(double)>>(
                                                              [](double x) -> double { return x; })),
                                      std::make_pair("foo",
                                                     std::make_shared<
                                                       BuiltinFunctionEmbedder<double(double, const std::string&)>>(
                                                       [](double x, const std::string&) -> double { return x; }))});

      auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
      REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::double_t);
      REQUIRE(function_embedder->getParameterDataTypes().size() == 2);
      REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
      REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::string_t);
    }

    SECTION("builtin function R*string -> R (using builtin function result)")
    {
      std::string_view data = R"(
foo(foo(3),"bar");
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto root_node = ASTBuilder::build(input);
      register_functions(root_node,
                         FunctionList{std::make_pair("foo", std::make_shared<BuiltinFunctionEmbedder<double(double)>>(
                                                              [](double x) -> double { return x; })),
                                      std::make_pair("foo",
                                                     std::make_shared<
                                                       BuiltinFunctionEmbedder<double(double, const std::string&)>>(
                                                       [](double x, const std::string&) -> double { return x; }))});

      auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
      REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::double_t);
      REQUIRE(function_embedder->getParameterDataTypes().size() == 2);
      REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
      REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::string_t);
    }

    SECTION("builtin function N -> N (choosing appropriate candidate)")
    {
      std::string_view data = R"(
foo(2);
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto root_node = ASTBuilder::build(input);
      register_functions(root_node,
                         FunctionList{std::make_pair("foo", std::make_shared<BuiltinFunctionEmbedder<double(double)>>(
                                                              [](double x) -> double { return x; })),
                                      std::make_pair("foo", std::make_shared<BuiltinFunctionEmbedder<int64_t(int64_t)>>(
                                                              [](int64_t n) -> int64_t { return n; })),
                                      std::make_pair("foo",
                                                     std::make_shared<
                                                       BuiltinFunctionEmbedder<double(double, const std::string&)>>(
                                                       [](double x, const std::string&) -> double { return x; }))});

      auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
      REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::int_t);
      REQUIRE(function_embedder->getParameterDataTypes().size() == 1);
      REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::int_t);
    }

    SECTION("builtin function R -> R (using function result)")
    {
      std::string_view data = R"(
let f: R -> R, x -> x+1;
foo(f(3));
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto root_node = ASTBuilder::build(input);
      register_functions(root_node,
                         FunctionList{std::make_pair("foo", std::make_shared<BuiltinFunctionEmbedder<double(double)>>(
                                                              [](double x) -> double { return x; }))});

      auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
      REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::double_t);
      REQUIRE(function_embedder->getParameterDataTypes().size() == 1);
      REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
    }

    SECTION("builtin function R*string -> R (using  function result)")
    {
      std::string_view data = R"(
let f : R -> R*string, x -> (x,"bar"+x);
foo(f(3));
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto root_node = ASTBuilder::build(input);
      register_functions(root_node,
                         FunctionList{std::make_pair("foo", std::make_shared<BuiltinFunctionEmbedder<double(double)>>(
                                                              [](double x) -> double { return x; })),
                                      std::make_pair("foo",
                                                     std::make_shared<
                                                       BuiltinFunctionEmbedder<double(double, const std::string&)>>(
                                                       [](double x, const std::string&) -> double { return x; }))});

      auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
      REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::double_t);
      REQUIRE(function_embedder->getParameterDataTypes().size() == 2);
      REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
      REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::string_t);
    }
  }

  SECTION("using R^1 arguments")
  {
    SECTION("builtin function R*R^1 -> R^1")
    {
      std::string_view data = R"(
let x:R^1;
foo(3,x);
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto root_node = ASTBuilder::build(input);
      register_functions(root_node,
                         FunctionList{
                           std::make_pair("foo",
                                          std::make_shared<
                                            BuiltinFunctionEmbedder<TinyVector<1>(double, const TinyVector<1>&)>>(
                                            [](double a, const TinyVector<1>& x) -> TinyVector<1> { return a * x; }))});

      auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
      REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::vector_t);
      REQUIRE(function_embedder->getReturnDataType().dimension() == 1);
      REQUIRE(function_embedder->getParameterDataTypes().size() == 2);
      REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
      REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::vector_t);
      REQUIRE(function_embedder->getParameterDataTypes()[1].dimension() == 1);
    }

    SECTION("builtin function R*R^1 -> R^2")
    {
      std::string_view data = R"(
foo(3,1);
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto root_node = ASTBuilder::build(input);
      register_functions(root_node,
                         FunctionList{
                           std::make_pair("foo",
                                          std::make_shared<
                                            BuiltinFunctionEmbedder<TinyVector<2>(double, const TinyVector<1>&)>>(
                                            [](double a, const TinyVector<1>& x) -> TinyVector<2> {
                                              return {a, x[0]};
                                            }))});

      auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
      REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::vector_t);
      REQUIRE(function_embedder->getReturnDataType().dimension() == 2);
      REQUIRE(function_embedder->getParameterDataTypes().size() == 2);
      REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
      REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::vector_t);
      REQUIRE(function_embedder->getParameterDataTypes()[1].dimension() == 1);
    }

    SECTION("builtin function R*R^1 -> R^2")
    {
      std::string_view data = R"(
let f :  R -> R^1, x -> x;
foo(3.1,f(1));
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto root_node = ASTBuilder::build(input);
      register_functions(root_node,
                         FunctionList{
                           std::make_pair("foo",
                                          std::make_shared<
                                            BuiltinFunctionEmbedder<TinyVector<2>(double, const TinyVector<1>&)>>(
                                            [](double a, const TinyVector<1>& x) -> TinyVector<2> {
                                              return {a, x[0]};
                                            }))});

      auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
      REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::vector_t);
      REQUIRE(function_embedder->getReturnDataType().dimension() == 2);
      REQUIRE(function_embedder->getParameterDataTypes().size() == 2);
      REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
      REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::vector_t);
      REQUIRE(function_embedder->getParameterDataTypes()[1].dimension() == 1);
    }

    SECTION("builtin function R^1 -> R^2 (from f: R -> R*R^1)")
    {
      std::string_view data = R"(
let f :  R -> R*R^1, x -> (2+x, 2*x);
foo(f(2));
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto root_node = ASTBuilder::build(input);
      register_functions(root_node,
                         FunctionList{
                           std::make_pair("foo",
                                          std::make_shared<
                                            BuiltinFunctionEmbedder<TinyVector<2>(double, const TinyVector<1>&)>>(
                                            [](double a, const TinyVector<1>& x) -> TinyVector<2> {
                                              return {a, x[0]};
                                            }))});

      auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
      REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::vector_t);
      REQUIRE(function_embedder->getReturnDataType().dimension() == 2);
      REQUIRE(function_embedder->getParameterDataTypes().size() == 2);
      REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
      REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::vector_t);
      REQUIRE(function_embedder->getParameterDataTypes()[1].dimension() == 1);
    }
  }

  SECTION("using R^2 arguments")
  {
    SECTION("builtin function R*R^2 -> R^2")
    {
      std::string_view data = R"(
let x:R^2;
foo(3,x);
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto root_node = ASTBuilder::build(input);
      register_functions(root_node,
                         FunctionList{
                           std::make_pair("foo",
                                          std::make_shared<
                                            BuiltinFunctionEmbedder<TinyVector<2>(double, const TinyVector<2>&)>>(
                                            [](double a, const TinyVector<2>& x) -> TinyVector<2> { return a * x; }))});

      auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
      REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::vector_t);
      REQUIRE(function_embedder->getReturnDataType().dimension() == 2);
      REQUIRE(function_embedder->getParameterDataTypes().size() == 2);
      REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
      REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::vector_t);
      REQUIRE(function_embedder->getParameterDataTypes()[1].dimension() == 2);
    }

    SECTION("builtin function R*R^2 -> R^3")
    {
      std::string_view data = R"(
foo(3,0);
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto root_node = ASTBuilder::build(input);
      register_functions(root_node,
                         FunctionList{
                           std::make_pair("foo",
                                          std::make_shared<
                                            BuiltinFunctionEmbedder<TinyVector<3>(double, const TinyVector<2>&)>>(
                                            [](double a, const TinyVector<2>& x) -> TinyVector<3> {
                                              return {a, x[0], x[1]};
                                            }))});

      auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
      REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::vector_t);
      REQUIRE(function_embedder->getReturnDataType().dimension() == 3);
      REQUIRE(function_embedder->getParameterDataTypes().size() == 2);
      REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
      REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::vector_t);
      REQUIRE(function_embedder->getParameterDataTypes()[1].dimension() == 2);
    }

    SECTION("builtin function R*R^2 -> R^2 (R^2 from list)")
    {
      std::string_view data = R"(
foo(3.1,(1,2.3));
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto root_node = ASTBuilder::build(input);
      register_functions(root_node,
                         FunctionList{
                           std::make_pair("foo",
                                          std::make_shared<
                                            BuiltinFunctionEmbedder<TinyVector<2>(double, const TinyVector<2>&)>>(
                                            [](double a, const TinyVector<2>& x) -> TinyVector<2> {
                                              return {a * x[1], x[0]};
                                            }))});

      auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
      REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::vector_t);
      REQUIRE(function_embedder->getReturnDataType().dimension() == 2);
      REQUIRE(function_embedder->getParameterDataTypes().size() == 2);
      REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
      REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::vector_t);
      REQUIRE(function_embedder->getParameterDataTypes()[1].dimension() == 2);
    }
  }

  SECTION("using R^3 arguments")
  {
    SECTION("builtin function R*R^3 -> R^2")
    {
      std::string_view data = R"(
let x:R^3;
foo(3,x);
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto root_node = ASTBuilder::build(input);
      register_functions(root_node,
                         FunctionList{
                           std::make_pair("foo",
                                          std::make_shared<
                                            BuiltinFunctionEmbedder<TinyVector<2>(double, const TinyVector<3>&)>>(
                                            [](double a, const TinyVector<3>& x) -> TinyVector<2> {
                                              return {a * x[0], x[1] + x[2]};
                                            }))});

      auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
      REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::vector_t);
      REQUIRE(function_embedder->getReturnDataType().dimension() == 2);
      REQUIRE(function_embedder->getParameterDataTypes().size() == 2);
      REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
      REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::vector_t);
      REQUIRE(function_embedder->getParameterDataTypes()[1].dimension() == 3);
    }

    SECTION("builtin function R*R^3 -> R^3")
    {
      std::string_view data = R"(
foo(3,0);
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto root_node = ASTBuilder::build(input);
      register_functions(root_node,
                         FunctionList{
                           std::make_pair("foo",
                                          std::make_shared<
                                            BuiltinFunctionEmbedder<TinyVector<3>(double, const TinyVector<3>&)>>(
                                            [](double a, const TinyVector<3>& x) -> TinyVector<3> {
                                              return {a * x};
                                            }))});

      auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
      REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::vector_t);
      REQUIRE(function_embedder->getReturnDataType().dimension() == 3);
      REQUIRE(function_embedder->getParameterDataTypes().size() == 2);
      REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
      REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::vector_t);
      REQUIRE(function_embedder->getParameterDataTypes()[1].dimension() == 3);
    }

    SECTION("builtin function R*R^3 -> R^2 (R^3 from list)")
    {
      std::string_view data = R"(
foo(3.1,(1,2.3,4));
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto root_node = ASTBuilder::build(input);
      register_functions(root_node,
                         FunctionList{
                           std::make_pair("foo",
                                          std::make_shared<
                                            BuiltinFunctionEmbedder<TinyVector<2>(double, const TinyVector<3>&)>>(
                                            [](double a, const TinyVector<3>& x) -> TinyVector<2> {
                                              return {a * x[1], x[0] * x[2]};
                                            }))});

      auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
      REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::vector_t);
      REQUIRE(function_embedder->getReturnDataType().dimension() == 2);
      REQUIRE(function_embedder->getParameterDataTypes().size() == 2);
      REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
      REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::vector_t);
      REQUIRE(function_embedder->getParameterDataTypes()[1].dimension() == 3);
    }
  }

  SECTION("using R^1x1 arguments")
  {
    SECTION("builtin function R*R^1x1 -> R^1x1")
    {
      std::string_view data = R"(
let x:R^1x1;
foo(3,x);
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto root_node = ASTBuilder::build(input);
      register_functions(root_node,
                         FunctionList{
                           std::make_pair("foo",
                                          std::make_shared<
                                            BuiltinFunctionEmbedder<TinyMatrix<1>(double, const TinyMatrix<1>&)>>(
                                            [](double a, const TinyMatrix<1>& x) -> TinyMatrix<1> { return a * x; }))});

      auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
      REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::matrix_t);
      REQUIRE(function_embedder->getReturnDataType().numberOfRows() == 1);
      REQUIRE(function_embedder->getParameterDataTypes().size() == 2);
      REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
      REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::matrix_t);
      REQUIRE(function_embedder->getParameterDataTypes()[1].numberOfRows() == 1);
    }

    SECTION("builtin function R*R^1x1 -> R^2x2")
    {
      std::string_view data = R"(
foo(3,1);
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto root_node = ASTBuilder::build(input);
      register_functions(root_node,
                         FunctionList{
                           std::make_pair("foo",
                                          std::make_shared<
                                            BuiltinFunctionEmbedder<TinyMatrix<2>(double, const TinyMatrix<1>&)>>(
                                            [](double a, const TinyMatrix<1>& x) -> TinyMatrix<2> {
                                              return {a, x(0, 0), a * x(0, 0), x(0, 0)};
                                            }))});

      auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
      REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::matrix_t);
      REQUIRE(function_embedder->getReturnDataType().numberOfRows() == 2);
      REQUIRE(function_embedder->getParameterDataTypes().size() == 2);
      REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
      REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::matrix_t);
      REQUIRE(function_embedder->getParameterDataTypes()[1].numberOfRows() == 1);
    }

    SECTION("builtin function R*R^1x1 -> R^2x2")
    {
      std::string_view data = R"(
let f :  R -> R^1x1, x -> x;
foo(3.1,f(1));
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto root_node = ASTBuilder::build(input);
      register_functions(root_node,
                         FunctionList{
                           std::make_pair("foo",
                                          std::make_shared<
                                            BuiltinFunctionEmbedder<TinyMatrix<2>(double, const TinyMatrix<1>&)>>(
                                            [](double a, const TinyMatrix<1>& x) -> TinyMatrix<2> {
                                              return {a, x(0, 0), 2 + x(0, 0), 1};
                                            }))});

      auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
      REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::matrix_t);
      REQUIRE(function_embedder->getReturnDataType().numberOfRows() == 2);
      REQUIRE(function_embedder->getParameterDataTypes().size() == 2);
      REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
      REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::matrix_t);
      REQUIRE(function_embedder->getParameterDataTypes()[1].numberOfRows() == 1);
    }

    SECTION("builtin function R^1x1 -> R^2x2 (from f: R -> R*R^1x1)")
    {
      std::string_view data = R"(
let f :  R -> R*R^1x1, x -> (2+x, 2*x);
foo(f(2));
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto root_node = ASTBuilder::build(input);
      register_functions(root_node,
                         FunctionList{
                           std::make_pair("foo",
                                          std::make_shared<
                                            BuiltinFunctionEmbedder<TinyMatrix<2>(double, const TinyMatrix<1>&)>>(
                                            [](double a, const TinyMatrix<1>& x) -> TinyMatrix<2> {
                                              return {a, x(0, 0), a + x(0, 0), 1};
                                            }))});

      auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
      REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::matrix_t);
      REQUIRE(function_embedder->getReturnDataType().numberOfColumns() == 2);
      REQUIRE(function_embedder->getParameterDataTypes().size() == 2);
      REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
      REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::matrix_t);
      REQUIRE(function_embedder->getParameterDataTypes()[1].numberOfRows() == 1);
    }
  }

  SECTION("using R^2x2 arguments")
  {
    SECTION("builtin function R*R^2x2 -> R^2x2")
    {
      std::string_view data = R"(
let x:R^2x2;
foo(3,x);
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto root_node = ASTBuilder::build(input);
      register_functions(root_node,
                         FunctionList{
                           std::make_pair("foo",
                                          std::make_shared<
                                            BuiltinFunctionEmbedder<TinyMatrix<2>(double, const TinyMatrix<2>&)>>(
                                            [](double a, const TinyMatrix<2>& x) -> TinyMatrix<2> { return a * x; }))});

      auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
      REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::matrix_t);
      REQUIRE(function_embedder->getReturnDataType().numberOfRows() == 2);
      REQUIRE(function_embedder->getParameterDataTypes().size() == 2);
      REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
      REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::matrix_t);
      REQUIRE(function_embedder->getParameterDataTypes()[1].numberOfRows() == 2);
    }

    SECTION("builtin function R*R^2x2 -> R^3x3")
    {
      std::string_view data = R"(
foo(3,0);
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto root_node = ASTBuilder::build(input);
      register_functions(root_node,
                         FunctionList{
                           std::make_pair("foo",
                                          std::make_shared<
                                            BuiltinFunctionEmbedder<TinyMatrix<3>(double, const TinyMatrix<2>&)>>(
                                            [](double a, const TinyMatrix<2>& x) -> TinyMatrix<3> {
                                              return {a, x(0, 0), x(1, 0),   //
                                                      a, x(1, 0), x(1, 1),   //
                                                      a, x(0, 0), x(1, 1)};
                                            }))});

      auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
      REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::matrix_t);
      REQUIRE(function_embedder->getReturnDataType().numberOfRows() == 3);
      REQUIRE(function_embedder->getParameterDataTypes().size() == 2);
      REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
      REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::matrix_t);
      REQUIRE(function_embedder->getParameterDataTypes()[1].numberOfRows() == 2);
    }

    SECTION("builtin function R*R^2x2 -> R^2x2 (R^2x2 from list)")
    {
      std::string_view data = R"(
foo(3.1,(1,2.3,0,3));
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto root_node = ASTBuilder::build(input);
      register_functions(root_node,
                         FunctionList{
                           std::make_pair("foo",
                                          std::make_shared<
                                            BuiltinFunctionEmbedder<TinyMatrix<2>(double, const TinyMatrix<2>&)>>(
                                            [](double a, const TinyMatrix<2>& x) -> TinyMatrix<2> {
                                              return {a * x(0, 0), x(1, 1), a, x(1, 0)};
                                            }))});

      auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
      REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::matrix_t);
      REQUIRE(function_embedder->getReturnDataType().numberOfRows() == 2);
      REQUIRE(function_embedder->getParameterDataTypes().size() == 2);
      REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
      REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::matrix_t);
      REQUIRE(function_embedder->getParameterDataTypes()[1].numberOfRows() == 2);
    }
  }

  SECTION("using R^3x3 arguments")
  {
    SECTION("builtin function R*R^3x3 -> R^2x2")
    {
      std::string_view data = R"(
let x:R^3x3;
foo(3,x);
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto root_node = ASTBuilder::build(input);
      register_functions(root_node,
                         FunctionList{
                           std::make_pair("foo",
                                          std::make_shared<
                                            BuiltinFunctionEmbedder<TinyMatrix<2>(double, const TinyMatrix<3>&)>>(
                                            [](double a, const TinyMatrix<3>& x) -> TinyMatrix<2> {
                                              return {a * x(0, 0), x(1, 0) + x(0, 1),   //
                                                      a, x(1, 1)};
                                            }))});

      auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
      REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::matrix_t);
      REQUIRE(function_embedder->getReturnDataType().numberOfRows() == 2);
      REQUIRE(function_embedder->getParameterDataTypes().size() == 2);
      REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
      REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::matrix_t);
      REQUIRE(function_embedder->getParameterDataTypes()[1].numberOfRows() == 3);
    }

    SECTION("builtin function R*R^3x3 -> R^3x3")
    {
      std::string_view data = R"(
foo(3,0);
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto root_node = ASTBuilder::build(input);
      register_functions(root_node,
                         FunctionList{
                           std::make_pair("foo",
                                          std::make_shared<
                                            BuiltinFunctionEmbedder<TinyMatrix<3>(double, const TinyMatrix<3>&)>>(
                                            [](double a, const TinyMatrix<3>& x) -> TinyMatrix<3> {
                                              return {a * x};
                                            }))});

      auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
      REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::matrix_t);
      REQUIRE(function_embedder->getReturnDataType().numberOfRows() == 3);
      REQUIRE(function_embedder->getParameterDataTypes().size() == 2);
      REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
      REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::matrix_t);
      REQUIRE(function_embedder->getParameterDataTypes()[1].numberOfRows() == 3);
    }

    SECTION("builtin function R*R^3x3 -> R^2x2 (R^3x3 from list)")
    {
      std::string_view data = R"(
foo(3.1,(1, 2.3, 4, 0.3, 2.5, 4.6, 2.7, 8.1, -9));
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto root_node = ASTBuilder::build(input);
      register_functions(root_node,
                         FunctionList{
                           std::make_pair("foo",
                                          std::make_shared<
                                            BuiltinFunctionEmbedder<TinyMatrix<2>(double, const TinyMatrix<3>&)>>(
                                            [](double a, const TinyMatrix<3>& x) -> TinyMatrix<2> {
                                              return {a * x(1, 1), x(1, 0),   //
                                                      x(0, 1), a * x(0, 0)};
                                            }))});

      auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
      REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::matrix_t);
      REQUIRE(function_embedder->getReturnDataType().numberOfRows() == 2);
      REQUIRE(function_embedder->getParameterDataTypes().size() == 2);
      REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
      REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::matrix_t);
      REQUIRE(function_embedder->getParameterDataTypes()[1].numberOfRows() == 3);
    }
  }

  SECTION("using tuple arguments")
  {
    SECTION("builtin function (R...) -> N (from (N...))")
    {
      std::string_view data = R"(
let v:(N);
foo(v);
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto root_node = ASTBuilder::build(input);
      register_functions(root_node,
                         FunctionList{
                           std::make_pair("foo",
                                          std::make_shared<
                                            BuiltinFunctionEmbedder<uint64_t(const std::vector<double>&)>>(
                                            [](const std::vector<double>& x) -> uint64_t { return x.size(); }))});

      auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
      REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::unsigned_int_t);
      REQUIRE(function_embedder->getParameterDataTypes().size() == 1);
      REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::tuple_t);
      REQUIRE(function_embedder->getParameterDataTypes()[0].contentType() == ASTNodeDataType::double_t);
    }

    SECTION("builtin function (R...) -> N (from Z)")
    {
      std::string_view data = R"(
foo(-4);
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto root_node = ASTBuilder::build(input);
      register_functions(root_node,
                         FunctionList{
                           std::make_pair("foo",
                                          std::make_shared<
                                            BuiltinFunctionEmbedder<uint64_t(const std::vector<double>&)>>(
                                            [](const std::vector<double>& x) -> uint64_t { return x.size(); }))});

      auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
      REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::unsigned_int_t);
      REQUIRE(function_embedder->getParameterDataTypes().size() == 1);
      REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::tuple_t);
      REQUIRE(function_embedder->getParameterDataTypes()[0].contentType() == ASTNodeDataType::double_t);
    }

    SECTION("builtin function (R^1...) -> N (from (N...))")
    {
      std::string_view data = R"(
let v:(N);
foo(v);
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto root_node = ASTBuilder::build(input);
      register_functions(root_node,
                         FunctionList{std::make_pair("foo", std::make_shared<BuiltinFunctionEmbedder<uint64_t(
                                                              const std::vector<TinyVector<1>>&)>>(
                                                              [](const std::vector<TinyVector<1>>& x) -> uint64_t {
                                                                return x.size();
                                                              }))});

      auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
      REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::unsigned_int_t);
      REQUIRE(function_embedder->getParameterDataTypes().size() == 1);
      REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::tuple_t);
      REQUIRE(function_embedder->getParameterDataTypes()[0].contentType() == ASTNodeDataType::vector_t);
      REQUIRE(function_embedder->getParameterDataTypes()[0].contentType().dimension() == 1);
    }

    SECTION("builtin function (R^1...) -> N (from 0)")
    {
      std::string_view data = R"(
foo(0);
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto root_node = ASTBuilder::build(input);
      register_functions(root_node,
                         FunctionList{std::make_pair("foo", std::make_shared<BuiltinFunctionEmbedder<uint64_t(
                                                              const std::vector<TinyVector<1>>&)>>(
                                                              [](const std::vector<TinyVector<1>>& x) -> uint64_t {
                                                                return x.size();
                                                              }))});

      auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
      REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::unsigned_int_t);
      REQUIRE(function_embedder->getParameterDataTypes().size() == 1);
      REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::tuple_t);
      REQUIRE(function_embedder->getParameterDataTypes()[0].contentType() == ASTNodeDataType::vector_t);
      REQUIRE(function_embedder->getParameterDataTypes()[0].contentType().dimension() == 1);
    }

    SECTION("builtin function (R...) -> N (from list)")
    {
      std::string_view data = R"(
foo((1,2,3,5));
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto root_node = ASTBuilder::build(input);
      register_functions(root_node,
                         FunctionList{
                           std::make_pair("foo",
                                          std::make_shared<
                                            BuiltinFunctionEmbedder<uint64_t(const std::vector<double>&)>>(
                                            [](const std::vector<double>& x) -> uint64_t { return x.size(); }))});

      auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
      REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::unsigned_int_t);
      REQUIRE(function_embedder->getParameterDataTypes().size() == 1);
      REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::tuple_t);
      REQUIRE(function_embedder->getParameterDataTypes()[0].contentType() == ASTNodeDataType::double_t);
    }

    SECTION("builtin function (R^1...) -> N (from castable list)")
    {
      std::string_view data = R"(
foo((1,2,3,5));
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto root_node = ASTBuilder::build(input);
      register_functions(root_node,
                         FunctionList{std::make_pair("foo", std::make_shared<BuiltinFunctionEmbedder<uint64_t(
                                                              const std::vector<TinyVector<1>>&)>>(
                                                              [](const std::vector<TinyVector<1>>& x) -> uint64_t {
                                                                return x.size();
                                                              }))});

      auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
      REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::unsigned_int_t);
      REQUIRE(function_embedder->getParameterDataTypes().size() == 1);
      REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::tuple_t);
      REQUIRE(function_embedder->getParameterDataTypes()[0].contentType() == ASTNodeDataType::vector_t);
      REQUIRE(function_embedder->getParameterDataTypes()[0].contentType().dimension() == 1);
    }

    SECTION("builtin function (R^1...) -> N (from list)")
    {
      std::string_view data = R"(
let x:R^1;
foo((x,2*x));
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto root_node = ASTBuilder::build(input);
      register_functions(root_node,
                         FunctionList{std::make_pair("foo", std::make_shared<BuiltinFunctionEmbedder<uint64_t(
                                                              const std::vector<TinyVector<1>>&)>>(
                                                              [](const std::vector<TinyVector<1>>& x) -> uint64_t {
                                                                return x.size();
                                                              }))});

      auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
      REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::unsigned_int_t);
      REQUIRE(function_embedder->getParameterDataTypes().size() == 1);
      REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::tuple_t);
      REQUIRE(function_embedder->getParameterDataTypes()[0].contentType() == ASTNodeDataType::vector_t);
      REQUIRE(function_embedder->getParameterDataTypes()[0].contentType().dimension() == 1);
    }

    SECTION("builtin function (R^1x1...) -> N (from castable list)")
    {
      std::string_view data = R"(
foo((1,2,3,5));
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto root_node = ASTBuilder::build(input);
      register_functions(root_node,
                         FunctionList{std::make_pair("foo", std::make_shared<BuiltinFunctionEmbedder<uint64_t(
                                                              const std::vector<TinyMatrix<1>>&)>>(
                                                              [](const std::vector<TinyMatrix<1>>& x) -> uint64_t {
                                                                return x.size();
                                                              }))});

      auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
      REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::unsigned_int_t);
      REQUIRE(function_embedder->getParameterDataTypes().size() == 1);
      REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::tuple_t);
      REQUIRE(function_embedder->getParameterDataTypes()[0].contentType() == ASTNodeDataType::matrix_t);
      REQUIRE(function_embedder->getParameterDataTypes()[0].contentType().numberOfRows() == 1);
    }

    SECTION("builtin function (R^1x1...) -> N (from 0)")
    {
      std::string_view data = R"(
foo(0);
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto root_node = ASTBuilder::build(input);
      register_functions(root_node,
                         FunctionList{std::make_pair("foo", std::make_shared<BuiltinFunctionEmbedder<uint64_t(
                                                              const std::vector<TinyMatrix<1>>&)>>(
                                                              [](const std::vector<TinyMatrix<1>>& x) -> uint64_t {
                                                                return x.size();
                                                              }))});

      auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
      REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::unsigned_int_t);
      REQUIRE(function_embedder->getParameterDataTypes().size() == 1);
      REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::tuple_t);
      REQUIRE(function_embedder->getParameterDataTypes()[0].contentType() == ASTNodeDataType::matrix_t);
      REQUIRE(function_embedder->getParameterDataTypes()[0].contentType().numberOfRows() == 1);
    }

    SECTION("builtin function (R^1x1...) -> N (from list)")
    {
      std::string_view data = R"(
let x:R^1x1;
foo((x,2*x));
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto root_node = ASTBuilder::build(input);
      register_functions(root_node,
                         FunctionList{std::make_pair("foo", std::make_shared<BuiltinFunctionEmbedder<uint64_t(
                                                              const std::vector<TinyMatrix<1>>&)>>(
                                                              [](const std::vector<TinyMatrix<1>>& x) -> uint64_t {
                                                                return x.size();
                                                              }))});

      auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
      REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::unsigned_int_t);
      REQUIRE(function_embedder->getParameterDataTypes().size() == 1);
      REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::tuple_t);
      REQUIRE(function_embedder->getParameterDataTypes()[0].contentType() == ASTNodeDataType::matrix_t);
      REQUIRE(function_embedder->getParameterDataTypes()[0].contentType().numberOfRows() == 1);
    }

    SECTION("builtin function (R^2...) -> N (from list)")
    {
      std::string_view data = R"(
let x:R^2;
foo((x,2*x));
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto root_node = ASTBuilder::build(input);
      register_functions(root_node,
                         FunctionList{std::make_pair("foo", std::make_shared<BuiltinFunctionEmbedder<uint64_t(
                                                              const std::vector<TinyVector<2>>&)>>(
                                                              [](const std::vector<TinyVector<2>>& x) -> uint64_t {
                                                                return x.size();
                                                              }))});

      auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
      REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::unsigned_int_t);
      REQUIRE(function_embedder->getParameterDataTypes().size() == 1);
      REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::tuple_t);
      REQUIRE(function_embedder->getParameterDataTypes()[0].contentType() == ASTNodeDataType::vector_t);
      REQUIRE(function_embedder->getParameterDataTypes()[0].contentType().dimension() == 2);
    }

    SECTION("builtin function (R^2...) -> N (from 0)")
    {
      std::string_view data = R"(
foo(0);
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto root_node = ASTBuilder::build(input);
      register_functions(root_node,
                         FunctionList{std::make_pair("foo", std::make_shared<BuiltinFunctionEmbedder<uint64_t(
                                                              const std::vector<TinyVector<2>>&)>>(
                                                              [](const std::vector<TinyVector<2>>& x) -> uint64_t {
                                                                return x.size();
                                                              }))});

      auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
      REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::unsigned_int_t);
      REQUIRE(function_embedder->getParameterDataTypes().size() == 1);
      REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::tuple_t);
      REQUIRE(function_embedder->getParameterDataTypes()[0].contentType() == ASTNodeDataType::vector_t);
      REQUIRE(function_embedder->getParameterDataTypes()[0].contentType().dimension() == 2);
    }

    SECTION("builtin function (R^2x2...) -> N (from castable list)")
    {
      std::string_view data = R"(
foo((1,2,3));
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto root_node = ASTBuilder::build(input);
      register_functions(root_node,
                         FunctionList{std::make_pair("foo", std::make_shared<BuiltinFunctionEmbedder<uint64_t(
                                                              const std::vector<TinyMatrix<2>>&)>>(
                                                              [](const std::vector<TinyMatrix<2>>& x) -> uint64_t {
                                                                return x.size();
                                                              }))});

      auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
      REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::unsigned_int_t);
      REQUIRE(function_embedder->getParameterDataTypes().size() == 1);
      REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::tuple_t);
      REQUIRE(function_embedder->getParameterDataTypes()[0].contentType() == ASTNodeDataType::matrix_t);
      REQUIRE(function_embedder->getParameterDataTypes()[0].contentType().numberOfRows() == 2);
    }

    SECTION("builtin function (R^2x2...) -> N (from 0)")
    {
      std::string_view data = R"(
foo(0);
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto root_node = ASTBuilder::build(input);
      register_functions(root_node,
                         FunctionList{std::make_pair("foo", std::make_shared<BuiltinFunctionEmbedder<uint64_t(
                                                              const std::vector<TinyMatrix<2>>&)>>(
                                                              [](const std::vector<TinyMatrix<2>>& x) -> uint64_t {
                                                                return x.size();
                                                              }))});

      auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
      REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::unsigned_int_t);
      REQUIRE(function_embedder->getParameterDataTypes().size() == 1);
      REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::tuple_t);
      REQUIRE(function_embedder->getParameterDataTypes()[0].contentType() == ASTNodeDataType::matrix_t);
      REQUIRE(function_embedder->getParameterDataTypes()[0].contentType().numberOfRows() == 2);
    }

    SECTION("builtin function (R^2x2...) -> N (from list)")
    {
      std::string_view data = R"(
let x:R^2x2;
foo((x,2*x));
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto root_node = ASTBuilder::build(input);
      register_functions(root_node,
                         FunctionList{std::make_pair("foo", std::make_shared<BuiltinFunctionEmbedder<uint64_t(
                                                              const std::vector<TinyMatrix<2>>&)>>(
                                                              [](const std::vector<TinyMatrix<2>>& x) -> uint64_t {
                                                                return x.size();
                                                              }))});

      auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
      REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::unsigned_int_t);
      REQUIRE(function_embedder->getParameterDataTypes().size() == 1);
      REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::tuple_t);
      REQUIRE(function_embedder->getParameterDataTypes()[0].contentType() == ASTNodeDataType::matrix_t);
      REQUIRE(function_embedder->getParameterDataTypes()[0].contentType().numberOfRows() == 2);
    }

    SECTION("builtin function (R^3...) -> N (from list)")
    {
      std::string_view data = R"(
let x:R^3;
foo((x,2*x));
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto root_node = ASTBuilder::build(input);
      register_functions(root_node,
                         FunctionList{std::make_pair("foo", std::make_shared<BuiltinFunctionEmbedder<uint64_t(
                                                              const std::vector<TinyVector<3>>&)>>(
                                                              [](const std::vector<TinyVector<3>>& x) -> uint64_t {
                                                                return x.size();
                                                              }))});

      auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
      REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::unsigned_int_t);
      REQUIRE(function_embedder->getParameterDataTypes().size() == 1);
      REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::tuple_t);
      REQUIRE(function_embedder->getParameterDataTypes()[0].contentType() == ASTNodeDataType::vector_t);
      REQUIRE(function_embedder->getParameterDataTypes()[0].contentType().dimension() == 3);
    }

    SECTION("builtin function (R^3...) -> N (from 0)")
    {
      std::string_view data = R"(
foo(0);
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto root_node = ASTBuilder::build(input);
      register_functions(root_node,
                         FunctionList{std::make_pair("foo", std::make_shared<BuiltinFunctionEmbedder<uint64_t(
                                                              const std::vector<TinyVector<3>>&)>>(
                                                              [](const std::vector<TinyVector<3>>& x) -> uint64_t {
                                                                return x.size();
                                                              }))});

      auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
      REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::unsigned_int_t);
      REQUIRE(function_embedder->getParameterDataTypes().size() == 1);
      REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::tuple_t);
      REQUIRE(function_embedder->getParameterDataTypes()[0].contentType() == ASTNodeDataType::vector_t);
      REQUIRE(function_embedder->getParameterDataTypes()[0].contentType().dimension() == 3);
    }

    SECTION("builtin function (R^3x3...) -> N (from castable list)")
    {
      std::string_view data = R"(
foo((1,2,3));
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto root_node = ASTBuilder::build(input);
      register_functions(root_node,
                         FunctionList{std::make_pair("foo", std::make_shared<BuiltinFunctionEmbedder<uint64_t(
                                                              const std::vector<TinyMatrix<3>>&)>>(
                                                              [](const std::vector<TinyMatrix<3>>& x) -> uint64_t {
                                                                return x.size();
                                                              }))});

      auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
      REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::unsigned_int_t);
      REQUIRE(function_embedder->getParameterDataTypes().size() == 1);
      REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::tuple_t);
      REQUIRE(function_embedder->getParameterDataTypes()[0].contentType() == ASTNodeDataType::matrix_t);
      REQUIRE(function_embedder->getParameterDataTypes()[0].contentType().numberOfRows() == 3);
    }

    SECTION("builtin function (R^3x3...) -> N (from 0)")
    {
      std::string_view data = R"(
foo(0);
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto root_node = ASTBuilder::build(input);
      register_functions(root_node,
                         FunctionList{std::make_pair("foo", std::make_shared<BuiltinFunctionEmbedder<uint64_t(
                                                              const std::vector<TinyMatrix<3>>&)>>(
                                                              [](const std::vector<TinyMatrix<3>>& x) -> uint64_t {
                                                                return x.size();
                                                              }))});

      auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
      REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::unsigned_int_t);
      REQUIRE(function_embedder->getParameterDataTypes().size() == 1);
      REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::tuple_t);
      REQUIRE(function_embedder->getParameterDataTypes()[0].contentType() == ASTNodeDataType::matrix_t);
      REQUIRE(function_embedder->getParameterDataTypes()[0].contentType().numberOfRows() == 3);
    }

    SECTION("builtin function (R^3x3...) -> N (from list)")
    {
      std::string_view data = R"(
let x:R^3x3;
foo((x,2*x));
)";

      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
      auto root_node = ASTBuilder::build(input);
      register_functions(root_node,
                         FunctionList{std::make_pair("foo", std::make_shared<BuiltinFunctionEmbedder<uint64_t(
                                                              const std::vector<TinyMatrix<3>>&)>>(
                                                              [](const std::vector<TinyMatrix<3>>& x) -> uint64_t {
                                                                return x.size();
                                                              }))});

      auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
      REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::unsigned_int_t);
      REQUIRE(function_embedder->getParameterDataTypes().size() == 1);
      REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::tuple_t);
      REQUIRE(function_embedder->getParameterDataTypes()[0].contentType() == ASTNodeDataType::matrix_t);
      REQUIRE(function_embedder->getParameterDataTypes()[0].contentType().numberOfRows() == 3);
    }
  }

  SECTION("complete case")
  {
    std::string_view data = R"(
let x:R^3x3;
foo(1, "bar", (x,2*x), (2,3));
)";

    TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
    auto root_node = ASTBuilder::build(input);
    register_functions(root_node,
                       FunctionList{
                         std::make_pair("foo",
                                        std::make_shared<BuiltinFunctionEmbedder<
                                          std::tuple<uint64_t, double, std::string>(const double&, const std::string&,
                                                                                    const std::vector<TinyMatrix<3>>&,
                                                                                    const TinyVector<2>&)>>(
                                          [](const double& a, const std::string& s, const std::vector<TinyMatrix<3>>& x,
                                             const TinyVector<2>& y) -> std::tuple<uint64_t, double, std::string> {
                                            return std::make_tuple(x.size(), a * y[0] + y[1], s + "_foo");
                                          }))});

    auto function_embedder = getBuiltinFunctionEmbedder(*root_node->children[0]);
    REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::list_t);
    REQUIRE(function_embedder->getReturnDataType().contentTypeList().size() == 3);
    REQUIRE(*function_embedder->getReturnDataType().contentTypeList()[0] == ASTNodeDataType::unsigned_int_t);
    REQUIRE(*function_embedder->getReturnDataType().contentTypeList()[1] == ASTNodeDataType::double_t);
    REQUIRE(*function_embedder->getReturnDataType().contentTypeList()[2] == ASTNodeDataType::string_t);
    REQUIRE(function_embedder->getParameterDataTypes().size() == 4);
    REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
    REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::string_t);
    REQUIRE(function_embedder->getParameterDataTypes()[2] == ASTNodeDataType::tuple_t);
    REQUIRE(function_embedder->getParameterDataTypes()[2].contentType() == ASTNodeDataType::matrix_t);
    REQUIRE(function_embedder->getParameterDataTypes()[2].contentType().numberOfRows() == 3);
    REQUIRE(function_embedder->getParameterDataTypes()[3] == ASTNodeDataType::vector_t);
    REQUIRE(function_embedder->getParameterDataTypes()[3].dimension() == 2);
  }

  SECTION("errors")
  {
    SECTION("R^1: invalid conversion")
    {
      std::string_view data = R"(
let x:R^3;
foo(x);
)";

      std::string error_msg = "no matching function to call foo: R^3\n"
                              "note: candidates are\n"
                              " foo: R^1 -> R\n"
                              " foo: R -> R";

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

      REQUIRE_THROWS_WITH(register_functions(root_node,
                                             FunctionList{std::make_pair("foo",
                                                                         std::make_shared<BuiltinFunctionEmbedder<
                                                                           double(const TinyVector<1>)>>(
                                                                           [](const TinyVector<1>& x) -> double {
                                                                             return x[0];
                                                                           })),
                                                          std::make_pair("foo",
                                                                         std::make_shared<BuiltinFunctionEmbedder<
                                                                           double(const double&)>>(
                                                                           [](const double& x) -> double {
                                                                             return x;
                                                                           }))}),
                          error_msg);
    }

    SECTION("R^2: invalid conversion")
    {
      std::string_view data = R"(
let x:R^3;
foo(x);
)";

      std::string error_msg = "no matching function to call foo: R^3\n"
                              "note: candidates are\n"
                              " foo: R^2 -> R\n"
                              " foo: R -> R";

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

      REQUIRE_THROWS_WITH(register_functions(root_node,
                                             FunctionList{std::make_pair("foo",
                                                                         std::make_shared<BuiltinFunctionEmbedder<
                                                                           double(const TinyVector<2>)>>(
                                                                           [](const TinyVector<2>& x) -> double {
                                                                             return x[1];
                                                                           })),
                                                          std::make_pair("foo",
                                                                         std::make_shared<BuiltinFunctionEmbedder<
                                                                           double(const double&)>>(
                                                                           [](const double& x) -> double {
                                                                             return x;
                                                                           }))}),
                          error_msg);
    }

    SECTION("R^3: invalid conversion")
    {
      std::string_view data = R"(
let x:R^2;
foo(x);
)";

      std::string error_msg = "no matching function to call foo: R^2\n"
                              "note: candidates are\n"
                              " foo: R^3 -> R\n"
                              " foo: R -> R";

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

      REQUIRE_THROWS_WITH(register_functions(root_node,
                                             FunctionList{std::make_pair("foo",
                                                                         std::make_shared<BuiltinFunctionEmbedder<
                                                                           double(const TinyVector<3>)>>(
                                                                           [](const TinyVector<3>& x) -> double {
                                                                             return x[1];
                                                                           })),
                                                          std::make_pair("foo",
                                                                         std::make_shared<BuiltinFunctionEmbedder<
                                                                           double(const double&)>>(
                                                                           [](const double& x) -> double {
                                                                             return x;
                                                                           }))}),
                          error_msg);
    }

    SECTION("R^2: invalid argument list size")
    {
      std::string_view data = R"(
foo((1,2,3,4));
)";

      std::string error_msg = "no matching function to call foo: Z*Z*Z*Z\n"
                              "note: candidates are\n"
                              " foo: R^2 -> R\n"
                              " foo: R -> R";

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

      REQUIRE_THROWS_WITH(register_functions(root_node,
                                             FunctionList{std::make_pair("foo",
                                                                         std::make_shared<BuiltinFunctionEmbedder<
                                                                           double(const TinyVector<2>)>>(
                                                                           [](const TinyVector<2>& x) -> double {
                                                                             return x[1];
                                                                           })),
                                                          std::make_pair("foo",
                                                                         std::make_shared<BuiltinFunctionEmbedder<
                                                                           double(const double&)>>(
                                                                           [](const double& x) -> double {
                                                                             return x;
                                                                           }))}),
                          error_msg);
    }

    SECTION("R^3: invalid argument list size")
    {
      std::string_view data = R"(
foo((1,2,3,4));
)";

      std::string error_msg = "no matching function to call foo: Z*Z*Z*Z\n"
                              "note: candidates are\n"
                              " foo: R^3 -> R\n"
                              " foo: R -> R";

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

      REQUIRE_THROWS_WITH(register_functions(root_node,
                                             FunctionList{std::make_pair("foo",
                                                                         std::make_shared<BuiltinFunctionEmbedder<
                                                                           double(const TinyVector<3>)>>(
                                                                           [](const TinyVector<3>& x) -> double {
                                                                             return x[1];
                                                                           })),
                                                          std::make_pair("foo",
                                                                         std::make_shared<BuiltinFunctionEmbedder<
                                                                           double(const double&)>>(
                                                                           [](const double& x) -> double {
                                                                             return x;
                                                                           }))}),
                          error_msg);
    }

    SECTION("R^1x1: invalid conversion")
    {
      std::string_view data = R"(
let x:R^3x3;
foo(x);
)";

      std::string error_msg = "no matching function to call foo: R^3x3\n"
                              "note: candidates are\n"
                              " foo: R^1x1 -> R\n"
                              " foo: R -> R";

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

      REQUIRE_THROWS_WITH(register_functions(root_node,
                                             FunctionList{std::make_pair("foo",
                                                                         std::make_shared<BuiltinFunctionEmbedder<
                                                                           double(const TinyMatrix<1>)>>(
                                                                           [](const TinyMatrix<1>& x) -> double {
                                                                             return x(0, 0);
                                                                           })),
                                                          std::make_pair("foo",
                                                                         std::make_shared<BuiltinFunctionEmbedder<
                                                                           double(const double&)>>(
                                                                           [](const double& x) -> double {
                                                                             return x;
                                                                           }))}),
                          error_msg);
    }

    SECTION("R^2x2: invalid conversion")
    {
      std::string_view data = R"(
let x:R^3x3;
foo(x);
)";

      std::string error_msg = "no matching function to call foo: R^3x3\n"
                              "note: candidates are\n"
                              " foo: R^2x2 -> R\n"
                              " foo: R -> R";

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

      REQUIRE_THROWS_WITH(register_functions(root_node,
                                             FunctionList{std::make_pair("foo",
                                                                         std::make_shared<BuiltinFunctionEmbedder<
                                                                           double(const TinyMatrix<2>)>>(
                                                                           [](const TinyMatrix<2>& x) -> double {
                                                                             return x(0, 0);
                                                                           })),
                                                          std::make_pair("foo",
                                                                         std::make_shared<BuiltinFunctionEmbedder<
                                                                           double(const double&)>>(
                                                                           [](const double& x) -> double {
                                                                             return x;
                                                                           }))}),
                          error_msg);
    }

    SECTION("R^3x3: invalid conversion")
    {
      std::string_view data = R"(
let x:R^2x2;
foo(x);
)";

      std::string error_msg = "no matching function to call foo: R^2x2\n"
                              "note: candidates are\n"
                              " foo: R^3x3 -> R\n"
                              " foo: R -> R";

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

      REQUIRE_THROWS_WITH(register_functions(root_node,
                                             FunctionList{std::make_pair("foo",
                                                                         std::make_shared<BuiltinFunctionEmbedder<
                                                                           double(const TinyMatrix<3>)>>(
                                                                           [](const TinyMatrix<3>& x) -> double {
                                                                             return x(1, 2);
                                                                           })),
                                                          std::make_pair("foo",
                                                                         std::make_shared<BuiltinFunctionEmbedder<
                                                                           double(const double&)>>(
                                                                           [](const double& x) -> double {
                                                                             return x;
                                                                           }))}),
                          error_msg);
    }

    SECTION("R^2x2: invalid argument list size")
    {
      std::string_view data = R"(
foo((1,2,3));
)";

      std::string error_msg = "no matching function to call foo: Z*Z*Z\n"
                              "note: candidates are\n"
                              " foo: R^2x2 -> R\n"
                              " foo: R -> R";

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

      REQUIRE_THROWS_WITH(register_functions(root_node,
                                             FunctionList{std::make_pair("foo",
                                                                         std::make_shared<BuiltinFunctionEmbedder<
                                                                           double(const TinyMatrix<2>)>>(
                                                                           [](const TinyMatrix<2>& x) -> double {
                                                                             return x(0, 0);
                                                                           })),
                                                          std::make_pair("foo",
                                                                         std::make_shared<BuiltinFunctionEmbedder<
                                                                           double(const double&)>>(
                                                                           [](const double& x) -> double {
                                                                             return x;
                                                                           }))}),
                          error_msg);
    }

    SECTION("R^3x3: invalid argument list size")
    {
      std::string_view data = R"(
foo((1,2,3,4));
)";

      std::string error_msg = "no matching function to call foo: Z*Z*Z*Z\n"
                              "note: candidates are\n"
                              " foo: R^3x3 -> R\n"
                              " foo: R -> R";

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

      REQUIRE_THROWS_WITH(register_functions(root_node,
                                             FunctionList{std::make_pair("foo",
                                                                         std::make_shared<BuiltinFunctionEmbedder<
                                                                           double(const TinyMatrix<3>)>>(
                                                                           [](const TinyMatrix<3>& x) -> double {
                                                                             return x(1, 2);
                                                                           })),
                                                          std::make_pair("foo",
                                                                         std::make_shared<BuiltinFunctionEmbedder<
                                                                           double(const double&)>>(
                                                                           [](const double& x) -> double {
                                                                             return x;
                                                                           }))}),
                          error_msg);
    }

    SECTION("(N...) invalid cast")
    {
      std::string_view data = R"(
foo(1.34);
)";

      std::string error_msg = "no matching function to call foo: R\n"
                              "note: candidates are\n"
                              " foo: (N...) -> N";

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

      REQUIRE_THROWS_WITH(register_functions(root_node,
                                             FunctionList{
                                               std::make_pair("foo", std::make_shared<BuiltinFunctionEmbedder<uint64_t(
                                                                       const std::vector<uint64_t>&)>>(
                                                                       [](const std::vector<uint64_t>& t) -> uint64_t {
                                                                         return t.size();
                                                                       }))}),
                          error_msg);
    }

    SECTION("ambiguous function call")
    {
      std::string_view data = R"(
foo(1);
)";

      std::string error_msg = "ambiguous function call foo: Z\n"
                              "note: candidates are\n"
                              " foo: (R...) -> R\n"
                              " foo: R -> R\n"
                              " foo: R^1 -> R";

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

      REQUIRE_THROWS_WITH(register_functions(root_node,
                                             FunctionList{std::make_pair("foo",
                                                                         std::make_shared<BuiltinFunctionEmbedder<
                                                                           double(const std::vector<double>&)>>(
                                                                           [](const std::vector<double>& x) -> double {
                                                                             return x.size();
                                                                           })),
                                                          std::make_pair("foo",
                                                                         std::make_shared<BuiltinFunctionEmbedder<
                                                                           double(const double&)>>(
                                                                           [](const double& x) -> double {
                                                                             return x;
                                                                           })),
                                                          std::make_pair("foo",
                                                                         std::make_shared<BuiltinFunctionEmbedder<
                                                                           double(const TinyVector<1>&)>>(
                                                                           [](const TinyVector<1>& x) -> double {
                                                                             return x[0];
                                                                           }))}),
                          error_msg);
    }
  }
}