diff options
| author | Eugen Wissner <belka@caraus.de> | 2026-10-07 17:33:40 +0200 |
|---|---|---|
| committer | Eugen Wissner <belka@caraus.de> | 2026-10-07 17:45:20 +0200 |
| commit | 12e304fd56f054ba7296ab538eecd4280a67832a (patch) | |
| tree | 218f37dc04fa14323d2ca7704e47a830dcd98b22 /boot | |
| parent | f547dae96d770cc7bc27d3790d0d87bb8e37963d (diff) | |
| download | elna-12e304fd56f054ba7296ab538eecd4280a67832a.tar.gz | |
Allow assigning contants to enumeration members
Diffstat (limited to 'boot')
| -rw-r--r-- | boot/ast.cc | 41 | ||||
| -rw-r--r-- | boot/evaluator.cc | 54 | ||||
| -rw-r--r-- | boot/name_analysis.cc | 98 | ||||
| -rw-r--r-- | boot/parser.yy | 21 | ||||
| -rw-r--r-- | boot/symbol.cc | 15 |
5 files changed, 169 insertions, 60 deletions
diff --git a/boot/ast.cc b/boot/ast.cc index bdc9089..dc643fe 100644 --- a/boot/ast.cc +++ b/boot/ast.cc @@ -400,8 +400,15 @@ namespace elna::boot } } - void walking_visitor::visit(enumeration_type_expression *) + void walking_visitor::visit(enumeration_type_expression *expression) { + for (const enumeration_type_expression::member_declaration& member : expression->members) + { + if (member.second != nullptr) + { + member.second->accept(this); + } + } } void walking_visitor::visit(extern_type_expression *) @@ -816,26 +823,9 @@ namespace elna::boot return this; } - field_initializer::field_initializer(identifier name, expression *value) - : m_name(std::move(name)), m_value(value) - { - } - - field_initializer::~field_initializer() - { - delete this->m_value; - } - - field_initializer::field_initializer(field_initializer&& other) noexcept - : m_name(std::move(other.m_name)), m_value(std::exchange(other.m_value, nullptr)) - { - } - - field_initializer& field_initializer::operator=(field_initializer&& other) noexcept + field_initializer::field_initializer(identifier&& name, expression *value) + : m_name(std::move(name)), m_value(std::unique_ptr<expression>(value)) { - std::swap(this->m_value, other.m_value); - this->m_name = std::move(other.m_name); - return *this; } const std::string& field_initializer::name() const @@ -853,15 +843,6 @@ namespace elna::boot return *this->m_value; } - void field_initializer::value(expression& value) - { - if (this->m_value != &value) - { - delete this->m_value; - this->m_value = &value; - } - } - record_constructor_expression::record_constructor_expression(const source_position position, identifier&& type_name, std::vector<field_initializer>&& field_initializers) @@ -970,7 +951,7 @@ namespace elna::boot } enumeration_type_expression::enumeration_type_expression(const source_position position, - std::vector<identifier>&& members) + std::vector<enumeration_type_expression::member_declaration>&& members) : node(position), members(std::move(members)) { } diff --git a/boot/evaluator.cc b/boot/evaluator.cc index 67fc42a..18633ba 100644 --- a/boot/evaluator.cc +++ b/boot/evaluator.cc @@ -48,6 +48,10 @@ namespace elna::boot return "Array dimensions for type '" + payload.array_type.to_string() + "' should be constant"; } + else if constexpr (std::is_same_v<T, enumeration_value>) + { + return "Enumeration member value must be an integer constant expression"; + } }, this->payload); } @@ -63,6 +67,21 @@ namespace elna::boot } } + /** + * Builds the constant for an enumeration value. + * + * Goes through the bit pattern because \c integer_literal::from imports + * the magnitude of a negative value, not its two's complement. + * + * \param value Member value. + * \param size Size of the enumeration type in bytes. + * \return Signed constant of the given size. + */ + static integer_literal enumeration_constant(const std::ptrdiff_t value, const std::size_t size) + { + return integer_literal::from(static_cast<std::size_t>(value)).cast_to(true, size); + } + std::optional<type_properties> get_type_properties(const type& subject, const target_info& target) { const type resolved = resolve_underlying_type(subject); @@ -330,10 +349,12 @@ namespace elna::boot { return std::nullopt; } - const std::size_t enumeration_position = static_cast<std::size_t>( - std::distance(enumeration->members.begin(), member_iterator)); + const std::ptrdiff_t enumeration_position = + std::distance(enumeration->members.begin(), member_iterator); - return constant_value{ integer_literal::from(enumeration_position + 1U) }; + return constant_value{ enumeration_constant( + enumeration->values.at(static_cast<std::size_t>(enumeration_position)), + this->target.word_properties.front().size) }; } } } @@ -897,21 +918,21 @@ namespace elna::boot } std::optional<constant_value> evaluator::cast_to_enumeration(const constant_value& source_expression, - const std::size_t member_count, const std::size_t target_size) + const enumeration_type& enumeration, const std::size_t target_size) { - std::optional<constant_value> position = cast_to_integer(source_expression, false, target_size); + std::optional<constant_value> value = cast_to_integer(source_expression, true, target_size); - if (!position.has_value()) + if (!value.has_value()) { return std::nullopt; } - const auto *converted = std::get_if<integer_literal>(&position.value()); + const auto *converted = std::get_if<integer_literal>(&value.value()); - if (converted == nullptr || *converted < std::size_t{ 1 } || *converted > member_count) + if (converted == nullptr || *converted < enumeration.minimum() || *converted > enumeration.maximum()) { return std::nullopt; } - return position; + return value; } std::optional<constant_value> evaluator::cast_to_pointer(const constant_value& source_expression) @@ -952,7 +973,7 @@ namespace elna::boot { return std::nullopt; } - return cast_to_enumeration(value.value(), enumeration->members.size(), properties->size); + return cast_to_enumeration(value.value(), *enumeration, properties->size); } if (is_integral_type(target_type)) { @@ -1055,10 +1076,8 @@ namespace elna::boot } if (const auto enumeration = resolved.get<enumeration_type>()) { - return constant_value{ - integer_literal::from<std::size_t>( - this->target.word_properties.front().size, 1U).value() - }; + return constant_value{ enumeration_constant(enumeration->minimum(), + this->target.word_properties.front().size) }; } } else if (subject.name.name() == "max") @@ -1104,11 +1123,8 @@ namespace elna::boot } if (const auto enumeration = resolved.get<enumeration_type>()) { - return constant_value{ - integer_literal::from<std::size_t>( - this->target.word_properties.front().size, - enumeration->members.size()).value() - }; + return constant_value{ enumeration_constant(enumeration->maximum(), + this->target.word_properties.front().size) }; } } else if (subject.name.name() == "offset") diff --git a/boot/name_analysis.cc b/boot/name_analysis.cc index 12a14c3..ff5f6bc 100644 --- a/boot/name_analysis.cc +++ b/boot/name_analysis.cc @@ -17,6 +17,7 @@ along with GCC; see the file COPYING3. If not see #include "elna/boot/name_analysis.h" +#include <limits> #include <map> #include <utility> #include <algorithm> @@ -156,6 +157,9 @@ namespace elna::boot return "Procedure types are always constant, 'const' is not allowed"; case module_entry: return "Program entry point should have no or one argument"; + case enumeration_values: + return "Enumeration values should be omitted, given for the first member only," + " or given for all members"; default: __builtin_unreachable(); } @@ -213,6 +217,10 @@ namespace elna::boot case field_on_type: return "Cannot access field '" + this->name + "' on type '" + this->composite.to_string() + "'"; + case duplicate_value: + return "Enumeration member '" + this->name + "' has a value that is already used"; + case value_out_of_range: + return "Value of the enumeration member '" + this->name + "' does not fit into 'Int'"; default: __builtin_unreachable(); } @@ -657,28 +665,104 @@ namespace elna::boot void resolving_visitor::visit(enumeration_type_expression *expression) { + const auto written = std::ranges::count_if(expression->members, + [](const enumeration_type_expression::member_declaration& member) { + return member.second != nullptr; + }); + const bool first_written = expression->members.front().second != nullptr; + const bool all_written = static_cast<std::size_t>(written) == expression->members.size(); + + if (written > 0 && !all_written && (written != 1 || !first_written)) + { + add_error<declaration_format_error>(expression->position(), + declaration_format_error::kind::enumeration_values); + this->current_type = type(); + return; + } std::vector<std::string> member_names; + std::vector<std::ptrdiff_t> member_values; + std::map<std::ptrdiff_t, std::string> used_values; + bool values_valid = true; + member_names.reserve(expression->members.size()); - for (const auto& member : expression->members) + member_values.reserve(expression->members.size()); + for (const enumeration_type_expression::member_declaration& member : expression->members) { - member_names.emplace_back(member.name()); + std::optional<std::ptrdiff_t> value; + + if (member.second != nullptr) + { + member.second->accept(this); + if (member.second->type_decoration.empty()) + { + // The value expression failed to resolve and reported its own error already. + values_valid = false; + continue; + } + const std::optional<constant_value> evaluated = this->constant_evaluator.evaluate(*member.second); + const integer_literal *literal = evaluated.has_value() + ? std::get_if<integer_literal>(&evaluated.value()) + : nullptr; + + if (literal == nullptr) + { + add_error<non_constant_expression_error>(member.second->position(), + non_constant_expression_error::enumeration_value{}); + values_valid = false; + continue; + } + value = literal->try_to<std::ptrdiff_t>(); + } + else if (member_values.empty()) + { + value = std::ptrdiff_t{ 1 }; + } + else if (member_values.back() < std::numeric_limits<std::ptrdiff_t>::max()) + { + value = member_values.back() + 1; + } + + if (!value.has_value()) + { + add_error<member_error>(member.first.position(), member.first.name(), type(), + member_error::kind::value_out_of_range); + values_valid = false; + continue; + } + const std::map<std::ptrdiff_t, std::string>::const_iterator existing = used_values.find(value.value()); + + if (existing != used_values.end()) + { + add_error<member_error>(member.first.position(), member.first.name(), type(), + member_error::kind::duplicate_value); + values_valid = false; + continue; + } + used_values.insert({ value.value(), member.first.name() }); + member_names.emplace_back(member.first.name()); + member_values.emplace_back(value.value()); + } + if (!values_valid) + { + this->current_type = type(); + return; } std::shared_ptr<enumeration_type> const result_type = std::make_shared<enumeration_type>( - member_names); + member_names, member_values); std::map<std::string, source_position> seen; const type aggregate(result_type); - for (const auto& member : expression->members) + for (const enumeration_type_expression::member_declaration& member : expression->members) { - const std::map<std::string, source_position>::const_iterator existing = seen.find(member.name()); + const std::map<std::string, source_position>::const_iterator existing = seen.find(member.first.name()); if (existing != seen.end()) { - add_error<member_error>(member.position(), member.name(), aggregate, + add_error<member_error>(member.first.position(), member.first.name(), aggregate, member_error::duplicate{ .original = existing->second, .base = std::nullopt }); } else { - seen.insert({ member.name(), member.position() }); + seen.insert({ member.first.name(), member.first.position() }); } } this->current_type = type(result_type); diff --git a/boot/parser.yy b/boot/parser.yy index 04c07a8..e8c3590 100644 --- a/boot/parser.yy +++ b/boot/parser.yy @@ -162,6 +162,8 @@ along with GCC; see the file COPYING3. If not see %type <std::vector<elna::boot::attribute>> attributes; %type <std::vector<std::string>> import_declaration; %type <std::vector<elna::boot::identifier>> required_identifiers optional_identifiers generic_parameters; +%type <std::unique_ptr<elna::boot::enumeration_type_expression::member_declaration>> enumeration_member; +%type <std::vector<elna::boot::enumeration_type_expression::member_declaration>> enumeration_members; %type <elna::boot::named_expression *> name_reference; %type <std::vector<elna::boot::import_declaration *>> import_declarations; %type <elna::boot::array_type_expression *> array_type_expression; @@ -679,9 +681,24 @@ type_expression: } | "proc" procedure_heading { $$ = $2; } - | "(" required_identifiers ")" - { $$ = new boot::enumeration_type_expression(boot::make_position(@$), $2); } + | "(" enumeration_members ")" + { $$ = new boot::enumeration_type_expression(boot::make_position(@$), std::move($2)); } | name_reference { $$ = $1; } +enumeration_members: + enumeration_member "," enumeration_members + { + $$ = std::move($3); + $$.emplace($$.cbegin(), std::move(*$1)); + } + | enumeration_member { $$.emplace_back(std::move(*$1)); } +enumeration_member: + identifier + { $$ = std::make_unique<boot::enumeration_type_expression::member_declaration>(std::move(*$1), nullptr); } + | identifier "=" expression + { + $$ = std::make_unique<boot::enumeration_type_expression::member_declaration>(std::move(*$1), + std::unique_ptr<boot::expression>($3)); + } generic_parameters: %empty {} | "#[" required_identifiers "]" { $$ = $2; } diff --git a/boot/symbol.cc b/boot/symbol.cc index fa423e0..375cbfd 100644 --- a/boot/symbol.cc +++ b/boot/symbol.cc @@ -273,11 +273,22 @@ namespace elna::boot { } - enumeration_type::enumeration_type(const std::vector<std::string>& members) - : members(members) + enumeration_type::enumeration_type(const std::vector<std::string>& members, + const std::vector<std::ptrdiff_t>& values) + : members(members), values(values) { } + std::ptrdiff_t enumeration_type::minimum() const + { + return *std::ranges::min_element(this->values); + } + + std::ptrdiff_t enumeration_type::maximum() const + { + return *std::ranges::max_element(this->values); + } + parameter_type::parameter_type(const std::string& name) : name(name) { |
