From 34d37b4999c881f1d8352dca0d6ac0b29a9bb7bf Mon Sep 17 00:00:00 2001
From: Stephane Del Pino <stephane.delpino44@gmail.com>
Date: Wed, 13 May 2020 19:48:40 +0200
Subject: [PATCH] Improve numerical function evaluation in C++

- code is now generic (even if more testing remains).
- data types are now check accordingly to catch error in a clean way

In some cases (generally?) data types can only be checked at
runtime. This is the case for instance if function definition depends
on the dimension (which is likely to happen in many cases). Thus a
special procedure has been developed to indicate where the semantic
error occurs at runtime. (this is in some way related to issue #19)
---
 src/language/ASTNodeDataType.cpp              |  10 +-
 src/language/ASTNodeDataTypeBuilder.cpp       |   6 +-
 .../ASTNodeNaturalConversionChecker.cpp       |   8 +-
 src/language/ASTSymbolTableBuilder.cpp        |   2 +-
 src/language/BuiltinFunctionEmbedder.hpp      |  17 +-
 src/language/FunctionTable.hpp                |  14 +-
 src/language/MeshModule.cpp                   |  25 +--
 src/language/PugsFunctionAdapter.hpp          | 161 ++++++++++++++++--
 .../BuiltinFunctionProcessor.hpp              |   9 +-
 src/language/utils/ASTNodeDataTypeTraits.hpp  |  28 +++
 src/language/utils/RuntimeError.hpp           |  27 +++
 src/utils/Exceptions.cpp                      |   6 +-
 ...st_ASTNodeAffectationExpressionBuilder.cpp |   5 +-
 tests/test_ASTNodeDataTypeBuilder.cpp         |  10 +-
 tests/test_FunctionTable.cpp                  |   7 +-
 15 files changed, 273 insertions(+), 62 deletions(-)
 create mode 100644 src/language/utils/ASTNodeDataTypeTraits.hpp
 create mode 100644 src/language/utils/RuntimeError.hpp

diff --git a/src/language/ASTNodeDataType.cpp b/src/language/ASTNodeDataType.cpp
index 942336835..0c5bdb27a 100644
--- a/src/language/ASTNodeDataType.cpp
+++ b/src/language/ASTNodeDataType.cpp
@@ -2,6 +2,7 @@
 
 #include <language/ASTNode.hpp>
 #include <language/PEGGrammar.hpp>
+#include <utils/PugsAssert.hpp>
 
 ASTNodeDataType
 getVectorDataType(const ASTNode& type_node)
@@ -90,11 +91,14 @@ dataTypePromotion(const ASTNodeDataType& data_type_1, const ASTNodeDataType& dat
 bool
 isNaturalConversion(const ASTNodeDataType& data_type, const ASTNodeDataType& target_data_type)
 {
+  Assert((data_type != ASTNodeDataType::undefined_t) and (target_data_type != ASTNodeDataType::undefined_t));
   if (target_data_type == data_type) {
-    if (data_type != ASTNodeDataType::type_id_t) {
-      return true;
-    } else {
+    if (data_type == ASTNodeDataType::type_id_t) {
       return (data_type.typeName() == target_data_type.typeName());
+    } else if (data_type == ASTNodeDataType::vector_t) {
+      return (data_type.dimension() == target_data_type.dimension());
+    } else {
+      return true;
     }
   } else if (target_data_type == ASTNodeDataType::bool_t) {
     return false;
diff --git a/src/language/ASTNodeDataTypeBuilder.cpp b/src/language/ASTNodeDataTypeBuilder.cpp
index a03abf3a9..7ae2758fd 100644
--- a/src/language/ASTNodeDataTypeBuilder.cpp
+++ b/src/language/ASTNodeDataTypeBuilder.cpp
@@ -207,6 +207,7 @@ ASTNodeDataTypeBuilder::_buildNodeDataTypes(ASTNode& n) const
           for (size_t i = 0; i < nb_parameter_domains; ++i) {
             simple_type_allocator(*parameters_domain_node.children[i], *parameters_name_node.children[i]);
           }
+          parameters_name_node.m_data_type = ASTNodeDataType::list_t;
         }
 
         // build types for compound types
@@ -283,6 +284,7 @@ ASTNodeDataTypeBuilder::_buildNodeDataTypes(ASTNode& n) const
         }
 
         n.m_data_type = ASTNodeDataType::void_t;
+        return;
       } else if (n.is_type<language::name>()) {
         std::shared_ptr<SymbolTable>& symbol_table = n.m_symbol_table;
 
@@ -442,9 +444,7 @@ ASTNodeDataTypeBuilder::_buildNodeDataTypes(ASTNode& n) const
                n.is_type<language::vector_type>() or n.is_type<language::type_name_id>()) {
       n.m_data_type = ASTNodeDataType::typename_t;
     } else if (n.is_type<language::name_list>() or n.is_type<language::lvalue_list>() or
-               n.is_type<language::function_argument_list>()) {
-      n.m_data_type = ASTNodeDataType::void_t;
-    } else if (n.is_type<language::expression_list>()) {
+               n.is_type<language::function_argument_list>() or n.is_type<language::expression_list>()) {
       n.m_data_type = ASTNodeDataType::list_t;
     }
   }
diff --git a/src/language/ASTNodeNaturalConversionChecker.cpp b/src/language/ASTNodeNaturalConversionChecker.cpp
index d7027ce1a..e80cd2623 100644
--- a/src/language/ASTNodeNaturalConversionChecker.cpp
+++ b/src/language/ASTNodeNaturalConversionChecker.cpp
@@ -1,6 +1,7 @@
 #include <language/ASTNodeNaturalConversionChecker.hpp>
 
 #include <language/PEGGrammar.hpp>
+#include <utils/Exceptions.hpp>
 
 void
 ASTNodeNaturalConversionChecker::_checkIsNaturalTypeConversion(const ASTNode& node,
@@ -12,7 +13,12 @@ ASTNodeNaturalConversionChecker::_checkIsNaturalTypeConversion(const ASTNode& no
     error_message << "invalid implicit conversion: ";
     error_message << rang::fgB::red << dataTypeName(data_type) << " -> " << dataTypeName(target_data_type)
                   << rang::fg::reset;
-    throw parse_error(error_message.str(), node.begin());
+
+    if ((data_type == ASTNodeDataType::undefined_t) or (target_data_type == ASTNodeDataType::undefined_t)) {
+      throw UnexpectedError(error_message.str());
+    } else {
+      throw parse_error(error_message.str(), node.begin());
+    }
   }
 }
 
diff --git a/src/language/ASTSymbolTableBuilder.cpp b/src/language/ASTSymbolTableBuilder.cpp
index f0ed7ece3..07dc16e33 100644
--- a/src/language/ASTSymbolTableBuilder.cpp
+++ b/src/language/ASTSymbolTableBuilder.cpp
@@ -33,7 +33,7 @@ ASTSymbolTableBuilder::buildSymbolTable(ASTNode& n, std::shared_ptr<SymbolTable>
     }
 
     size_t function_id =
-      symbol_table->functionTable().add(FunctionDescriptor{std::move(n.children[1]), std::move(n.children[2])});
+      symbol_table->functionTable().add(FunctionDescriptor{symbol, std::move(n.children[1]), std::move(n.children[2])});
     i_symbol->attributes().value() = function_id;
     n.children.resize(1);
   } else {
diff --git a/src/language/BuiltinFunctionEmbedder.hpp b/src/language/BuiltinFunctionEmbedder.hpp
index 79eb78e4c..169a1f196 100644
--- a/src/language/BuiltinFunctionEmbedder.hpp
+++ b/src/language/BuiltinFunctionEmbedder.hpp
@@ -5,6 +5,7 @@
 #include <language/DataHandler.hpp>
 #include <language/DataVariant.hpp>
 #include <language/FunctionTable.hpp>
+#include <language/utils/ASTNodeDataTypeTraits.hpp>
 #include <utils/Demangle.hpp>
 #include <utils/Exceptions.hpp>
 #include <utils/PugsTraits.hpp>
@@ -13,22 +14,6 @@
 #include <memory>
 #include <vector>
 
-template <typename T>
-inline ASTNodeDataType ast_node_data_type_from = ASTNodeDataType::undefined_t;
-
-template <>
-inline ASTNodeDataType ast_node_data_type_from<void> = ASTNodeDataType::void_t;
-template <>
-inline ASTNodeDataType ast_node_data_type_from<bool> = ASTNodeDataType::bool_t;
-template <>
-inline ASTNodeDataType ast_node_data_type_from<int64_t> = ASTNodeDataType::int_t;
-template <>
-inline ASTNodeDataType ast_node_data_type_from<uint64_t> = ASTNodeDataType::unsigned_int_t;
-template <>
-inline ASTNodeDataType ast_node_data_type_from<double> = ASTNodeDataType::double_t;
-template <>
-inline ASTNodeDataType ast_node_data_type_from<std::string> = ASTNodeDataType::string_t;
-
 class IBuiltinFunctionEmbedder
 {
  public:
diff --git a/src/language/FunctionTable.hpp b/src/language/FunctionTable.hpp
index a2b7b95af..af262d196 100644
--- a/src/language/FunctionTable.hpp
+++ b/src/language/FunctionTable.hpp
@@ -10,10 +10,18 @@
 
 class FunctionDescriptor
 {
+  std::string m_name;
+
   std::unique_ptr<ASTNode> m_domain_mapping_node;
   std::unique_ptr<ASTNode> m_definition_node;
 
  public:
+  const std::string&
+  name() const
+  {
+    return m_name;
+  }
+
   auto&
   domainMappingNode() const
   {
@@ -30,8 +38,10 @@ class FunctionDescriptor
 
   FunctionDescriptor& operator=(FunctionDescriptor&&) = default;
 
-  FunctionDescriptor(std::unique_ptr<ASTNode>&& domain_mapping_node, std::unique_ptr<ASTNode>&& definition_node)
-    : m_domain_mapping_node(std::move(domain_mapping_node)), m_definition_node(std::move(definition_node))
+  FunctionDescriptor(const std::string& name,
+                     std::unique_ptr<ASTNode>&& domain_mapping_node,
+                     std::unique_ptr<ASTNode>&& definition_node)
+    : m_name{name}, m_domain_mapping_node(std::move(domain_mapping_node)), m_definition_node(std::move(definition_node))
   {}
 
   FunctionDescriptor(FunctionDescriptor&&) = default;
diff --git a/src/language/MeshModule.cpp b/src/language/MeshModule.cpp
index cd987e244..b8e64da51 100644
--- a/src/language/MeshModule.cpp
+++ b/src/language/MeshModule.cpp
@@ -12,14 +12,13 @@
 #include <utils/Exceptions.hpp>
 
 #include <Kokkos_Core.hpp>
+
+#include <array>
 #include <cstdio>
 
 template <>
 inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<IMesh>> = {ASTNodeDataType::type_id_t, "mesh"};
 
-template <>
-inline ASTNodeDataType ast_node_data_type_from<FunctionSymbolId> = {ASTNodeDataType::function_t};
-
 template <typename T>
 class MeshTransformation;
 template <typename OutputType, typename... InputType>
@@ -30,13 +29,16 @@ class MeshTransformation<OutputType(InputType...)> : public PugsFunctionAdapter<
 
  public:
   static inline std::shared_ptr<Mesh<Connectivity<Dimension>>>
-  transform(FunctionSymbolId function_symbol_id, std::shared_ptr<const IMesh> p_mesh)
+  transform(const FunctionSymbolId& function_symbol_id, std::shared_ptr<const IMesh> p_mesh)
   {
     using MeshType             = Mesh<Connectivity<Dimension>>;
     const MeshType& given_mesh = dynamic_cast<const MeshType&>(*p_mesh);
 
-    auto& expression                    = Adapter::getFunctionExpression(function_symbol_id);
-    auto convert_result                 = Adapter::getResultConverter(expression.m_data_type);
+    const auto flatten_args = Adapter::getFlattenArgs(function_symbol_id);
+
+    auto& expression    = Adapter::getFunctionExpression(function_symbol_id);
+    auto convert_result = Adapter::getResultConverter(expression.m_data_type);
+
     Array<ExecutionPolicy> context_list = Adapter::getContextList(expression);
 
     NodeValue<const OutputType> given_xr = given_mesh.xr();
@@ -45,13 +47,14 @@ class MeshTransformation<OutputType(InputType...)> : public PugsFunctionAdapter<
     using execution_space = typename Kokkos::DefaultExecutionSpace::execution_space;
     Kokkos::Experimental::UniqueToken<execution_space, Kokkos::Experimental::UniqueTokenScope::Global> tokens;
 
-    parallel_for(given_mesh.numberOfNodes(), [=, &expression, &tokens](NodeId r) {
+    parallel_for(given_mesh.numberOfNodes(), [=, &expression, &flatten_args, &tokens](NodeId r) {
       const int32_t t = tokens.acquire();
 
       auto& execution_policy = context_list[t];
 
-      Adapter::convertArgs(execution_policy.currentContext(), given_xr[r]);
-      xr[r] = convert_result(expression.execute(execution_policy));
+      Adapter::convertArgs(execution_policy.currentContext(), flatten_args, given_xr[r]);
+      auto result = expression.execute(execution_policy);
+      xr[r]       = convert_result(std::move(result));
 
       tokens.release(t);
     });
@@ -80,7 +83,7 @@ MeshModule::MeshModule()
                                                                      FunctionSymbolId>>(
                               std::function<std::shared_ptr<IMesh>(std::shared_ptr<IMesh>, FunctionSymbolId)>{
                                 [](std::shared_ptr<IMesh> p_mesh,
-                                   FunctionSymbolId function_id) -> std::shared_ptr<IMesh> {
+                                   const FunctionSymbolId& function_id) -> std::shared_ptr<IMesh> {
                                   switch (p_mesh->dimension()) {
                                   case 1: {
                                     using TransformT = TinyVector<1>(TinyVector<1>);
@@ -95,7 +98,7 @@ MeshModule::MeshModule()
                                     return MeshTransformation<TransformT>::transform(function_id, p_mesh);
                                   }
                                   default: {
-                                    throw NormalError("invalid mesh dimension");
+                                    throw UnexpectedError("invalid mesh dimension");
                                   }
                                   }
                                 }}
diff --git a/src/language/PugsFunctionAdapter.hpp b/src/language/PugsFunctionAdapter.hpp
index 262299728..fc2b752cb 100644
--- a/src/language/PugsFunctionAdapter.hpp
+++ b/src/language/PugsFunctionAdapter.hpp
@@ -2,38 +2,177 @@
 #define PUGS_FUNCTION_ADAPTER_HPP
 
 #include <language/ASTNode.hpp>
+#include <language/ASTNodeDataType.hpp>
 #include <language/SymbolTable.hpp>
 #include <language/node_processor/ExecutionPolicy.hpp>
+#include <language/utils/ASTNodeDataTypeTraits.hpp>
+#include <language/utils/RuntimeError.hpp>
 #include <utils/Array.hpp>
 #include <utils/Exceptions.hpp>
 #include <utils/PugsMacros.hpp>
 
 #include <Kokkos_Core.hpp>
 
+#include <array>
+
 template <typename T>
 class PugsFunctionAdapter;
 template <typename OutputType, typename... InputType>
 class PugsFunctionAdapter<OutputType(InputType...)>
 {
+ protected:
+  using FlattenList = std::array<int32_t, sizeof...(InputType)>;
+
  private:
+  template <typename T>
+  PUGS_INLINE static void
+  _flattenArgT(const T&, ExecutionPolicy::Context&, size_t&)
+  {
+    throw UnexpectedError("cannot flatten type " + demangle<T>());
+  }
+
+  template <size_t N>
+  PUGS_INLINE static void
+  _flattenArgT(const TinyVector<N>& t, ExecutionPolicy::Context& context, size_t& i_context)
+  {
+    for (size_t i = 0; i < N; ++i) {
+      context[i_context + i] = t[i];
+    }
+  }
+
   template <typename T, typename... Args>
   PUGS_INLINE static void
-  _convertArgs(const Args&&... args, const T& t, ExecutionPolicy::Context& context)
+  _convertArgs(const T& t,
+               const Args&&... args,
+               ExecutionPolicy::Context& context,
+               const FlattenList& flatten,
+               size_t i_context)
   {
-    context[sizeof...(args)] = t;
+    if (flatten[sizeof...(args)]) {
+      _flattenArgT(t, context, i_context);
+    } else {
+      context[i_context++] = t;
+    }
     if constexpr (sizeof...(args) > 0) {
-      _convertArgs(std::forward<Args>(args)..., context);
+      _convertArgs(std::forward<Args>(args)..., context, flatten, i_context);
     }
   }
 
+  template <typename Arg, typename... RemainingArgs>
+  [[nodiscard]] PUGS_INLINE static bool
+  _checkValidArgumentDataType(const ASTNode& input_expression, FlattenList& flatten_list) noexcept
+  {
+    constexpr const ASTNodeDataType& expected_input_data_type = ast_node_data_type_from<Arg>;
+    const ASTNodeDataType& input_data_type                    = input_expression.m_data_type;
+
+    constexpr size_t i_argument = sizeof...(InputType) - 1 - sizeof...(RemainingArgs);
+    flatten_list[i_argument]    = false;
+
+    if (not isNaturalConversion(expected_input_data_type, input_data_type)) {
+      if ((expected_input_data_type == ASTNodeDataType::vector_t) and (input_data_type == ASTNodeDataType::list_t)) {
+        flatten_list[i_argument] = true;
+        if (expected_input_data_type.dimension() != input_expression.children.size()) {
+          return false;
+        } else {
+          for (const auto& child : input_expression.children) {
+            const ASTNodeDataType& data_type = child->m_data_type;
+            if (not isNaturalConversion(ast_node_data_type_from<double>, data_type)) {
+              return false;
+            }
+          }
+        }
+      } else {
+        return false;
+      }
+    }
+    if constexpr (sizeof...(RemainingArgs) == 0) {
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  [[nodiscard]] PUGS_INLINE static bool
+  _checkValidInputDataType(const ASTNode& input_expression, FlattenList& flatten_list) noexcept
+  {
+    return _checkValidArgumentDataType<InputType...>(input_expression, flatten_list);
+  }
+
+  [[nodiscard]] PUGS_INLINE static bool
+  _checkValidOutputDataType(const ASTNode& return_expression) noexcept
+  {
+    constexpr const ASTNodeDataType& expected_return_data_type = ast_node_data_type_from<OutputType>;
+    const ASTNodeDataType& return_data_type                    = return_expression.m_data_type;
+
+    if (not isNaturalConversion(return_data_type, expected_return_data_type)) {
+      if (expected_return_data_type == ASTNodeDataType::vector_t) {
+        if (return_data_type == ASTNodeDataType::list_t) {
+          if (expected_return_data_type.dimension() != return_expression.children.size()) {
+            return false;
+          } else {
+            for (const auto& child : return_expression.children) {
+              const ASTNodeDataType& data_type = child->m_data_type;
+              if (not isNaturalConversion(data_type, ast_node_data_type_from<double>)) {
+                return false;
+              }
+            }
+          }
+        }
+      }
+    }
+    return true;
+  }
+
+  template <typename Arg, typename... RemainingArgs>
+  [[nodiscard]] PUGS_INLINE static std::string
+  _getCompoundTypeName()
+  {
+    if constexpr (sizeof...(RemainingArgs) > 0) {
+      return dataTypeName(ast_node_data_type_from<Arg>) + _getCompoundTypeName<RemainingArgs...>();
+    } else {
+      return dataTypeName(ast_node_data_type_from<Arg>);
+    }
+  }
+
+  [[nodiscard]] static std::string
+  _getInputDataTypeName()
+  {
+    return _getCompoundTypeName<InputType...>();
+  }
+
  protected:
-  PUGS_INLINE static auto&
-  getFunctionExpression(FunctionSymbolId function_symbol_id)
+  [[nodiscard]] PUGS_INLINE static FlattenList
+  getFlattenArgs(const FunctionSymbolId& function_symbol_id)
+  {
+    auto& function = function_symbol_id.symbolTable().functionTable()[function_symbol_id.id()];
+
+    FlattenList flatten_list;
+
+    bool has_valid_input  = _checkValidInputDataType(*function.definitionNode().children[0], flatten_list);
+    bool has_valid_output = _checkValidOutputDataType(*function.definitionNode().children[1]);
+
+    if (not(has_valid_input and has_valid_output)) {
+      std::ostringstream error_message;
+      error_message << "invalid function type" << rang::style::reset << "\nnote: expecting " << rang::fgB::yellow
+                    << _getInputDataTypeName() << " -> " << dataTypeName(ast_node_data_type_from<OutputType>)
+                    << rang::style::reset << '\n'
+                    << "note: provided function " << rang::fgB::magenta << function.name() << ": "
+                    << function.domainMappingNode().string() << rang::style::reset << std::ends;
+      throw RuntimeError(error_message.str());
+    }
+
+    return flatten_list;
+  }
+
+  [[nodiscard]] PUGS_INLINE static auto&
+  getFunctionExpression(const FunctionSymbolId& function_symbol_id)
   {
-    return *function_symbol_id.symbolTable().functionTable()[function_symbol_id.id()].definitionNode().children[1];
+    auto& function = function_symbol_id.symbolTable().functionTable()[function_symbol_id.id()];
+
+    return *function.definitionNode().children[1];
   }
 
-  PUGS_INLINE static auto
+  [[nodiscard]] PUGS_INLINE static auto
   getContextList(const ASTNode& expression)
   {
     Array<ExecutionPolicy> context_list(Kokkos::DefaultExecutionSpace::impl_thread_pool_size());
@@ -50,14 +189,14 @@ class PugsFunctionAdapter<OutputType(InputType...)>
 
   template <typename... Args>
   PUGS_INLINE static void
-  convertArgs(ExecutionPolicy::Context& context, const Args&... args)
+  convertArgs(ExecutionPolicy::Context& context, const FlattenList& flatten, const Args&... args)
   {
     static_assert(std::is_same_v<std::tuple<InputType...>, std::tuple<Args...>>, "unexpected input type");
-    _convertArgs(args..., context);
+    _convertArgs(args..., context, flatten, 0);
   }
 
-  PUGS_INLINE static std::function<OutputType(DataVariant&& result)>
-  getResultConverter(ASTNodeDataType data_type)
+  [[nodiscard]] PUGS_INLINE static std::function<OutputType(DataVariant&& result)>
+  getResultConverter(const ASTNodeDataType& data_type)
   {
     switch (data_type) {
     case ASTNodeDataType::list_t: {
diff --git a/src/language/node_processor/BuiltinFunctionProcessor.hpp b/src/language/node_processor/BuiltinFunctionProcessor.hpp
index 3a4b48503..a2c32d598 100644
--- a/src/language/node_processor/BuiltinFunctionProcessor.hpp
+++ b/src/language/node_processor/BuiltinFunctionProcessor.hpp
@@ -5,6 +5,7 @@
 #include <language/PEGGrammar.hpp>
 #include <language/node_processor/FunctionArgumentConverter.hpp>
 #include <language/node_processor/INodeProcessor.hpp>
+#include <language/utils/RuntimeError.hpp>
 
 class BuiltinFunctionExpressionProcessor final : public INodeProcessor
 {
@@ -59,8 +60,12 @@ class BuiltinFunctionProcessor : public INodeProcessor
         m_argument_converters[i]->convert(context_exec_policy, std::move(argument_values[i]));
       }
     }
-
-    return m_function_expression_processor->execute(context_exec_policy);
+    try {
+      return m_function_expression_processor->execute(context_exec_policy);
+    }
+    catch (RuntimeError& e) {
+      throw parse_error(e.message(), {m_argument_node.begin()});
+    }
   }
 
   BuiltinFunctionProcessor(ASTNode& argument_node) : m_argument_node{argument_node} {}
diff --git a/src/language/utils/ASTNodeDataTypeTraits.hpp b/src/language/utils/ASTNodeDataTypeTraits.hpp
new file mode 100644
index 000000000..14435925d
--- /dev/null
+++ b/src/language/utils/ASTNodeDataTypeTraits.hpp
@@ -0,0 +1,28 @@
+#ifndef AST_NODE_DATA_TYPE_TRAITS_H
+#define AST_NODE_DATA_TYPE_TRAITS_H
+
+#include <algebra/TinyVector.hpp>
+#include <language/ASTNodeDataType.hpp>
+#include <language/FunctionSymbolId.hpp>
+
+template <typename T>
+inline ASTNodeDataType ast_node_data_type_from = ASTNodeDataType::undefined_t;
+
+template <>
+inline ASTNodeDataType ast_node_data_type_from<void> = ASTNodeDataType::void_t;
+template <>
+inline ASTNodeDataType ast_node_data_type_from<bool> = ASTNodeDataType::bool_t;
+template <>
+inline ASTNodeDataType ast_node_data_type_from<int64_t> = ASTNodeDataType::int_t;
+template <>
+inline ASTNodeDataType ast_node_data_type_from<uint64_t> = ASTNodeDataType::unsigned_int_t;
+template <>
+inline ASTNodeDataType ast_node_data_type_from<double> = ASTNodeDataType::double_t;
+template <>
+inline ASTNodeDataType ast_node_data_type_from<std::string> = ASTNodeDataType::string_t;
+template <>
+inline ASTNodeDataType ast_node_data_type_from<FunctionSymbolId> = ASTNodeDataType::function_t;
+template <size_t N>
+inline ASTNodeDataType ast_node_data_type_from<TinyVector<N>> = {ASTNodeDataType::vector_t, N};
+
+#endif   // AST_NODE_DATA_TYPE_TRAITS_H
diff --git a/src/language/utils/RuntimeError.hpp b/src/language/utils/RuntimeError.hpp
new file mode 100644
index 000000000..b5e2c11a5
--- /dev/null
+++ b/src/language/utils/RuntimeError.hpp
@@ -0,0 +1,27 @@
+#ifndef RUNTIME_ERROR_HPP
+#define RUNTIME_ERROR_HPP
+
+#include <string>
+
+class RuntimeError
+{
+ private:
+  std::string m_message;
+
+ public:
+  const std::string&
+  message() const
+  {
+    return m_message;
+  }
+
+  RuntimeError(const std::string& message) : m_message{message} {}
+
+  RuntimeError()                    = delete;
+  RuntimeError(const RuntimeError&) = delete;
+  RuntimeError(RuntimeError&&)      = delete;
+
+  ~RuntimeError() = default;
+};
+
+#endif   // RUNTIME_ERROR_HPP
diff --git a/src/utils/Exceptions.cpp b/src/utils/Exceptions.cpp
index 85534848f..1bf598b7c 100644
--- a/src/utils/Exceptions.cpp
+++ b/src/utils/Exceptions.cpp
@@ -10,7 +10,7 @@ RawError::RawError(std::string_view error_msg) : IExitError(std::string{error_ms
 NormalError::NormalError(std::string_view error_msg)
   : IExitError([&] {
       std::ostringstream os;
-      os << rang::style::bold << "Error:" << rang::style::reset << ' ' << error_msg;
+      os << rang::style::bold << "error:" << rang::style::reset << ' ' << error_msg;
       return os.str();
     }())
 {}
@@ -18,7 +18,7 @@ NormalError::NormalError(std::string_view error_msg)
 UnexpectedError::UnexpectedError(std::string_view error_msg)
   : IBacktraceError([&] {
       std::ostringstream os;
-      os << rang::fgB::red << "Unexpected error:" << rang::style::reset << ' ' << error_msg;
+      os << rang::fgB::red << "unexpected error:" << rang::style::reset << ' ' << error_msg;
       return os.str();
     }())
 {}
@@ -26,7 +26,7 @@ UnexpectedError::UnexpectedError(std::string_view error_msg)
 NotImplementedError::NotImplementedError(std::string_view error_msg)
   : IBacktraceError([&] {
       std::ostringstream os;
-      os << rang::fgB::yellow << "Not implemented yet:" << rang::style::reset << ' ' << error_msg;
+      os << rang::fgB::yellow << "not implemented yet:" << rang::style::reset << ' ' << error_msg;
       return os.str();
     }())
 {}
diff --git a/tests/test_ASTNodeAffectationExpressionBuilder.cpp b/tests/test_ASTNodeAffectationExpressionBuilder.cpp
index dce1fac55..9f4c26d7f 100644
--- a/tests/test_ASTNodeAffectationExpressionBuilder.cpp
+++ b/tests/test_ASTNodeAffectationExpressionBuilder.cpp
@@ -1123,7 +1123,8 @@ let x : R, x=1; x/=2.3;
 
       ast->children.emplace_back(std::make_unique<ASTNode>());
       ast->children.emplace_back(std::make_unique<ASTNode>());
-      REQUIRE_THROWS_WITH(ASTNodeAffectationExpressionBuilder{*ast}, "invalid implicit conversion: undefined -> Z");
+      REQUIRE_THROWS_WITH(ASTNodeAffectationExpressionBuilder{*ast},
+                          "unexpected error: invalid implicit conversion: undefined -> Z");
     }
 
     SECTION("Invalid string rhs")
@@ -1135,7 +1136,7 @@ let x : R, x=1; x/=2.3;
       ast->children.emplace_back(std::make_unique<ASTNode>());
       ast->children.emplace_back(std::make_unique<ASTNode>());
       REQUIRE_THROWS_WITH(ASTNodeAffectationExpressionBuilder{*ast},
-                          "invalid implicit conversion: undefined -> string");
+                          "unexpected error: invalid implicit conversion: undefined -> string");
     }
 
     SECTION("Invalid string affectation operator")
diff --git a/tests/test_ASTNodeDataTypeBuilder.cpp b/tests/test_ASTNodeDataTypeBuilder.cpp
index 0d5a33060..3f5f361f5 100644
--- a/tests/test_ASTNodeDataTypeBuilder.cpp
+++ b/tests/test_ASTNodeDataTypeBuilder.cpp
@@ -241,7 +241,7 @@ let (x,b,n,s) : R*B*N*string;
       std::string_view result = R"(
 (root:void)
  `-(language::var_declaration:typename)
-     +-(language::name_list:void)
+     +-(language::name_list:list)
      |   +-(language::name:x:R)
      |   +-(language::name:b:B)
      |   +-(language::name:n:N)
@@ -663,7 +663,7 @@ let  diff : R, diff = substract(3,2);
      +-(language::name:diff:R)
      `-(language::function_evaluation:R)
          +-(language::name:substract:function)
-         `-(language::function_argument_list:void)
+         `-(language::function_argument_list:list)
              +-(language::integer:3:Z)
              `-(language::integer:2:Z)
 )";
@@ -758,7 +758,7 @@ let s : string, s = cat("foo", "bar");
      +-(language::name:s:string)
      `-(language::function_evaluation:string)
          +-(language::name:cat:function)
-         `-(language::function_argument_list:void)
+         `-(language::function_argument_list:list)
              +-(language::literal:"foo":string)
              `-(language::literal:"bar":string)
 )";
@@ -778,13 +778,13 @@ let (x,x2) : R*R, (x,x2) = x_x2(3);
  +-(language::fct_declaration:void)
  |   `-(language::name:x_x2:function)
  `-(language::var_declaration:typename)
-     +-(language::name_list:void)
+     +-(language::name_list:list)
      |   +-(language::name:x:R)
      |   `-(language::name:x2:R)
      +-(language::type_expression:typename)
      |   +-(language::R_set:typename)
      |   `-(language::R_set:typename)
-     +-(language::name_list:void)
+     +-(language::name_list:list)
      |   +-(language::name:x:R)
      |   `-(language::name:x2:R)
      `-(language::function_evaluation:typename)
diff --git a/tests/test_FunctionTable.cpp b/tests/test_FunctionTable.cpp
index d056f0727..5c9d26f39 100644
--- a/tests/test_FunctionTable.cpp
+++ b/tests/test_FunctionTable.cpp
@@ -15,7 +15,7 @@ TEST_CASE("FunctionTable", "[language]")
 
     std::unique_ptr definition_node = std::make_unique<ASTNode>();
     definition_node->m_data_type    = ASTNodeDataType::double_t;
-    FunctionDescriptor f{std::move(domain_mapping_node), std::move(definition_node)};
+    FunctionDescriptor f{"f", std::move(domain_mapping_node), std::move(definition_node)};
 
     REQUIRE(f.domainMappingNode().m_data_type == ASTNodeDataType::unsigned_int_t);
     REQUIRE(f.definitionNode().m_data_type == ASTNodeDataType::double_t);
@@ -68,7 +68,8 @@ TEST_CASE("FunctionTable", "[language]")
   std::unique_ptr definition_node = std::make_unique<ASTNode>();
   definition_node->m_data_type    = ASTNodeDataType::double_t;
 
-  size_t function_id = table.add(FunctionDescriptor{std::move(domain_mapping_node), std::move(definition_node)});
+  size_t function_id =
+    table.add(FunctionDescriptor{"function", std::move(domain_mapping_node), std::move(definition_node)});
 
   REQUIRE(domain_mapping_node == nullptr);
   REQUIRE(definition_node == nullptr);
@@ -80,10 +81,12 @@ TEST_CASE("FunctionTable", "[language]")
   REQUIRE(const_table.size() == 1);
 
   auto& f = table[function_id];
+  REQUIRE(f.name() == "function");
   REQUIRE(f.domainMappingNode().m_data_type == ASTNodeDataType::unsigned_int_t);
   REQUIRE(f.definitionNode().m_data_type == ASTNodeDataType::double_t);
 
   const auto& const_f = const_table[function_id];
+  REQUIRE(const_f.name() == "function");
   REQUIRE(const_f.domainMappingNode().m_data_type == ASTNodeDataType::unsigned_int_t);
   REQUIRE(const_f.definitionNode().m_data_type == ASTNodeDataType::double_t);
 
-- 
GitLab