diff --git a/doc/userdoc.org b/doc/userdoc.org
index b50768b072d372e1d462508343aaed9e2acff75b..eaac7da46edb913aff2df4d527d7c9ac20456cfe 100644
--- a/doc/userdoc.org
+++ b/doc/userdoc.org
@@ -2540,11 +2540,16 @@ to files for instance) as we will see below.
 
 **** ~core~ provided functions
 
+***** ~exit: Z -> void~
+
+This function interrupts the execution of the script. The integer (~Z~)
+value is the code that is returned when ~pugs~ exits.
+
 ***** ~getAvailableModules: void -> string~
 
 This function that is used in the preamble of this section is a
 function that returns a ~string~ that contains the list of modules that
-are available in the current version of pugs.
+are available in the current version of ~pugs~.
 
 ***** ~getModuleInfo: string -> string~
 
diff --git a/src/language/PugsParser.cpp b/src/language/PugsParser.cpp
index fbf7b60a21d0ea6aed9e9609d2b4c733a56fbcbc..603367fd66889d62511950d2b42176ada090222b 100644
--- a/src/language/PugsParser.cpp
+++ b/src/language/PugsParser.cpp
@@ -17,9 +17,11 @@
 #include <language/utils/ASTDotPrinter.hpp>
 #include <language/utils/ASTExecutionInfo.hpp>
 #include <language/utils/ASTPrinter.hpp>
+#include <language/utils/Exit.hpp>
 #include <language/utils/OperatorRepository.hpp>
 #include <language/utils/SymbolTable.hpp>
 #include <utils/ConsoleManager.hpp>
+#include <utils/ExecutionStatManager.hpp>
 #include <utils/PugsAssert.hpp>
 #include <utils/PugsUtils.hpp>
 #include <utils/SignalManager.hpp>
@@ -89,8 +91,12 @@ parser(const std::string& filename)
     ASTExecutionInfo execution_info{*root_node, module_importer.moduleRepository()};
 
     ExecutionPolicy exec_all;
-    root_node->execute(exec_all);
-
+    try {
+      root_node->execute(exec_all);
+    }
+    catch (language::Exit& e) {
+      ExecutionStatManager::getInstance().setExitCode(e.code());
+    }
     root_node->m_symbol_table->clearValues();
 
     OperatorRepository::destroy();
diff --git a/src/language/modules/CoreModule.cpp b/src/language/modules/CoreModule.cpp
index ff07fc9da15f52f67a03e632503c4d62bf9af439..e9be93f3c1cc96af47d7665317d5a364cd54f17e 100644
--- a/src/language/modules/CoreModule.cpp
+++ b/src/language/modules/CoreModule.cpp
@@ -19,6 +19,7 @@
 #include <language/utils/BinaryOperatorRegisterForString.hpp>
 #include <language/utils/BinaryOperatorRegisterForZ.hpp>
 #include <language/utils/BuiltinFunctionEmbedder.hpp>
+#include <language/utils/Exit.hpp>
 #include <language/utils/IncDecOperatorRegisterForN.hpp>
 #include <language/utils/IncDecOperatorRegisterForZ.hpp>
 #include <language/utils/OFStream.hpp>
@@ -95,6 +96,21 @@ CoreModule::CoreModule() : BuiltinModule(true)
 
                                           ));
 
+  this->_addBuiltinFunction("exit", std::function(
+
+                                      [](const int64_t& exit_code) -> void {
+                                        const auto& location = ASTBacktrace::getInstance().sourceLocation();
+                                        std::cout << "\n** " << rang::fgB::yellow << "exit" << rang::fg::reset
+                                                  << " explicitly called with code " << rang::fgB::cyan << exit_code
+                                                  << rang::fg::reset << "\n   from " << rang::style::underline
+                                                  << location.filename() << rang::style::reset << ':'
+                                                  << rang::fgB::yellow << location.line() << rang::fg::reset << '\n';
+
+                                        throw language::Exit(exit_code);
+                                      }
+
+                                      ));
+
   this->_addNameValue("cout", ast_node_data_type_from<std::shared_ptr<const OStream>>,
                       EmbeddedData{std::make_shared<DataHandler<const OStream>>(std::make_shared<OStream>(std::cout))});
 
diff --git a/src/language/utils/Exit.hpp b/src/language/utils/Exit.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..ec24402e86c01494f1453b1d6dcbcd7d33300db4
--- /dev/null
+++ b/src/language/utils/Exit.hpp
@@ -0,0 +1,29 @@
+#ifndef EXIT_HPP
+#define EXIT_HPP
+
+namespace language
+{
+
+class Exit
+{
+ private:
+  int m_code = 0;
+
+ public:
+  int
+  code() const
+  {
+    return m_code;
+  }
+
+  Exit(int code) : m_code{code} {}
+
+  Exit(const Exit&) = default;
+  Exit(Exit&&)      = default;
+
+  ~Exit() = default;
+};
+
+}   // namespace language
+
+#endif   // EXIT_HPP
diff --git a/src/main.cpp b/src/main.cpp
index 7ee38e518aba13655318300e2ac53442ad4e8f49..7d0112c80715b3cd0b69ea91b874527134b6a2ed 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -42,7 +42,9 @@ main(int argc, char* argv[])
   finalize();
 
   ParallelChecker::destroy();
+
+  int return_code = ExecutionStatManager::getInstance().exitCode();
   ExecutionStatManager::destroy();
 
-  return 0;
+  return return_code;
 }
diff --git a/src/utils/ExecutionStatManager.hpp b/src/utils/ExecutionStatManager.hpp
index 6f7c333561147ab3fede6c534a9c2680a29bd4cd..3738492a1741c7ee7a4e4a45edf4139b579a022e 100644
--- a/src/utils/ExecutionStatManager.hpp
+++ b/src/utils/ExecutionStatManager.hpp
@@ -11,6 +11,7 @@ class ExecutionStatManager
 
   Timer m_elapse_time;
   bool m_do_print = true;
+  int m_exit_code = 0;
 
   std::string _prettyPrintTime(double seconds) const;
 
@@ -38,6 +39,20 @@ class ExecutionStatManager
     m_do_print = do_print;
   }
 
+  PUGS_INLINE
+  int
+  exitCode() const
+  {
+    return m_exit_code;
+  }
+
+  PUGS_INLINE
+  void
+  setExitCode(int exit_code)
+  {
+    m_exit_code = exit_code;
+  }
+
   PUGS_INLINE
   static ExecutionStatManager&
   getInstance()