diff --git a/src/language/PugsParser.cpp b/src/language/PugsParser.cpp
index 427e084e87ae89de498558fff98b7c246b1d4a68..ed00e3032b5efddcbfbd21124836f737889041e3 100644
--- a/src/language/PugsParser.cpp
+++ b/src/language/PugsParser.cpp
@@ -14,6 +14,7 @@
 #include <language/ast/ASTSymbolInitializationChecker.hpp>
 #include <language/ast/ASTSymbolTableBuilder.hpp>
 #include <language/utils/ASTDotPrinter.hpp>
+#include <language/utils/ASTExecutionInfo.hpp>
 #include <language/utils/ASTPrinter.hpp>
 #include <language/utils/SymbolTable.hpp>
 #include <utils/PugsAssert.hpp>
@@ -32,8 +33,6 @@
 #include <unordered_map>
 #include <variant>
 
-const std::unique_ptr<ASTNode>* p_root_node = nullptr;
-
 void
 parser(const std::string& filename)
 {
@@ -50,9 +49,8 @@ parser(const std::string& filename)
 
   auto parse_and_execute = [](auto& input) {
     std::unique_ptr<ASTNode> root_node = ASTBuilder::build(input);
-    p_root_node                        = &root_node;
 
-    ASTModulesImporter{*root_node};
+    ASTModulesImporter module_importer{*root_node};
     ASTNodeTypeCleaner<language::import_instruction>{*root_node};
 
     ASTSymbolTableBuilder{*root_node};
@@ -77,11 +75,12 @@ parser(const std::string& filename)
     std::cout << "-------------------------------------------------------\n";
     std::cout << rang::style::bold << "Executing AST..." << rang::style::reset << '\n';
 
+    ASTExecutionInfo execution_info{*root_node, module_importer.moduleRepository()};
+
     ExecutionPolicy exec_all;
     root_node->execute(exec_all);
 
     root_node->m_symbol_table->clearValues();
-    p_root_node = nullptr;
   };
 
   if (not SignalManager::pauseOnError()) {
diff --git a/src/language/ast/ASTModulesImporter.cpp b/src/language/ast/ASTModulesImporter.cpp
index 197f9dcdd33a7e268a550cea12464567ff0f93b5..d52b964a8a670ca3af3d0bcd0f3223798e2502c3 100644
--- a/src/language/ast/ASTModulesImporter.cpp
+++ b/src/language/ast/ASTModulesImporter.cpp
@@ -37,6 +37,8 @@ ASTModulesImporter::_importAllModules(ASTNode& node)
 ASTModulesImporter::ASTModulesImporter(ASTNode& root_node) : m_symbol_table{*root_node.m_symbol_table}
 {
   Assert(root_node.is_root());
+  m_module_repository.populateMandatorySymbolTable(root_node, m_symbol_table);
+
   this->_importAllModules(root_node);
   std::cout << " - loaded modules\n";
 }
diff --git a/src/language/ast/ASTModulesImporter.hpp b/src/language/ast/ASTModulesImporter.hpp
index 2b8c081fdee5ef48dd9594a79ceeca13e52fdedc..566155c4a19ecd00f0b049a29030c0036f212d8a 100644
--- a/src/language/ast/ASTModulesImporter.hpp
+++ b/src/language/ast/ASTModulesImporter.hpp
@@ -20,6 +20,12 @@ class ASTModulesImporter
   void _importAllModules(ASTNode& node);
 
  public:
+  const ModuleRepository&
+  moduleRepository() const
+  {
+    return m_module_repository;
+  }
+
   ASTModulesImporter(ASTNode& root_node);
 
   ASTModulesImporter(const ASTModulesImporter&) = delete;
diff --git a/src/language/ast/ASTNodeDataType.cpp b/src/language/ast/ASTNodeDataType.cpp
index ce2bbcc60182e0fe025d4836ee43fe0cb6d37a59..1f0847f865a6972e7865f4b50601898ab5930616 100644
--- a/src/language/ast/ASTNodeDataType.cpp
+++ b/src/language/ast/ASTNodeDataType.cpp
@@ -84,6 +84,24 @@ dataTypeName(const ASTNodeDataType& data_type)
   return name;
 }
 
+std::string
+dataTypeName(const std::vector<ASTNodeDataType>& data_type_vector)
+{
+  if (data_type_vector.size() == 0) {
+    return dataTypeName(ASTNodeDataType::build<ASTNodeDataType::void_t>());
+  } else if (data_type_vector.size() == 1) {
+    return dataTypeName(data_type_vector[0]);
+  } else {
+    std::ostringstream os;
+    os << '(' << dataTypeName(data_type_vector[0]);
+    for (size_t i = 1; i < data_type_vector.size(); ++i) {
+      os << ',' << dataTypeName(data_type_vector[i]);
+    }
+    os << ')';
+    return os.str();
+  }
+}
+
 ASTNodeDataType
 dataTypePromotion(const ASTNodeDataType& data_type_1, const ASTNodeDataType& data_type_2)
 {
diff --git a/src/language/ast/ASTNodeDataType.hpp b/src/language/ast/ASTNodeDataType.hpp
index 98dfaecf8dc06da4b0b42090fe9ea9bd411ddc50..951aff6e4d070e4747f5eb3f5a5fcc93f8ba5b8e 100644
--- a/src/language/ast/ASTNodeDataType.hpp
+++ b/src/language/ast/ASTNodeDataType.hpp
@@ -14,6 +14,8 @@ class ASTNodeDataType;
 
 ASTNodeDataType getVectorDataType(const ASTNode& type_node);
 
+std::string dataTypeName(const std::vector<ASTNodeDataType>& data_type_vector);
+
 std::string dataTypeName(const ASTNodeDataType& data_type);
 
 ASTNodeDataType dataTypePromotion(const ASTNodeDataType& data_type_1, const ASTNodeDataType& data_type_2);
diff --git a/src/language/modules/BuiltinModule.cpp b/src/language/modules/BuiltinModule.cpp
index 0ab7911394a9ebb81454c3381a380ea4aca9c03e..22d23f8033604698f44ea18e42c63e9ba48ba345 100644
--- a/src/language/modules/BuiltinModule.cpp
+++ b/src/language/modules/BuiltinModule.cpp
@@ -6,6 +6,8 @@
 
 #include <memory>
 
+BuiltinModule::BuiltinModule(bool is_mandatory) : m_is_mandatory{is_mandatory} {}
+
 void
 BuiltinModule::_addBuiltinFunction(const std::string& name,
                                    std::shared_ptr<IBuiltinFunctionEmbedder> builtin_function_embedder)
diff --git a/src/language/modules/BuiltinModule.hpp b/src/language/modules/BuiltinModule.hpp
index bf8743c9e5a9913dd6939e3d6cbe18495bccc27e..6ea3ee56ae6f4b8c3cbfd86ede3370a638d0bff4 100644
--- a/src/language/modules/BuiltinModule.hpp
+++ b/src/language/modules/BuiltinModule.hpp
@@ -18,7 +18,15 @@ class BuiltinModule : public IModule
 
   void _addTypeDescriptor(const ASTNodeDataType& type);
 
+  const bool m_is_mandatory;
+
  public:
+  bool
+  isMandatory() const final
+  {
+    return m_is_mandatory;
+  }
+
   const NameBuiltinFunctionMap&
   getNameBuiltinFunctionMap() const final
   {
@@ -31,7 +39,7 @@ class BuiltinModule : public IModule
     return m_name_type_map;
   }
 
-  BuiltinModule() = default;
+  BuiltinModule(bool is_mandatory = false);
 
   ~BuiltinModule() = default;
 };
diff --git a/src/language/modules/CMakeLists.txt b/src/language/modules/CMakeLists.txt
index cd4c99bd8fb9d8e354706020a4077bf0c8234057..020727478c5e64bac9ec0a780a0cfb80541fb28a 100644
--- a/src/language/modules/CMakeLists.txt
+++ b/src/language/modules/CMakeLists.txt
@@ -2,6 +2,7 @@
 
 add_library(PugsLanguageModules
   BuiltinModule.cpp
+  CoreModule.cpp
   LinearSolverModule.cpp
   MathModule.cpp
   MeshModule.cpp
diff --git a/src/language/modules/CoreModule.cpp b/src/language/modules/CoreModule.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..14a9548238c91682aac53850a6cdf6f36b4d9e93
--- /dev/null
+++ b/src/language/modules/CoreModule.cpp
@@ -0,0 +1,44 @@
+#include <language/modules/CoreModule.hpp>
+
+#include <language/modules/CoreModule.hpp>
+#include <language/modules/ModuleRepository.hpp>
+#include <language/utils/ASTExecutionInfo.hpp>
+#include <language/utils/BuiltinFunctionEmbedder.hpp>
+#include <utils/PugsUtils.hpp>
+
+CoreModule::CoreModule() : BuiltinModule(true)
+{
+  this->_addBuiltinFunction("getPugsVersion", std::make_shared<BuiltinFunctionEmbedder<std::string(void)>>(
+
+                                                []() -> std::string { return pugsVersion(); }
+
+                                                ));
+
+  this->_addBuiltinFunction("getPugsBuildInfo", std::make_shared<BuiltinFunctionEmbedder<std::string(void)>>(
+
+                                                  []() -> std::string { return pugsBuildInfo(); }
+
+                                                  ));
+
+  this->_addBuiltinFunction("getAvailableModules", std::make_shared<BuiltinFunctionEmbedder<std::string()>>(
+
+                                                     []() -> std::string {
+                                                       const ModuleRepository& repository =
+                                                         ASTExecutionInfo::current().moduleRepository();
+
+                                                       return repository.getAvailableModules();
+                                                     }
+
+                                                     ));
+
+  this->_addBuiltinFunction("getModuleInfo", std::make_shared<BuiltinFunctionEmbedder<std::string(const std::string&)>>(
+
+                                               [](const std::string& module_name) -> std::string {
+                                                 const ModuleRepository& repository =
+                                                   ASTExecutionInfo::current().moduleRepository();
+
+                                                 return repository.getModuleInfo(module_name);
+                                               }
+
+                                               ));
+}
diff --git a/src/language/modules/CoreModule.hpp b/src/language/modules/CoreModule.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..963719be2bce1b841fe9cee05db5b957ad53ba2b
--- /dev/null
+++ b/src/language/modules/CoreModule.hpp
@@ -0,0 +1,19 @@
+#ifndef CORE_MODULE_HPP
+#define CORE_MODULE_HPP
+
+#include <language/modules/BuiltinModule.hpp>
+
+class CoreModule : public BuiltinModule
+{
+ public:
+  std::string_view
+  name() const final
+  {
+    return "core";
+  }
+
+  CoreModule();
+  ~CoreModule() = default;
+};
+
+#endif   // CORE_MODULE_HPP
diff --git a/src/language/modules/IModule.hpp b/src/language/modules/IModule.hpp
index b839ed496d8d032f660a6d21c484c7ca342c5f98..48e4b15360de922a83025921d1058095cc205d89 100644
--- a/src/language/modules/IModule.hpp
+++ b/src/language/modules/IModule.hpp
@@ -19,6 +19,8 @@ class IModule
   IModule(IModule&&) = default;
   IModule& operator=(IModule&&) = default;
 
+  virtual bool isMandatory() const = 0;
+
   virtual const NameBuiltinFunctionMap& getNameBuiltinFunctionMap() const = 0;
 
   virtual const NameTypeMap& getNameTypeMap() const = 0;
diff --git a/src/language/modules/ModuleRepository.cpp b/src/language/modules/ModuleRepository.cpp
index 83d44b23ee90423f495144216f5244a9b40db482..abb5d527d4858f86ff3a25e68f4d666c2da8193b 100644
--- a/src/language/modules/ModuleRepository.cpp
+++ b/src/language/modules/ModuleRepository.cpp
@@ -1,6 +1,7 @@
 #include <language/modules/ModuleRepository.hpp>
 
 #include <language/ast/ASTNode.hpp>
+#include <language/modules/CoreModule.hpp>
 #include <language/modules/LinearSolverModule.hpp>
 #include <language/modules/MathModule.hpp>
 #include <language/modules/MeshModule.hpp>
@@ -10,8 +11,11 @@
 #include <language/utils/BuiltinFunctionEmbedder.hpp>
 #include <language/utils/ParseError.hpp>
 #include <language/utils/SymbolTable.hpp>
+#include <language/utils/TypeDescriptor.hpp>
 #include <utils/PugsAssert.hpp>
 
+#include <algorithm>
+
 void
 ModuleRepository::_subscribe(std::unique_ptr<IModule> m)
 {
@@ -21,6 +25,7 @@ ModuleRepository::_subscribe(std::unique_ptr<IModule> m)
 
 ModuleRepository::ModuleRepository()
 {
+  this->_subscribe(std::make_unique<CoreModule>());
   this->_subscribe(std::make_unique<LinearSolverModule>());
   this->_subscribe(std::make_unique<MathModule>());
   this->_subscribe(std::make_unique<MeshModule>());
@@ -31,22 +36,21 @@ ModuleRepository::ModuleRepository()
 
 template <typename NameEmbedderMapT, typename EmbedderTableT>
 void
-ModuleRepository::_populateEmbedderTableT(const ASTNode& module_name_node,
+ModuleRepository::_populateEmbedderTableT(const ASTNode& module_node,
+                                          const std::string& module_name,
                                           const NameEmbedderMapT& name_embedder_map,
                                           const ASTNodeDataType& data_type,
                                           SymbolTable& symbol_table,
                                           EmbedderTableT& embedder_table)
 {
-  const std::string& module_name = module_name_node.string();
-
   for (auto [symbol_name, embedded] : name_embedder_map) {
-    auto [i_symbol, success] = symbol_table.add(symbol_name, module_name_node.begin());
+    auto [i_symbol, success] = symbol_table.add(symbol_name, module_node.begin());
 
     if (not success) {
       std::ostringstream error_message;
       error_message << "importing module '" << module_name << "', cannot add symbol '" << symbol_name
                     << "', it is already defined!";
-      throw ParseError(error_message.str(), module_name_node.begin());
+      throw ParseError(error_message.str(), module_node.begin());
     }
 
     i_symbol->attributes().setDataType(data_type);
@@ -66,14 +70,83 @@ ModuleRepository::populateSymbolTable(const ASTNode& module_name_node, SymbolTab
   if (i_module != m_module_set.end()) {
     const IModule& populating_module = *i_module->second;
 
-    this->_populateEmbedderTableT(module_name_node, populating_module.getNameBuiltinFunctionMap(),
+    this->_populateEmbedderTableT(module_name_node, module_name, populating_module.getNameBuiltinFunctionMap(),
                                   ASTNodeDataType::build<ASTNodeDataType::builtin_function_t>(), symbol_table,
                                   symbol_table.builtinFunctionEmbedderTable());
 
-    this->_populateEmbedderTableT(module_name_node, populating_module.getNameTypeMap(),
+    this->_populateEmbedderTableT(module_name_node, module_name, populating_module.getNameTypeMap(),
                                   ASTNodeDataType::build<ASTNodeDataType::type_name_id_t>(), symbol_table,
                                   symbol_table.typeEmbedderTable());
   } else {
     throw ParseError(std::string{"could not find module "} + module_name, std::vector{module_name_node.begin()});
   }
 }
+
+void
+ModuleRepository::populateMandatorySymbolTable(const ASTNode& root_node, SymbolTable& symbol_table)
+{
+  for (auto&& [module_name, i_module] : m_module_set) {
+    if (i_module->isMandatory()) {
+      this->_populateEmbedderTableT(root_node, module_name, i_module->getNameBuiltinFunctionMap(),
+                                    ASTNodeDataType::build<ASTNodeDataType::builtin_function_t>(), symbol_table,
+                                    symbol_table.builtinFunctionEmbedderTable());
+
+      this->_populateEmbedderTableT(root_node, module_name, i_module->getNameTypeMap(),
+                                    ASTNodeDataType::build<ASTNodeDataType::type_name_id_t>(), symbol_table,
+                                    symbol_table.typeEmbedderTable());
+    }
+  }
+}
+
+std::string
+ModuleRepository::getAvailableModules() const
+{
+  std::stringstream os;
+  os << rang::fgB::yellow << "Available modules" << rang::fg::blue << " [modules tagged with a " << rang::style::reset
+     << rang::style::bold << '*' << rang::style::reset << rang::fg::blue << " are automatically imported]"
+     << rang::style::reset << '\n';
+  for (auto& [name, i_module] : m_module_set) {
+    if (i_module->isMandatory()) {
+      os << rang::style::bold << " *" << rang::style::reset;
+    } else {
+      os << "  ";
+    }
+    os << rang::fgB::green << name << rang::style::reset << '\n';
+  }
+
+  return os.str();
+}
+
+std::string
+ModuleRepository::getModuleInfo(const std::string& module_name) const
+{
+  std::stringstream os;
+  auto i_module = m_module_set.find(module_name);
+  if (i_module != m_module_set.end()) {
+    os << rang::fgB::yellow << "Module '" << rang::fgB::blue << module_name << rang::fgB::yellow << "' provides"
+       << rang::style::reset << '\n';
+    const auto& builtin_function_map = i_module->second->getNameBuiltinFunctionMap();
+    if (builtin_function_map.size() > 0) {
+      os << "  functions\n";
+      for (auto& [name, function] : builtin_function_map) {
+        os << "    " << rang::fgB::green << name << rang::style::reset << ": ";
+        os << dataTypeName(function->getParameterDataTypes());
+        os << rang::fgB::yellow << " -> " << rang::style::reset;
+        os << dataTypeName(function->getReturnDataType()) << '\n';
+      }
+    }
+
+    const auto& builtin_type_map = i_module->second->getNameTypeMap();
+    if (builtin_type_map.size() > 0) {
+      os << "  types\n";
+      for (auto& [name, descriptor] : builtin_type_map) {
+        os << "    " << rang::fgB::green << name << rang::style::reset << '\n';
+      }
+    }
+
+  } else {
+    throw NormalError(std::string{"could not find module "} + module_name);
+  }
+
+  return os.str();
+}
diff --git a/src/language/modules/ModuleRepository.hpp b/src/language/modules/ModuleRepository.hpp
index f928a1552563612ed77434e4295a2d31fb2969c1..c224f6491521ad9757ad5253011604e863a10503 100644
--- a/src/language/modules/ModuleRepository.hpp
+++ b/src/language/modules/ModuleRepository.hpp
@@ -19,7 +19,8 @@ class ModuleRepository
   void _subscribe(std::unique_ptr<IModule> a);
 
   template <typename NameEmbedderMapT, typename EmbedderTableT>
-  void _populateEmbedderTableT(const ASTNode& module_name_node,
+  void _populateEmbedderTableT(const ASTNode& module_node,
+                               const std::string& module_name,
                                const NameEmbedderMapT& name_embedder_map,
                                const ASTNodeDataType& data_type,
                                SymbolTable& symbol_table,
@@ -27,6 +28,10 @@ class ModuleRepository
 
  public:
   void populateSymbolTable(const ASTNode& module_name_node, SymbolTable& symbol_table);
+  void populateMandatorySymbolTable(const ASTNode& root_node, SymbolTable& symbol_table);
+
+  std::string getAvailableModules() const;
+  std::string getModuleInfo(const std::string& module_name) const;
 
   const ModuleRepository& operator=(const ModuleRepository&) = delete;
   const ModuleRepository& operator=(ModuleRepository&&) = delete;
diff --git a/src/language/modules/UtilsModule.cpp b/src/language/modules/UtilsModule.cpp
index 8c688e20768b5499549f88334deab4f2ac85a7a9..8b2271158c1548d05b39c379782abb3cc4754d66 100644
--- a/src/language/modules/UtilsModule.cpp
+++ b/src/language/modules/UtilsModule.cpp
@@ -1,35 +1,22 @@
 #include <language/modules/UtilsModule.hpp>
 
 #include <language/utils/ASTDotPrinter.hpp>
+#include <language/utils/ASTExecutionInfo.hpp>
 #include <language/utils/ASTPrinter.hpp>
 #include <language/utils/BuiltinFunctionEmbedder.hpp>
 #include <language/utils/SymbolTable.hpp>
-#include <utils/PugsUtils.hpp>
 
-extern const std::unique_ptr<ASTNode>* p_root_node;
+#include <fstream>
 
 UtilsModule::UtilsModule()
 {
-  this->_addBuiltinFunction("getPugsVersion", std::make_shared<BuiltinFunctionEmbedder<std::string(void)>>(
-
-                                                []() -> std::string { return pugsVersion(); }
-
-                                                ));
-
-  this->_addBuiltinFunction("getPugsBuildInfo", std::make_shared<BuiltinFunctionEmbedder<std::string(void)>>(
-
-                                                  []() -> std::string { return pugsBuildInfo(); }
-
-                                                  ));
-
   this->_addBuiltinFunction("getAST", std::make_shared<BuiltinFunctionEmbedder<std::string(void)>>(
 
                                         []() -> std::string {
-                                          Assert(p_root_node != nullptr, "unable to find AST's root node");
+                                          const auto& root_node = ASTExecutionInfo::current().rootNode();
 
-                                          const auto& root_node = *p_root_node;
                                           std::ostringstream os;
-                                          os << ASTPrinter{*root_node};
+                                          os << ASTPrinter{root_node};
 
                                           return os.str();
                                         }
@@ -39,9 +26,7 @@ UtilsModule::UtilsModule()
   this->_addBuiltinFunction("saveASTDot", std::make_shared<BuiltinFunctionEmbedder<void(const std::string&)>>(
 
                                             [](const std::string& dot_filename) -> void {
-                                              Assert(p_root_node != nullptr, "unable to find AST's root node");
-
-                                              const auto& root_node = *p_root_node;
+                                              const auto& root_node = ASTExecutionInfo::current().rootNode();
 
                                               std::ofstream fout(dot_filename);
 
@@ -51,7 +36,7 @@ UtilsModule::UtilsModule()
                                                 throw NormalError(os.str());
                                               }
 
-                                              ASTDotPrinter dot_printer{*root_node};
+                                              ASTDotPrinter dot_printer{root_node};
                                               fout << dot_printer;
 
                                               if (not fout) {
diff --git a/src/language/utils/ASTExecutionInfo.cpp b/src/language/utils/ASTExecutionInfo.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6015b6a8f03625d436916224566725eaa7a18e2d
--- /dev/null
+++ b/src/language/utils/ASTExecutionInfo.cpp
@@ -0,0 +1,25 @@
+#include <language/utils/ASTExecutionInfo.hpp>
+
+#include <language/ast/ASTNode.hpp>
+
+const ASTExecutionInfo* ASTExecutionInfo::m_current_execution_info = nullptr;
+
+ASTExecutionInfo::ASTExecutionInfo(const ASTNode& root_node, const ModuleRepository& module_repository)
+  : m_root_node{root_node}, m_module_repository{module_repository}
+{
+  Assert(m_current_execution_info == nullptr, "Can only define one ASTExecutionInfo");
+
+  m_current_execution_info = this;
+}
+
+const ASTExecutionInfo&
+ASTExecutionInfo::current()
+{
+  Assert(m_current_execution_info != nullptr, "ASTExecutionInfo is not defined!");
+  return *m_current_execution_info;
+}
+
+ASTExecutionInfo::~ASTExecutionInfo()
+{
+  m_current_execution_info = nullptr;
+}
diff --git a/src/language/utils/ASTExecutionInfo.hpp b/src/language/utils/ASTExecutionInfo.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..da4b817428c2ea00e8c089e14de2049c4faae4af
--- /dev/null
+++ b/src/language/utils/ASTExecutionInfo.hpp
@@ -0,0 +1,44 @@
+#ifndef AST_EXECUTION_INFO_HPP
+#define AST_EXECUTION_INFO_HPP
+
+#include <string>
+
+class ModuleRepository;
+class ASTNode;
+class ASTExecutionInfo
+{
+ private:
+  static const ASTExecutionInfo* m_current_execution_info;
+
+  const ASTNode& m_root_node;
+
+  const ModuleRepository& m_module_repository;
+
+  // The only place where the ASTExecutionInfo can be built
+  friend void parser(const std::string& filename);
+  // also allowed for testing
+  friend void test_ASTExecutionInfo(const ASTNode&, const ModuleRepository&);
+
+  ASTExecutionInfo(const ASTNode& root_node, const ModuleRepository& module_repository);
+
+ public:
+  const ASTNode&
+  rootNode() const
+  {
+    return m_root_node;
+  }
+
+  const ModuleRepository&
+  moduleRepository() const
+  {
+    return m_module_repository;
+  }
+
+  static const ASTExecutionInfo& current();
+
+  ASTExecutionInfo() = delete;
+
+  ~ASTExecutionInfo();
+};
+
+#endif   // AST_EXECUTION_INFO_HPP
diff --git a/src/language/utils/CMakeLists.txt b/src/language/utils/CMakeLists.txt
index 6a8216a9ca62a440c7eb84111cbb87d26ae6b1d3..1cb991d6c6d19a18579d3c0802a6ea032891d5c2 100644
--- a/src/language/utils/CMakeLists.txt
+++ b/src/language/utils/CMakeLists.txt
@@ -2,6 +2,7 @@
 
 add_library(PugsLanguageUtils
   ASTDotPrinter.cpp
+  ASTExecutionInfo.cpp
   ASTPrinter.cpp
   DataVariant.cpp
   EmbeddedData.cpp
diff --git a/tests/test_ASTModulesImporter.cpp b/tests/test_ASTModulesImporter.cpp
index 42638d918a362516b53a0f05772096b477f943b4..a3a4e3d7a6bd6bfc7d624b9d1ee4a9d69e34b876 100644
--- a/tests/test_ASTModulesImporter.cpp
+++ b/tests/test_ASTModulesImporter.cpp
@@ -4,11 +4,22 @@
 #include <language/ast/ASTModulesImporter.hpp>
 #include <language/ast/ASTNodeExpressionBuilder.hpp>
 #include <language/ast/ASTNodeTypeCleaner.hpp>
+#include <language/utils/ASTExecutionInfo.hpp>
 #include <language/utils/ASTPrinter.hpp>
 #include <language/utils/SymbolTable.hpp>
 
 #include <pegtl/string_input.hpp>
 
+inline void
+test_ASTExecutionInfo(const ASTNode& root_node, const ModuleRepository& module_repository)
+{
+  ASTExecutionInfo execution_info{root_node, module_repository};
+  REQUIRE(&root_node == &execution_info.rootNode());
+  REQUIRE(&module_repository == &execution_info.moduleRepository());
+
+  REQUIRE(&ASTExecutionInfo::current() == &execution_info);
+}
+
 #define CHECK_AST(data, expected_output)                                                            \
   {                                                                                                 \
     static_assert(std::is_same_v<std::decay_t<decltype(data)>, std::string_view>);                  \
@@ -17,10 +28,13 @@
     string_input input{data, "test.pgs"};                                                           \
     auto ast = ASTBuilder::build(input);                                                            \
                                                                                                     \
-    ASTModulesImporter{*ast};                                                                       \
+    ASTModulesImporter importer{*ast};                                                              \
     ASTNodeTypeCleaner<language::import_instruction>{*ast};                                         \
                                                                                                     \
     ASTNodeExpressionBuilder{*ast};                                                                 \
+    const auto& module_repository = importer.moduleRepository();                                    \
+    test_ASTExecutionInfo(*ast, module_repository);                                                 \
+                                                                                                    \
     ExecutionPolicy exec_policy;                                                                    \
     ast->execute(exec_policy);                                                                      \
                                                                                                     \
diff --git a/tests/test_ASTNodeDataType.cpp b/tests/test_ASTNodeDataType.cpp
index 21f5c08bfbe6399b8d5a27ea2c33b56e84f78232..3d4ea9b6954363cdd9007679e425d5f4f4c74c36 100644
--- a/tests/test_ASTNodeDataType.cpp
+++ b/tests/test_ASTNodeDataType.cpp
@@ -31,6 +31,9 @@ TEST_CASE("ASTNodeDataType", "[language]")
 
   const ASTNodeDataType list_dt = ASTNodeDataType::build<ASTNodeDataType::list_t>(type_list);
 
+  const ASTNodeDataType empty_list_dt =
+    ASTNodeDataType::build<ASTNodeDataType::list_t>(std::vector<std::shared_ptr<const ASTNodeDataType>>{});
+
   SECTION("dataTypeName")
   {
     REQUIRE(dataTypeName(undefined_dt) == "undefined");
@@ -44,6 +47,7 @@ TEST_CASE("ASTNodeDataType", "[language]")
     REQUIRE(dataTypeName(function_dt) == "function");
     REQUIRE(dataTypeName(builtin_function_dt) == "builtin_function");
     REQUIRE(dataTypeName(list_dt) == "list(R*Z)");
+    REQUIRE(dataTypeName(empty_list_dt) == "list(void)");
     REQUIRE(dataTypeName(ASTNodeDataType::build<ASTNodeDataType::tuple_t>(bool_dt)) == "tuple(B)");
     REQUIRE(dataTypeName(ASTNodeDataType::build<ASTNodeDataType::tuple_t>(unsigned_int_dt)) == "tuple(N)");
     REQUIRE(dataTypeName(ASTNodeDataType::build<ASTNodeDataType::tuple_t>(int_dt)) == "tuple(Z)");
@@ -57,6 +61,10 @@ TEST_CASE("ASTNodeDataType", "[language]")
     REQUIRE(dataTypeName(ASTNodeDataType::build<ASTNodeDataType::vector_t>(1)) == "R^1");
     REQUIRE(dataTypeName(ASTNodeDataType::build<ASTNodeDataType::vector_t>(2)) == "R^2");
     REQUIRE(dataTypeName(ASTNodeDataType::build<ASTNodeDataType::vector_t>(3)) == "R^3");
+
+    REQUIRE(dataTypeName(std::vector<ASTNodeDataType>{}) == "void");
+    REQUIRE(dataTypeName(std::vector<ASTNodeDataType>{bool_dt}) == "B");
+    REQUIRE(dataTypeName(std::vector<ASTNodeDataType>{bool_dt, unsigned_int_dt}) == "(B,N)");
   }
 
   SECTION("promotion")