From cdbc1695a93910df5729779de31912a3b4b4172b Mon Sep 17 00:00:00 2001 From: Eugen Wissner Date: Sun, 19 Jul 2026 17:09:40 +0200 Subject: Support unary plus operation --- boot/evaluator.cc | 4 ++ boot/name_analysis.cc | 8 +-- boot/parser.yy | 4 ++ boot/symbol.cc | 27 +++++++++ boot/type_check.cc | 64 ++++++++++++++++++++++ gcc/gcc/elna-generic.cc | 24 ++------ include/elna/boot/ast.h | 3 +- include/elna/boot/symbol.h | 28 ++++++++++ include/elna/boot/type_check.h | 18 ++++++ .../fail_compilation/unary_minus_on_record.elna | 9 +++ .../fail_compilation/unary_negation_on_record.elna | 9 +++ .../fail_compilation/unary_plus_on_record.elna | 9 +++ testsuite/runnable/unary_plus.elna | 8 +++ 13 files changed, 189 insertions(+), 26 deletions(-) create mode 100644 testsuite/fail_compilation/unary_minus_on_record.elna create mode 100644 testsuite/fail_compilation/unary_negation_on_record.elna create mode 100644 testsuite/fail_compilation/unary_plus_on_record.elna create mode 100644 testsuite/runnable/unary_plus.elna diff --git a/boot/evaluator.cc b/boot/evaluator.cc index f782dd6..1acadd3 100644 --- a/boot/evaluator.cc +++ b/boot/evaluator.cc @@ -185,6 +185,10 @@ namespace elna::boot return std::nullopt; }, operand.value()); } + if (subject.operation() == unary_operator::plus) + { + return operand; + } return std::nullopt; } diff --git a/boot/name_analysis.cc b/boot/name_analysis.cc index 5255878..b13c043 100644 --- a/boot/name_analysis.cc +++ b/boot/name_analysis.cc @@ -538,14 +538,8 @@ namespace elna::boot { type resolved = resolve_underlying_type(trait->type_decoration); bool is_enum = resolved.get() != nullptr; - bool is_integral = false; - if (auto prim = resolved.get()) - { - is_integral = prim->identifier == "Int" || prim->identifier == "Word" - || prim->identifier == "Bool" || prim->identifier == "Char"; - } - if (!is_enum && !is_integral) + if (!is_enum && !is_discrete_type(resolved)) { add_error(trait->name, trait->type_decoration); diff --git a/boot/parser.yy b/boot/parser.yy index 1dd42a8..aad5769 100644 --- a/boot/parser.yy +++ b/boot/parser.yy @@ -351,6 +351,10 @@ unary_expression: { $$ = new boot::unary_expression(boot::make_position(@$), $2, boot::unary_operator::minus); } + | "+" operand + { + $$ = new boot::unary_expression(boot::make_position(@$), $2, boot::unary_operator::plus); + } expressions: expression "," expressions { diff --git a/boot/symbol.cc b/boot/symbol.cc index 6ad89b7..7b45612 100644 --- a/boot/symbol.cc +++ b/boot/symbol.cc @@ -420,6 +420,33 @@ namespace elna::boot return false; } + bool is_numeric_type(const type& checked) + { + type resolved = resolve_underlying_type(checked); + + return is_primitive_type(resolved, "Int") + || is_primitive_type(resolved, "Word") + || is_primitive_type(resolved, "Float"); + } + + bool is_integral_type(const type& checked) + { + type resolved = resolve_underlying_type(checked); + + return is_primitive_type(resolved, "Int") + || is_primitive_type(resolved, "Word"); + } + + bool is_discrete_type(const type& checked) + { + type resolved = resolve_underlying_type(checked); + + return is_primitive_type(resolved, "Int") + || is_primitive_type(resolved, "Word") + || is_primitive_type(resolved, "Bool") + || is_primitive_type(resolved, "Char"); + } + bool is_any_pointer_type(const type& checked) { return checked.get() != nullptr diff --git a/boot/type_check.cc b/boot/type_check.cc index b310e34..d9f3071 100644 --- a/boot/type_check.cc +++ b/boot/type_check.cc @@ -200,6 +200,35 @@ namespace elna::boot + "' does not support trait '#" + trait_name + "'"; } + unary_operation_error::unary_operation_error(const source_position position, + type actual, unary_operator op) + : error(position), actual(actual), op(op) + { + } + + char unary_operation_error::unary_operator_symbol(unary_operator op) + { + switch (op) + { + case unary_operator::reference: + return '@'; + case unary_operator::negation: + return '~'; + case unary_operator::minus: + return '-'; + case unary_operator::plus: + return '+'; + } + __builtin_unreachable(); + } + + std::string unary_operation_error::what() const + { + return "Type '" + actual.to_string() + + "' cannot be used with unary '" + + unary_operator_symbol(op) + "'"; + } + /* * Whether the type itself is constant or has a constant member at any * nesting level, so that values of this type cannot be reassigned as a @@ -597,4 +626,39 @@ namespace elna::boot } } } + + void type_analysis_visitor::visit(unary_expression *expression) + { + walking_visitor::visit(expression); + + auto op = expression->operation(); + type resolved = resolve_underlying_type(expression->operand().type_decoration); + + if (op == unary_operator::plus) + { + if (!is_numeric_type(resolved)) + { + add_error(expression->position(), + expression->operand().type_decoration, op); + } + } + else if (op == unary_operator::minus) + { + if (!is_primitive_type(resolved, "Int") + && !is_primitive_type(resolved, "Float")) + { + add_error(expression->position(), + expression->operand().type_decoration, op); + } + } + else if (op == unary_operator::negation) + { + if (!is_primitive_type(resolved, "Bool") + && !is_integral_type(resolved)) + { + add_error(expression->position(), + expression->operand().type_decoration, op); + } + } + } } diff --git a/gcc/gcc/elna-generic.cc b/gcc/gcc/elna-generic.cc index ae04be9..b13fd2f 100644 --- a/gcc/gcc/elna-generic.cc +++ b/gcc/gcc/elna-generic.cc @@ -659,36 +659,24 @@ namespace elna::gcc this->current_expression = build1_loc(location, TRUTH_NOT_EXPR, boolean_type_node, this->current_expression); } - else if (is_integral_type(operand_type)) + else { this->current_expression = build1_loc(location, BIT_NOT_EXPR, operand_type, this->current_expression); } - else - { - error_at(location, "type '%s' cannot be negated", - print_type(operand_type).c_str()); - this->current_expression = error_mark_node; - } break; } case boot::unary_operator::minus: { tree operand_type = TREE_TYPE(this->current_expression); - if (is_integral_type(operand_type)) - { - this->current_expression = fold_build1_loc(location, NEGATE_EXPR, - operand_type, this->current_expression); - } - else - { - error_at(location, "type '%s' cannot be negated", - print_type(operand_type).c_str()); - this->current_expression = error_mark_node; - } + this->current_expression = fold_build1_loc(location, NEGATE_EXPR, + operand_type, this->current_expression); break; } + case boot::unary_operator::plus: + // Identity: operand already in current_expression. + break; } } diff --git a/include/elna/boot/ast.h b/include/elna/boot/ast.h index 9d978a3..30bc7c3 100644 --- a/include/elna/boot/ast.h +++ b/include/elna/boot/ast.h @@ -51,7 +51,8 @@ namespace elna::boot { reference, negation, - minus + minus, + plus }; class variable_declaration; diff --git a/include/elna/boot/symbol.h b/include/elna/boot/symbol.h index 216021e..194c1cd 100644 --- a/include/elna/boot/symbol.h +++ b/include/elna/boot/symbol.h @@ -474,6 +474,34 @@ namespace elna::boot */ bool is_primitive_type(const type& checked, const std::string& name); + /** + * Checks whether the given type is a numeric primitive type + * (\c Int, \c Word, or \c Float). + * + * \param checked The type to check. + * \return Whether the type is a numeric primitive. + */ + bool is_numeric_type(const type& checked); + + /** + * Checks whether the given type is an integral primitive type + * (\c Int or \c Word). + * + * \param checked The type to check. + * \return Whether the type is an integral primitive. + */ + bool is_integral_type(const type& checked); + + /** + * Checks whether the given type is a discrete primitive type + * (\c Int, \c Word, \c Bool, or \c Char), i.e. a type with a + * finite range of ordered values suitable for \c \#min/\c \#max. + * + * \param checked The type to check. + * \return Whether the type is a discrete primitive. + */ + bool is_discrete_type(const type& checked); + /** * Checks whether the given type is a pointer type. * diff --git a/include/elna/boot/type_check.h b/include/elna/boot/type_check.h index 5b6ed1c..8c7dd80 100644 --- a/include/elna/boot/type_check.h +++ b/include/elna/boot/type_check.h @@ -174,6 +174,23 @@ namespace elna::boot std::string what() const override; }; + /** + * A unary operator is applied to an unsupported type. + */ + class unary_operation_error : public error + { + type actual; + unary_operator op; + + static char unary_operator_symbol(unary_operator op); + + public: + unary_operation_error(const source_position position, type actual, + unary_operator op); + + std::string what() const override; + }; + /** * Chain of responsibility for type compatibility checks. * @@ -237,5 +254,6 @@ namespace elna::boot void visit(case_statement *statement) override; void visit(record_constructor_expression *expression) override; void visit(array_constructor_expression *expression) override; + void visit(unary_expression *expression) override; }; } diff --git a/testsuite/fail_compilation/unary_minus_on_record.elna b/testsuite/fail_compilation/unary_minus_on_record.elna new file mode 100644 index 0000000..cfd76a2 --- /dev/null +++ b/testsuite/fail_compilation/unary_minus_on_record.elna @@ -0,0 +1,9 @@ +type + R = record + x: Int + end + +proc f(r: R): Int +return -r (* @Error Type 'R' cannot be used with unary '-' *) + +end. diff --git a/testsuite/fail_compilation/unary_negation_on_record.elna b/testsuite/fail_compilation/unary_negation_on_record.elna new file mode 100644 index 0000000..370f04d --- /dev/null +++ b/testsuite/fail_compilation/unary_negation_on_record.elna @@ -0,0 +1,9 @@ +type + R = record + x: Int + end + +proc f(r: R): Int +return ~r (* @Error Type 'R' cannot be used with unary '~' *) + +end. diff --git a/testsuite/fail_compilation/unary_plus_on_record.elna b/testsuite/fail_compilation/unary_plus_on_record.elna new file mode 100644 index 0000000..7289873 --- /dev/null +++ b/testsuite/fail_compilation/unary_plus_on_record.elna @@ -0,0 +1,9 @@ +type + R = record + x: Int + end + +proc f(r: R): Int +return +r (* @Error Type 'R' cannot be used with unary '\+' *) + +end. diff --git a/testsuite/runnable/unary_plus.elna b/testsuite/runnable/unary_plus.elna new file mode 100644 index 0000000..2bc69f3 --- /dev/null +++ b/testsuite/runnable/unary_plus.elna @@ -0,0 +1,8 @@ +proc f(x: Int): Int +return +x + +begin + assert(+3 = 3); + assert(f(5) = 5); + assert(f(-7) = -7) +end. -- cgit v1.2.3