From d2c7bf7b9d9702d06f431a07dbc2d1015c47ee2f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Del=20Pino?= <stephane.delpino44@gmail.com>
Date: Thu, 9 Mar 2023 22:00:56 +0100
Subject: [PATCH] Add support for empty functions

Empty functions are defined the following way for instance
```
let f: void -> R, void -> 3.2;
```
---
 doc/lisp/share/pugs.el                        |   2 +-
 doc/userdoc.org                               |  21 +++-
 src/language/PEGGrammar.hpp                   |  14 ++-
 src/language/ast/ASTBuilder.cpp               |   1 +
 src/language/ast/ASTNodeDataTypeBuilder.cpp   |  33 ++++--
 src/language/ast/ASTNodeExpressionBuilder.cpp |   2 +
 .../ast/ASTNodeFunctionExpressionBuilder.cpp  |  22 ++--
 src/language/ast/ASTSymbolTableBuilder.cpp    |   4 +-
 ...st_ASTNodeAffectationExpressionBuilder.cpp |  40 +++++++
 .../test_ASTNodeFunctionExpressionBuilder.cpp | 112 ++++++++++++++++++
 tests/test_FunctionProcessor.cpp              |  41 +++++++
 11 files changed, 266 insertions(+), 26 deletions(-)

diff --git a/doc/lisp/share/pugs.el b/doc/lisp/share/pugs.el
index 36830ffa5..47b37a03e 100644
--- a/doc/lisp/share/pugs.el
+++ b/doc/lisp/share/pugs.el
@@ -41,7 +41,7 @@ Ask to save the buffer if needed"
   "pugs mode is a major mode for editing pugs files"
   ;; define pugs keywords
   (defvar pugs-keywords
-    '("import" "let" "Z" "N" "B" "R" "string" "and" "or" "xor" "not" "true" "false" "let" "do" "while" "for" "if" "else" "break" "continue" "cout" "cerr" "clog" "+"))
+    '("import" "let" "Z" "N" "B" "R" "string" "and" "or" "xor" "not" "true" "false" "let" "do" "while" "for" "if" "else" "break" "continue" "cout" "cerr" "clog" "void"))
 
   (defvar pugs-special-symbols
     '(":" "," ";" "{" "}" "->" "<=" ">=" "=" "+" "-" "*" "/"  "<" ">" "^"))
diff --git a/doc/userdoc.org b/doc/userdoc.org
index 8ae00fe16..57da51499 100644
--- a/doc/userdoc.org
+++ b/doc/userdoc.org
@@ -2229,6 +2229,17 @@ to ~Xi~. The function themselves are defined by the expressions ~e~ (or
 #+BEGIN_warning
 - ~X~, ~X1~, ..., ~Xn~ cannot be tuples spaces.
 - ~Y~, ~Y1~, ..., ~Ym~ can be tuple spaces.
+
+Also, ~X~ can be the empty space $\varnothing$, but none of ~X1~, ..., ~Xn~
+(for $n>1$) and none of ~Y~, ~Y1~, ..., ~Ym~ can be $\varnothing$. In other
+word one can define functions such as
+\begin{align*}
+    \mbox{let }g:& \varnothing \to Y_1\times \cdots \times Y_m,\\
+                 & \varnothing\mapsto (y_1,\ldots,y_m)=g(\varnothing).
+\end{align*}
+The *keyword* associated to $\varnothing$ is ~void~. Keep in mind that ~void~
+is just a keyword in ~pugs~, it is not a type in the computer science
+point of view (one cannot declare variables of type ~void~).
 #+END_warning
 
 Let us give a few examples.
@@ -2262,7 +2273,6 @@ But one cannot use tuples to define the domain
 produces the following compilation time error
 #+results: no-tuple-in-domain
 
-
 Using compound types as input and output, one can write
 #+NAME: R22-R-string-to-R-string-function
 #+BEGIN_SRC pugs :exports both :results output
@@ -2296,6 +2306,15 @@ The following example shows how to use tuples as codomain.
 #+END_SRC
 #+results: R-to-tuple-R-function
 
+Finally, we give an example of an empty function. Observe the special
+role of the ~void~ keyword
+#+NAME: void-to-tuple-R-function
+#+BEGIN_SRC pugs :exports both :results output
+  let f: void -> (R^2), void -> ([2, 3.5], [6, 7]);
+
+  cout << "f() = " << f() << "\n";
+#+END_SRC
+#+results: void-to-tuple-R-function
 
 **** Lifetime of function arguments
 
diff --git a/src/language/PEGGrammar.hpp b/src/language/PEGGrammar.hpp
index aa6698083..e2aaeec54 100644
--- a/src/language/PEGGrammar.hpp
+++ b/src/language/PEGGrammar.hpp
@@ -73,6 +73,7 @@ struct N_set : TAO_PEGTL_KEYWORD("N"){};
 struct Z_set : TAO_PEGTL_KEYWORD("Z"){};
 struct R_set : TAO_PEGTL_KEYWORD("R"){};
 struct empty_set : TAO_PEGTL_KEYWORD("void"){};
+struct EMPTY_SET : seq< empty_set, ignored>{};
 
 struct string_type : TAO_PEGTL_KEYWORD("string") {};
 
@@ -91,10 +92,9 @@ struct tuple_type_specifier :  sor<try_catch< open_parent, simple_type_specifier
 				   if_must< at< open_parent >, raise< simple_type_specifier >, until< eof > > >{};
 
 struct TYPE_SPECIFIER : seq< sor<simple_type_specifier, tuple_type_specifier>, ignored >{};
-struct EMPTY_SET : seq<empty_set, ignored >{};
 
 struct type_expression : list_must< TYPE_SPECIFIER, seq< ignored, one< '*' >, ignored > >{};
-struct TYPE_EXPRESSION : seq< type_expression, ignored >{};
+struct TYPE_EXPRESSION : seq< sor< empty_set, type_expression >, ignored >{};
 
 struct and_kw : TAO_PEGTL_KEYWORD("and") {};
 struct or_kw : TAO_PEGTL_KEYWORD("or") {};
@@ -131,7 +131,7 @@ struct BREAK : seq < break_kw, ignored > {};
 struct continue_kw : TAO_PEGTL_KEYWORD("continue") {};
 struct CONTINUE : seq < continue_kw, ignored > {};
 
-struct keyword : sor < basic_type, import_kw, true_kw, false_kw, let_kw, do_kw, while_kw, for_kw, if_kw, else_kw, and_kw, or_kw, xor_kw, not_kw, break_kw, continue_kw> {};
+struct keyword : sor < empty_set, basic_type, import_kw, true_kw, false_kw, let_kw, do_kw, while_kw, for_kw, if_kw, else_kw, and_kw, or_kw, xor_kw, not_kw, break_kw, continue_kw> {};
 
 struct identifier_minus_keyword : minus< identifier, keyword > {};
 
@@ -262,10 +262,9 @@ struct type_mapping : seq< TYPE_EXPRESSION, RIGHT_ARROW, TYPE_EXPRESSION >{};
 
 struct name_list : seq< open_parent, list_must< NAME, COMMA >, close_parent >{};
 
-struct function_definition : seq< sor< name_list, NAME >, RIGHT_ARROW, sor< expression_list, expression > >{};
+struct function_definition : seq< sor< EMPTY_SET, name_list, NAME >, RIGHT_ARROW, sor< expression_list, expression > >{};
 
-struct fct_declaration : sor< seq< LET, NAME, COLUMN, type_mapping, COMMA, function_definition >,
-			      seq< LET, EMPTY_SET, COLUMN, type_mapping, COMMA, EMPTY_SET, RIGHT_ARROW, sor< expression_list, expression >  >>{};
+struct fct_declaration : seq< LET, NAME, COLUMN, type_mapping, COMMA, function_definition >{};
 
 struct open_brace : seq< one< '{' >, ignored >{};
 struct close_brace : seq< one< '}' >, ignored >{};
@@ -406,6 +405,9 @@ inline const std::string errors<language::not_at<subscript_expression_of_lvalue>
 template <>
 inline const std::string errors<language::simple_type_specifier>::error_message = "expecting simple type specifier";
 
+template <>
+inline const std::string errors<language::TYPE_SPECIFIER>::error_message = "parse error, expecting type specifier";
+
 // clang-format on
 
 }   // namespace language
diff --git a/src/language/ast/ASTBuilder.cpp b/src/language/ast/ASTBuilder.cpp
index 476c30201..326d4139e 100644
--- a/src/language/ast/ASTBuilder.cpp
+++ b/src/language/ast/ASTBuilder.cpp
@@ -226,6 +226,7 @@ using selector = TAO_PEGTL_NAMESPACE::parse_tree::selector<
                                                      language::N_set,
                                                      language::Z_set,
                                                      language::R_set,
+                                                     language::empty_set,
                                                      language::type_name_id,
                                                      language::tuple_type_specifier,
                                                      language::inner_expression_list,
diff --git a/src/language/ast/ASTNodeDataTypeBuilder.cpp b/src/language/ast/ASTNodeDataTypeBuilder.cpp
index 54697f4a0..b67d81af3 100644
--- a/src/language/ast/ASTNodeDataTypeBuilder.cpp
+++ b/src/language/ast/ASTNodeDataTypeBuilder.cpp
@@ -41,6 +41,8 @@ ASTNodeDataTypeBuilder::_buildDeclarationNodeDataTypes(ASTNode& type_node, ASTNo
       data_type = ASTNodeDataType::build<ASTNodeDataType::unsigned_int_t>();
     } else if (type_node.is_type<language::R_set>()) {
       data_type = ASTNodeDataType::build<ASTNodeDataType::double_t>();
+    } else if (type_node.is_type<language::empty_set>()) {
+      throw ParseError("'void' keyword does not define a type", std::vector{type_node.begin()});
     } else if (type_node.is_type<language::vector_type>()) {
       data_type = getVectorDataType(type_node);
     } else if (type_node.is_type<language::matrix_type>()) {
@@ -217,18 +219,28 @@ ASTNodeDataTypeBuilder::_buildNodeDataTypes(ASTNode& n) const
           i_symbol->attributes().setDataType(data_type);
         };
 
-        if (nb_parameter_domains == 1) {
-          simple_type_allocator(parameters_domain_node, parameters_name_node);
+        if (parameters_domain_node.is_type<language::empty_set>() or
+            parameters_name_node.is_type<language::empty_set>()) {
+          if (not parameters_domain_node.is_type<language::empty_set>()) {
+            std::ostringstream error_msg;
+            throw ParseError("unexpected 'void' keyword", std::vector{parameters_name_node.begin()});
+          } else if (not parameters_name_node.is_type<language::empty_set>()) {
+            throw ParseError("expecting 'void' keyword", std::vector{parameters_name_node.begin()});
+          }
         } else {
-          std::vector<std::shared_ptr<const ASTNodeDataType>> sub_data_type_list;
-          sub_data_type_list.reserve(nb_parameter_domains);
+          if (nb_parameter_domains == 1) {
+            simple_type_allocator(parameters_domain_node, parameters_name_node);
+          } else {
+            std::vector<std::shared_ptr<const ASTNodeDataType>> sub_data_type_list;
+            sub_data_type_list.reserve(nb_parameter_domains);
 
-          for (size_t i = 0; i < nb_parameter_domains; ++i) {
-            simple_type_allocator(*parameters_domain_node.children[i], *parameters_name_node.children[i]);
-            sub_data_type_list.push_back(
-              std::make_shared<const ASTNodeDataType>(parameters_name_node.children[i]->m_data_type));
+            for (size_t i = 0; i < nb_parameter_domains; ++i) {
+              simple_type_allocator(*parameters_domain_node.children[i], *parameters_name_node.children[i]);
+              sub_data_type_list.push_back(
+                std::make_shared<const ASTNodeDataType>(parameters_name_node.children[i]->m_data_type));
+            }
+            parameters_name_node.m_data_type = ASTNodeDataType::build<ASTNodeDataType::list_t>(sub_data_type_list);
           }
-          parameters_name_node.m_data_type = ASTNodeDataType::build<ASTNodeDataType::list_t>(sub_data_type_list);
         }
 
         this->_buildNodeDataTypes(function_descriptor.definitionNode());
@@ -589,6 +601,9 @@ ASTNodeDataTypeBuilder::_buildNodeDataTypes(ASTNode& n) const
     } else if (n.is_type<language::R_set>()) {
       n.m_data_type =
         ASTNodeDataType::build<ASTNodeDataType::typename_t>(ASTNodeDataType::build<ASTNodeDataType::double_t>());
+    } else if (n.is_type<language::empty_set>()) {
+      n.m_data_type =
+        ASTNodeDataType::build<ASTNodeDataType::typename_t>(ASTNodeDataType::build<ASTNodeDataType::void_t>());
     } else if (n.is_type<language::vector_type>()) {
       n.m_data_type = ASTNodeDataType::build<ASTNodeDataType::typename_t>(getVectorDataType(n));
     } else if (n.is_type<language::matrix_type>()) {
diff --git a/src/language/ast/ASTNodeExpressionBuilder.cpp b/src/language/ast/ASTNodeExpressionBuilder.cpp
index 70722aea1..4ce7c9c02 100644
--- a/src/language/ast/ASTNodeExpressionBuilder.cpp
+++ b/src/language/ast/ASTNodeExpressionBuilder.cpp
@@ -182,6 +182,8 @@ ASTNodeExpressionBuilder::_buildExpression(ASTNode& n)
     n.m_node_processor = std::make_unique<BreakProcessor>();
   } else if (n.is_type<language::continue_kw>()) {
     n.m_node_processor = std::make_unique<ContinueProcessor>();
+  } else if (n.is_type<language::empty_set>()) {
+    n.m_node_processor = std::make_unique<FakeProcessor>();
   } else {
     std::ostringstream error_message;
     error_message << "undefined node processor type '" << rang::fgB::red << n.name() << rang::fg::reset << "'";
diff --git a/src/language/ast/ASTNodeFunctionExpressionBuilder.cpp b/src/language/ast/ASTNodeFunctionExpressionBuilder.cpp
index 46ec44e15..7e0d981c3 100644
--- a/src/language/ast/ASTNodeFunctionExpressionBuilder.cpp
+++ b/src/language/ast/ASTNodeFunctionExpressionBuilder.cpp
@@ -301,24 +301,30 @@ ASTNodeFunctionExpressionBuilder::_buildArgumentConverter(FunctionDescriptor& fu
 
   const size_t arguments_number = flattened_datatype_list.size();
 
-  const size_t parameters_number =
-    parameter_variables.is_type<language::name_list>() ? parameter_variables.children.size() : 1;
+  const size_t parameters_number = [&]() -> size_t {
+    if (parameter_variables.is_type<language::name_list>()) {
+      return parameter_variables.children.size();
+    } else if (parameter_variables.is_type<language::empty_set>()) {
+      return 0;
+    } else {
+      return 1;
+    }
+  }();
 
   if (arguments_number != parameters_number) {
     std::ostringstream error_message;
-    error_message << "bad number of arguments: expecting " << rang::fgB::yellow << parameters_number
-                  << rang::style::reset << rang::style::bold << ", provided " << rang::fgB::yellow << arguments_number
-                  << rang::style::reset;
+    error_message << "bad number of arguments: expecting " << rang::fgB::yellow << parameters_number << rang::fg::reset
+                  << ", provided " << rang::fgB::yellow << arguments_number;
     throw ParseError(error_message.str(), argument_nodes.begin());
   }
 
-  if (arguments_number > 1) {
+  if (arguments_number == 1) {
+    this->_storeArgumentConverter(parameter_variables, flattened_datatype_list[0], *function_processor);
+  } else if (arguments_number > 1) {
     for (size_t i = 0; i < arguments_number; ++i) {
       ASTNode& parameter_variable = *parameter_variables.children[i];
       this->_storeArgumentConverter(parameter_variable, flattened_datatype_list[i], *function_processor);
     }
-  } else {
-    this->_storeArgumentConverter(parameter_variables, flattened_datatype_list[0], *function_processor);
   }
 
   return function_processor;
diff --git a/src/language/ast/ASTSymbolTableBuilder.cpp b/src/language/ast/ASTSymbolTableBuilder.cpp
index 87baa5716..0fad55b80 100644
--- a/src/language/ast/ASTSymbolTableBuilder.cpp
+++ b/src/language/ast/ASTSymbolTableBuilder.cpp
@@ -100,7 +100,9 @@ ASTSymbolTableBuilder::buildSymbolTable(ASTNode& n, std::shared_ptr<SymbolTable>
           i_symbol->attributes().setIsInitialized();
         };
 
-        if (n.children[0]->is_type<language::name>()) {
+        if (n.children[0]->is_type<language::empty_set>()) {
+          // nothing to do
+        } else if (n.children[0]->is_type<language::name>()) {
           register_and_initialize_symbol(*n.children[0]);
         } else {   // treats the case of list of parameters
           Assert(n.children[0]->is_type<language::name_list>());
diff --git a/tests/test_ASTNodeAffectationExpressionBuilder.cpp b/tests/test_ASTNodeAffectationExpressionBuilder.cpp
index 36d82b038..f52a33c29 100644
--- a/tests/test_ASTNodeAffectationExpressionBuilder.cpp
+++ b/tests/test_ASTNodeAffectationExpressionBuilder.cpp
@@ -7485,6 +7485,46 @@ let v :(R^3x3), v = bt;
         }
       }
     }
+
+    SECTION("void as a type")
+    {
+      SECTION("declaration")
+      {
+        std::string_view data     = R"(
+let a:void;
+)";
+        std::string error_message = "'void' keyword does not define a type";
+
+        {
+          TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
+          auto ast = ASTBuilder::build(input);
+          OperatorRepository::instance().reset();
+          ASTModulesImporter{*ast};
+
+          ASTSymbolTableBuilder{*ast};
+          REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, error_message);
+        }
+      }
+
+      SECTION("definition")
+      {
+        std::string_view data = R"(
+let a:void, a = 3;
+)";
+
+        std::string error_message = "'void' keyword does not define a type";
+
+        {
+          TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
+          auto ast = ASTBuilder::build(input);
+          OperatorRepository::instance().reset();
+          ASTModulesImporter{*ast};
+
+          ASTSymbolTableBuilder{*ast};
+          REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, error_message);
+        }
+      }
+    }
   }
 }
 
