aboutsummaryrefslogtreecommitdiff
path: root/boot
diff options
context:
space:
mode:
authorEugen Wissner <belka@caraus.de>2026-10-07 17:33:40 +0200
committerEugen Wissner <belka@caraus.de>2026-10-07 17:45:20 +0200
commit12e304fd56f054ba7296ab538eecd4280a67832a (patch)
tree218f37dc04fa14323d2ca7704e47a830dcd98b22 /boot
parentf547dae96d770cc7bc27d3790d0d87bb8e37963d (diff)
downloadelna-12e304fd56f054ba7296ab538eecd4280a67832a.tar.gz
Allow assigning contants to enumeration members
Diffstat (limited to 'boot')
-rw-r--r--boot/ast.cc41
-rw-r--r--boot/evaluator.cc54
-rw-r--r--boot/name_analysis.cc98
-rw-r--r--boot/parser.yy21
-rw-r--r--boot/symbol.cc15
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)
{