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