diff --git a/tests/test_ASTNodeFunctionExpressionBuilder.cpp b/tests/test_ASTNodeFunctionExpressionBuilder.cpp
index 1d3ef3909..7d760d222 100644
--- a/tests/test_ASTNodeFunctionExpressionBuilder.cpp
+++ b/tests/test_ASTNodeFunctionExpressionBuilder.cpp
@@ -3952,4 +3952,116 @@ let non_pure : Z -> Z, x -> x + a--;
       }
     }
   }
+
+  SECTION("empty functions")
+  {
+    SECTION("void -> value")
+    {
+      std::string_view data = R"(
+let f:void -> R, void  -> 2.3;
+
+f();
+)";
+
+      std::string_view result = R"(
+(root:ASTNodeListProcessor)
+ `-(language::function_evaluation:FunctionProcessor)
+     +-(language::name:f:NameProcessor)
+     `-(language::function_argument_list:ASTNodeExpressionListProcessor)
+)";
+
+      CHECK_AST(data, result);
+    }
+
+    SECTION("void -> compound")
+    {
+      std::string_view data = R"(
+let f:void -> R*(Z)*R^2, void  -> (2.3, (1,2,3), [1.2, 2.3]);
+
+f();
+)";
+
+      std::string_view result = R"(
+(root:ASTNodeListProcessor)
+ `-(language::function_evaluation:FunctionProcessor)
+     +-(language::name:f:NameProcessor)
+     `-(language::function_argument_list:ASTNodeExpressionListProcessor)
+)";
+
+      CHECK_AST(data, result);
+    }
+
+    SECTION("errors")
+    {
+      SECTION("void as a parameter")
+      {
+        std::string_view data = R"(
+let g:R -> R, void -> 2;
+)";
+
+        std::string error = "unexpected 'void' keyword";
+
+        TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
+        auto ast = ASTBuilder::build(input);
+
+        ASTModulesImporter{*ast};
+        ASTNodeTypeCleaner<language::import_instruction>{*ast};
+
+        ASTSymbolTableBuilder{*ast};
+
+        REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, error);
+      }
+
+      SECTION("void as a parameter")
+      {
+        std::string_view data = R"(
+let g:void -> R, x -> 2;
+)";
+
+        std::string error = "expecting 'void' keyword";
+
+        TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
+        auto ast = ASTBuilder::build(input);
+
+        ASTModulesImporter{*ast};
+        ASTNodeTypeCleaner<language::import_instruction>{*ast};
+
+        ASTSymbolTableBuilder{*ast};
+
+        REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, error);
+      }
+
+      SECTION("void in compound domain")
+      {
+        std::string_view data = R"(
+let h:R*void -> R, (x,void) -> 2.5;
+)";
+
+        std::string error = "parse error, expecting type specifier";
+
+        TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
+        // auto ast = ASTBuilder::build(input);
+
+        // ASTModulesImporter{*ast};
+        // ASTNodeTypeCleaner<language::import_instruction>{*ast};
+
+        // ASTSymbolTableBuilder{*ast};
+        // ASTNodeDataTypeBuilder{*ast};
+
+        REQUIRE_THROWS_WITH(ASTBuilder::build(input), error);
+      }
+
+      SECTION("void in compound codomain")
+      {
+        std::string_view data = R"(
+let h:R*void -> R, (x,void) -> 2.5;
+)";
+        std::string error     = "parse error, expecting type specifier";
+
+        TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
+
+        REQUIRE_THROWS_WITH(ASTBuilder::build(input), error);
+      }
+    }
+  }
 }
