diff --git a/src/language/ASTNodeCFunctionExpressionBuilder.cpp b/src/language/ASTNodeCFunctionExpressionBuilder.cpp
index ec50f7b53d997b93f49367783d800288a717ffcf..0218bda83d12846e6eab106c91dd747dd5429c2b 100644
--- a/src/language/ASTNodeCFunctionExpressionBuilder.cpp
+++ b/src/language/ASTNodeCFunctionExpressionBuilder.cpp
@@ -47,7 +47,7 @@ ASTNodeCFunctionExpressionBuilder::_getArgumentProcessor(ASTNode& argument_node,
       return get_function_argument_processor_for_parameter_type(double{});
     }
     default: {
-      throw parse_error("unexpected error: invalid argument type for function", std::vector{argument_node.begin()});
+      throw parse_error("invalid argument type for function", std::vector{argument_node.begin()});
     }
     }
   };
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 1ceee1ee79dbb16d2b6d7c209f9237bb2983acf9..8d210ede78620ee09f9294c8a9145de0e36b8745 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -13,6 +13,7 @@ add_executable (unit_tests
   test_ASTNode.cpp
   test_ASTNodeAffectationExpressionBuilder.cpp
   test_ASTNodeBinaryOperatorExpressionBuilder.cpp
+  test_ASTNodeCFunctionExpressionBuilder.cpp
   test_ASTNodeDataType.cpp
   test_ASTNodeDataTypeBuilder.cpp
   test_ASTNodeDataTypeChecker.cpp
diff --git a/tests/test_ASTNodeCFunctionExpressionBuilder.cpp b/tests/test_ASTNodeCFunctionExpressionBuilder.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..7ce9f1f32823a46f97b8f44e2b55fe1b9cd77ed5
--- /dev/null
+++ b/tests/test_ASTNodeCFunctionExpressionBuilder.cpp
@@ -0,0 +1,460 @@
+#include <catch2/catch.hpp>
+
+#include <ASTNodeValueBuilder.hpp>
+
+#include <ASTBuilder.hpp>
+#include <ASTNodeDataTypeBuilder.hpp>
+
+#include <ASTModulesImporter.hpp>
+
+#include <ASTNodeExpressionBuilder.hpp>
+#include <ASTNodeFunctionEvaluationExpressionBuilder.hpp>
+#include <ASTNodeFunctionExpressionBuilder.hpp>
+#include <ASTNodeTypeCleaner.hpp>
+
+#include <CFunctionEmbedder.hpp>
+
+#include <ASTSymbolTableBuilder.hpp>
+
+#include <ASTPrinter.hpp>
+
+#include <PEGGrammar.hpp>
+
+#include <Demangle.hpp>
+
+#include <unordered_map>
+
+namespace test_only
+{
+class CFunctionRegister
+{
+ private:
+  std::unordered_map<std::string, std::shared_ptr<ICFunctionEmbedder>> m_name_cfunction_map;
+
+  void
+  _populateNameCFunctionMap()
+  {
+    m_name_cfunction_map.insert(
+      std::make_pair("RtoR", std::make_shared<CFunctionEmbedder<double, double>>(
+                               std::function<double(double)>{[](double x) -> double { return x + 1; }})));
+
+    m_name_cfunction_map.insert(
+      std::make_pair("ZtoR", std::make_shared<CFunctionEmbedder<double, int64_t>>(
+                               std::function<double(int64_t)>{[](int64_t z) -> double { return 0.5 * z; }})));
+
+    m_name_cfunction_map.insert(
+      std::make_pair("NtoR", std::make_shared<CFunctionEmbedder<double, uint64_t>>(
+                               std::function<double(uint64_t)>{[](uint64_t n) -> double { return 0.5 * n; }})));
+
+    m_name_cfunction_map.insert(
+      std::make_pair("BtoR", std::make_shared<CFunctionEmbedder<double, bool>>(
+                               std::function<double(bool)>{[](bool b) -> double { return b; }})));
+
+    m_name_cfunction_map.insert(std::make_pair("R2toB", std::make_shared<CFunctionEmbedder<bool, double, double>>(
+                                                          std::function<bool(double, double)>{
+                                                            [](double x, double y) -> bool { return x > y; }})));
+
+    m_name_cfunction_map.insert(
+      std::make_pair("StoB_invalid",
+                     std::make_shared<CFunctionEmbedder<bool, std::string>>(
+                       std::function<bool(std::string)>{[](std::string s) -> bool { return s.size() > 0; }})));
+  }
+
+ public:
+  CFunctionRegister(ASTNode& root_node)
+  {
+    SymbolTable& symbol_table = *root_node.m_symbol_table;
+
+    CFunctionEmbedderTable& c_function_embedder_table = symbol_table.cFunctionEbedderTable();
+
+    this->_populateNameCFunctionMap();
+
+    for (auto [symbol_name, c_function] : m_name_cfunction_map) {
+      auto [i_symbol, success] = symbol_table.add(symbol_name, root_node.begin());
+
+      if (not success) {
+        std::ostringstream error_message;
+        error_message << "cannot add symbol '" << symbol_name << "' it is already defined";
+        throw parse_error(error_message.str(), root_node.begin());
+      }
+
+      i_symbol->attributes().setDataType(ASTNodeDataType::c_function_t);
+      i_symbol->attributes().setIsInitialized();
+      i_symbol->attributes().value() = c_function_embedder_table.size();
+
+      c_function_embedder_table.add(c_function);
+    }
+  }
+};
+}   // namespace test_only
+
+#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>));          \
+                                                                                                    \
+    string_input input{data, "test.pgs"};                                                           \
+    auto ast = ASTBuilder::build(input);                                                            \
+                                                                                                    \
+    test_only::CFunctionRegister{*ast};                                                             \
+                                                                                                    \
+    ASTSymbolTableBuilder{*ast};                                                                    \
+    ASTNodeDataTypeBuilder{*ast};                                                                   \
+    ASTNodeValueBuilder{*ast};                                                                      \
+                                                                                                    \
+    ASTNodeTypeCleaner<language::declaration>{*ast};                                                \
+    ASTNodeExpressionBuilder{*ast};                                                                 \
+                                                                                                    \
+    std::stringstream ast_output;                                                                   \
+    ast_output << '\n' << ASTPrinter{*ast, ASTPrinter::Format::raw, {ASTPrinter::Info::exec_type}}; \
+                                                                                                    \
+    REQUIRE(ast_output.str() == expected_output);                                                   \
+  }
+
+#define CHECK_AST_THROWS_WITH(data, expected_error)                                                 \
+  {                                                                                                 \
+    static_assert(std::is_same_v<std::decay_t<decltype(data)>, std::string_view>);                  \
+    static_assert((std::is_same_v<std::decay_t<decltype(expected_error)>, std::string_view>) or     \
+                  (std::is_same_v<std::decay_t<decltype(expected_error)>, std::string>));           \
+                                                                                                    \
+    string_input input{data, "test.pgs"};                                                           \
+    auto ast = ASTBuilder::build(input);                                                            \
+                                                                                                    \
+    test_only::CFunctionRegister{*ast};                                                             \
+                                                                                                    \
+    ASTSymbolTableBuilder{*ast};                                                                    \
+    ASTNodeDataTypeBuilder{*ast};                                                                   \
+    ASTNodeValueBuilder{*ast};                                                                      \
+                                                                                                    \
+    ASTNodeTypeCleaner<language::declaration>{*ast};                                                \
+    REQUIRE_THROWS_WITH(ASTNodeExpressionBuilder{*ast}, Catch::Matchers::Contains(expected_error)); \
+  }
+
+TEST_CASE("ASTNodeCFunctionExpressionBuilder", "[language]")
+{
+  SECTION("R -> R")
+  {
+    SECTION("from R")
+    {
+      std::string_view data = R"(
+RtoR(1.);
+)";
+
+      std::string_view result = R"(
+(root:ASTNodeListProcessor)
+ `-(language::function_evaluation:CFunctionProcessor)
+     +-(language::name:RtoR:NameProcessor)
+     `-(language::real:1.:FakeProcessor)
+)";
+
+      CHECK_AST(data, result);
+    }
+
+    SECTION("from Z")
+    {
+      std::string_view data = R"(
+RtoR(1);
+)";
+
+      std::string_view result = R"(
+(root:ASTNodeListProcessor)
+ `-(language::function_evaluation:CFunctionProcessor)
+     +-(language::name:RtoR:NameProcessor)
+     `-(language::integer:1:FakeProcessor)
+)";
+
+      CHECK_AST(data, result);
+    }
+
+    SECTION("from N")
+    {
+      std::string_view data = R"(
+N n = 1;
+RtoR(n);
+)";
+
+      std::string_view result = R"(
+(root:ASTNodeListProcessor)
+ `-(language::function_evaluation:CFunctionProcessor)
+     +-(language::name:RtoR:NameProcessor)
+     `-(language::name:n:NameProcessor)
+)";
+
+      CHECK_AST(data, result);
+    }
+
+    SECTION("from B")
+    {
+      std::string_view data = R"(
+RtoR(true);
+)";
+
+      std::string_view result = R"(
+(root:ASTNodeListProcessor)
+ `-(language::function_evaluation:CFunctionProcessor)
+     +-(language::name:RtoR:NameProcessor)
+     `-(language::true_kw:FakeProcessor)
+)";
+
+      CHECK_AST(data, result);
+    }
+  }
+
+  SECTION("Z -> R")
+  {
+    SECTION("from R")
+    {
+      std::string_view data = R"(
+ZtoR(1.);
+)";
+
+      std::string_view result = R"(
+(root:ASTNodeListProcessor)
+ `-(language::function_evaluation:CFunctionProcessor)
+     +-(language::name:ZtoR:NameProcessor)
+     `-(language::real:1.:FakeProcessor)
+)";
+
+      CHECK_AST(data, result);
+    }
+
+    SECTION("from Z")
+    {
+      std::string_view data = R"(
+ZtoR(1);
+)";
+
+      std::string_view result = R"(
+(root:ASTNodeListProcessor)
+ `-(language::function_evaluation:CFunctionProcessor)
+     +-(language::name:ZtoR:NameProcessor)
+     `-(language::integer:1:FakeProcessor)
+)";
+
+      CHECK_AST(data, result);
+    }
+
+    SECTION("from N")
+    {
+      std::string_view data = R"(
+N n = 1;
+ZtoR(n);
+)";
+
+      std::string_view result = R"(
+(root:ASTNodeListProcessor)
+ `-(language::function_evaluation:CFunctionProcessor)
+     +-(language::name:ZtoR:NameProcessor)
+     `-(language::name:n:NameProcessor)
+)";
+
+      CHECK_AST(data, result);
+    }
+
+    SECTION("from B")
+    {
+      std::string_view data = R"(
+ZtoR(true);
+)";
+
+      std::string_view result = R"(
+(root:ASTNodeListProcessor)
+ `-(language::function_evaluation:CFunctionProcessor)
+     +-(language::name:ZtoR:NameProcessor)
+     `-(language::true_kw:FakeProcessor)
+)";
+
+      CHECK_AST(data, result);
+    }
+  }
+
+  SECTION("N -> R")
+  {
+    SECTION("from R")
+    {
+      std::string_view data = R"(
+NtoR(1.);
+)";
+
+      std::string_view result = R"(
+(root:ASTNodeListProcessor)
+ `-(language::function_evaluation:CFunctionProcessor)
+     +-(language::name:NtoR:NameProcessor)
+     `-(language::real:1.:FakeProcessor)
+)";
+
+      CHECK_AST(data, result);
+    }
+
+    SECTION("from Z")
+    {
+      std::string_view data = R"(
+NtoR(1);
+)";
+
+      std::string_view result = R"(
+(root:ASTNodeListProcessor)
+ `-(language::function_evaluation:CFunctionProcessor)
+     +-(language::name:NtoR:NameProcessor)
+     `-(language::integer:1:FakeProcessor)
+)";
+
+      CHECK_AST(data, result);
+    }
+
+    SECTION("from N")
+    {
+      std::string_view data = R"(
+N n = 1;
+NtoR(n);
+)";
+
+      std::string_view result = R"(
+(root:ASTNodeListProcessor)
+ `-(language::function_evaluation:CFunctionProcessor)
+     +-(language::name:NtoR:NameProcessor)
+     `-(language::name:n:NameProcessor)
+)";
+
+      CHECK_AST(data, result);
+    }
+
+    SECTION("from B")
+    {
+      std::string_view data = R"(
+NtoR(true);
+)";
+
+      std::string_view result = R"(
+(root:ASTNodeListProcessor)
+ `-(language::function_evaluation:CFunctionProcessor)
+     +-(language::name:NtoR:NameProcessor)
+     `-(language::true_kw:FakeProcessor)
+)";
+
+      CHECK_AST(data, result);
+    }
+  }
+
+  SECTION("B -> R")
+  {
+    SECTION("from R")
+    {
+      std::string_view data = R"(
+BtoR(1.);
+)";
+
+      std::string_view result = R"(
+(root:ASTNodeListProcessor)
+ `-(language::function_evaluation:CFunctionProcessor)
+     +-(language::name:BtoR:NameProcessor)
+     `-(language::real:1.:FakeProcessor)
+)";
+
+      CHECK_AST(data, result);
+    }
+
+    SECTION("from Z")
+    {
+      std::string_view data = R"(
+BtoR(1);
+)";
+
+      std::string_view result = R"(
+(root:ASTNodeListProcessor)
+ `-(language::function_evaluation:CFunctionProcessor)
+     +-(language::name:BtoR:NameProcessor)
+     `-(language::integer:1:FakeProcessor)
+)";
+
+      CHECK_AST(data, result);
+    }
+
+    SECTION("from N")
+    {
+      std::string_view data = R"(
+N n = 1;
+BtoR(n);
+)";
+
+      std::string_view result = R"(
+(root:ASTNodeListProcessor)
+ `-(language::function_evaluation:CFunctionProcessor)
+     +-(language::name:BtoR:NameProcessor)
+     `-(language::name:n:NameProcessor)
+)";
+
+      CHECK_AST(data, result);
+    }
+
+    SECTION("from B")
+    {
+      std::string_view data = R"(
+BtoR(true);
+)";
+
+      std::string_view result = R"(
+(root:ASTNodeListProcessor)
+ `-(language::function_evaluation:CFunctionProcessor)
+     +-(language::name:BtoR:NameProcessor)
+     `-(language::true_kw:FakeProcessor)
+)";
+
+      CHECK_AST(data, result);
+    }
+  }
+
+  SECTION("R2 -> B")
+  {
+    std::string_view data = R"(
+R2toB(1., 0.);
+)";
+
+    std::string_view result = R"(
+(root:ASTNodeListProcessor)
+ `-(language::function_evaluation:CFunctionProcessor)
+     +-(language::name:R2toB:NameProcessor)
+     `-(language::function_argument_list:FakeProcessor)
+         +-(language::real:1.:FakeProcessor)
+         `-(language::real:0.:FakeProcessor)
+)";
+
+    CHECK_AST(data, result);
+  }
+
+  SECTION("errors")
+  {
+    SECTION("bad number of arguments")
+    {
+      std::string_view data = R"(
+BtoR(true, false);
+)";
+      CHECK_AST_THROWS_WITH(data, std::string{"bad number of arguments:"});
+    }
+
+    SECTION("bad number of arguments 2")
+    {
+      std::string_view data = R"(
+R2toB(3);
+)";
+      CHECK_AST_THROWS_WITH(data, std::string{"bad number of arguments:"});
+    }
+
+    SECTION("invalid argument type")
+    {
+      std::string_view data = R"(
+RtoR("foo");
+)";
+      CHECK_AST_THROWS_WITH(data, std::string{"invalid argument type for function"});
+    }
+
+    SECTION("invalid function parameter type")
+    {
+      std::string_view data = R"(
+StoB_invalid(3);
+)";
+      CHECK_AST_THROWS_WITH(data, std::string{"unexpected error: undefined parameter type"});
+    }
+  }
+}