diff --git a/tests/test_FunctionProcessor.cpp b/tests/test_FunctionProcessor.cpp
index 649cfe1af..500998b50 100644
--- a/tests/test_FunctionProcessor.cpp
+++ b/tests/test_FunctionProcessor.cpp
@@ -1817,6 +1817,47 @@ let t :(string), t = f(false);
         CHECK_FUNCTION_EVALUATION_RESULT(data, "t", result);
       }
     }
+
+    SECTION("empty functions")
+    {
+      SECTION("to value")
+      {
+        std::string_view data = R"(
+let f : void -> R^3, void -> [1,2,3];
+let x :R^3, x = f();
+)";
+
+        CHECK_FUNCTION_EVALUATION_RESULT(data, "x", (TinyVector<3>{1, 2, 3}));
+      }
+
+      SECTION("to tuple")
+      {
+        std::string_view data = R"(
+let n : N, n = 3;
+let f : void -> (string), void -> (false, n, -12, 2.3, "foo");
+n+=5;
+let t :(string), t = f();
+)";
+
+        std::vector<std::string> result{stringify(
+          std::vector<std::string>{stringify(false), stringify(8ul), stringify(-12l), stringify(2.3), "foo"})};
+        CHECK_FUNCTION_EVALUATION_RESULT(data, "t", result);
+      }
+
+      SECTION("to compound")
+      {
+        std::string_view data = R"(
+let n : N, n = 3;
+let f : void -> N*(R)*R^2, void -> (n, (1,2,3), [-12, 2.3]);
+n+=5;
+let (m,t,x):N*(R)*R^2, (m,t,x) = f();
+)";
+
+        CHECK_FUNCTION_EVALUATION_RESULT(data, "m", 8ul);
+        CHECK_FUNCTION_EVALUATION_RESULT(data, "t", (std::vector<double>{1, 2, 3}));
+        CHECK_FUNCTION_EVALUATION_RESULT(data, "x", (TinyVector<2>{-12, 2.3}));
+      }
+    }
   }
 
   SECTION("errors")
-- 
GitLab