diff options
| -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 | ||||
| -rw-r--r-- | gcc/gcc/elna-builtins.cc | 2 | ||||
| -rw-r--r-- | gcc/gcc/elna-tree.cc | 21 | ||||
| -rw-r--r-- | include/elna/boot/ast.h | 21 | ||||
| -rw-r--r-- | include/elna/boot/evaluator.h | 7 | ||||
| -rw-r--r-- | include/elna/boot/name_analysis.h | 5 | ||||
| -rw-r--r-- | include/elna/boot/symbol.h | 9 | ||||
| -rw-r--r-- | include/elna/gcc/elna-tree.h | 2 | ||||
| -rw-r--r-- | testsuite/runnable/enumeration_members.elna | 19 | ||||
| -rw-r--r-- | tools/tree-sitter/grammar.js | 7 | ||||
| -rw-r--r-- | tools/tree-sitter/queries/elna/highlights.scm | 4 |
15 files changed, 234 insertions, 92 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) { diff --git a/gcc/gcc/elna-builtins.cc b/gcc/gcc/elna-builtins.cc index 56950e3..8eb5ce1 100644 --- a/gcc/gcc/elna-builtins.cc +++ b/gcc/gcc/elna-builtins.cc @@ -253,7 +253,7 @@ namespace elna::gcc } else if (const auto reference = type.get<boot::enumeration_type>()) { - return build_enumeration_type(reference->members); + return build_enumeration_type(*reference); } else if (const auto reference = type.get<boot::extern_type>()) { diff --git a/gcc/gcc/elna-tree.cc b/gcc/gcc/elna-tree.cc index b6384f8..0f94864 100644 --- a/gcc/gcc/elna-tree.cc +++ b/gcc/gcc/elna-tree.cc @@ -186,38 +186,39 @@ namespace elna::gcc return build_constructor(slice_type, elements); } - tree build_enumeration_type(const std::vector<std::string>& members) + tree build_enumeration_type(const boot::enumeration_type& enumeration) { tree composite_type_node = make_node(ENUMERAL_TYPE); - tree base_type = elna_word_type_node; + tree base_type = elna_int_type_node; TREE_TYPE(composite_type_node) = base_type; ENUM_IS_SCOPED(composite_type_node) = 1; + // The precision and signedness are needed to build the member + // constants, so they are set before the members. + TYPE_UNSIGNED(composite_type_node) = TYPE_UNSIGNED(base_type); + TYPE_PRECISION(composite_type_node) = TYPE_PRECISION(base_type); tree *members_pointer = &TYPE_VALUES(composite_type_node); - std::size_t order{ 1 }; + std::vector<std::ptrdiff_t>::const_iterator value_iterator = enumeration.values.begin(); - for (const std::string& member : members) + for (const std::string& member : enumeration.members) { tree member_name = get_identifier(member.c_str()); tree member_declaration = build_decl(UNKNOWN_LOCATION, CONST_DECL, member_name, composite_type_node); DECL_CONTEXT(member_declaration) = composite_type_node; - DECL_INITIAL(member_declaration) = build_int_cst_type(composite_type_node, order++); + DECL_INITIAL(member_declaration) = build_int_cst_type(composite_type_node, *value_iterator++); TREE_CONSTANT(member_declaration) = 1; TREE_READONLY(member_declaration) = 1; - TYPE_MAX_VALUE(composite_type_node) = DECL_INITIAL(member_declaration); - *members_pointer = build_tree_list(member_name, member_declaration); members_pointer = &TREE_CHAIN(*members_pointer); } - TYPE_MIN_VALUE(composite_type_node) = DECL_INITIAL(TREE_VALUE(TYPE_VALUES(composite_type_node))); - TYPE_UNSIGNED(composite_type_node) = TYPE_UNSIGNED(base_type); + TYPE_MIN_VALUE(composite_type_node) = build_int_cst_type(composite_type_node, enumeration.minimum()); + TYPE_MAX_VALUE(composite_type_node) = build_int_cst_type(composite_type_node, enumeration.maximum()); // NOLINTNEXTLINE(bugprone-signed-bitwise) SET_TYPE_ALIGN(composite_type_node, TYPE_ALIGN(base_type)); TYPE_SIZE(composite_type_node) = NULL_TREE; - TYPE_PRECISION(composite_type_node) = TYPE_PRECISION(base_type); layout_type(composite_type_node); return composite_type_node; diff --git a/include/elna/boot/ast.h b/include/elna/boot/ast.h index fba8ad4..c3b43bb 100644 --- a/include/elna/boot/ast.h +++ b/include/elna/boot/ast.h @@ -546,23 +546,15 @@ namespace elna::boot struct field_initializer { - field_initializer(identifier name, expression *value); - field_initializer(const field_initializer&) = delete; - field_initializer(field_initializer&& other) noexcept; - - field_initializer& operator=(const field_initializer&) = delete; - field_initializer& operator=(field_initializer&& other) noexcept; - - ~field_initializer(); + field_initializer(identifier&& name, expression *value); const std::string& name() const; const identifier& id() const; expression& value() const; - void value(expression& value); private: identifier m_name; - expression *m_value; + std::unique_ptr<expression> m_value; }; class record_constructor_expression : public expression @@ -606,10 +598,15 @@ namespace elna::boot class enumeration_type_expression : public type_expression { public: - const std::vector<identifier> members; + /** + * Member of an enumeration with an optional explicitly written value. + */ + using member_declaration = std::pair<identifier, std::unique_ptr<expression>>; + + const std::vector<member_declaration> members; enumeration_type_expression(const source_position, - std::vector<identifier>&& members); + std::vector<member_declaration>&& members); void accept(parser_visitor *visitor) override; enumeration_type_expression *is_enumeration() override; diff --git a/include/elna/boot/evaluator.h b/include/elna/boot/evaluator.h index dc6fd71..6dc204f 100644 --- a/include/elna/boot/evaluator.h +++ b/include/elna/boot/evaluator.h @@ -40,7 +40,10 @@ namespace elna::boot { type array_type; }; - using payload_type = std::variant<initializer, case_label, array_dimensions>; + struct enumeration_value + { + }; + using payload_type = std::variant<initializer, case_label, array_dimensions, enumeration_value>; non_constant_expression_error(const source_position position, payload_type payload); @@ -93,7 +96,7 @@ namespace elna::boot static std::optional<constant_value> cast_to_bool(const constant_value& source_expression); static std::optional<constant_value> cast_to_char(const constant_value& source_expression); static std::optional<constant_value> cast_to_enumeration(const constant_value& source_expression, - std::size_t member_count, std::size_t target_size); + const enumeration_type& enumeration, std::size_t target_size); static std::optional<constant_value> cast_to_pointer(const constant_value& source_expression); public: diff --git a/include/elna/boot/name_analysis.h b/include/elna/boot/name_analysis.h index d3f6f10..5cf5d43 100644 --- a/include/elna/boot/name_analysis.h +++ b/include/elna/boot/name_analysis.h @@ -131,7 +131,8 @@ namespace elna::boot array_position, duplicate, constant_procedure, - module_entry + module_entry, + enumeration_values }; struct not_initialized { @@ -154,7 +155,7 @@ namespace elna::boot class member_error final : public diagnostic { public: - enum class kind { not_found, field_on_type }; + enum class kind { not_found, field_on_type, duplicate_value, value_out_of_range }; struct duplicate { std::optional<source_position> original; std::optional<std::string> base; diff --git a/include/elna/boot/symbol.h b/include/elna/boot/symbol.h index 0d07b88..07bcf80 100644 --- a/include/elna/boot/symbol.h +++ b/include/elna/boot/symbol.h @@ -209,8 +209,15 @@ namespace elna::boot struct enumeration_type { std::vector<std::string> members; + /// Value of each member, in the order of \c members. + std::vector<std::ptrdiff_t> values; - explicit enumeration_type(const std::vector<std::string>& members); + enumeration_type(const std::vector<std::string>& members, const std::vector<std::ptrdiff_t>& values); + + /// \return The smallest member value. + std::ptrdiff_t minimum() const; + /// \return The largest member value. + std::ptrdiff_t maximum() const; }; /** diff --git a/include/elna/gcc/elna-tree.h b/include/elna/gcc/elna-tree.h index 6dd3616..c160a9b 100644 --- a/include/elna/gcc/elna-tree.h +++ b/include/elna/gcc/elna-tree.h @@ -49,7 +49,7 @@ namespace elna::gcc tree find_field_by_name(tree type, const std::string& field_name); tree build_static_array_type(tree type, const std::uint64_t size); tree build_slice(tree slice_type, tree ptr, tree length); - tree build_enumeration_type(const std::vector<std::string>& members); + tree build_enumeration_type(const boot::enumeration_type& enumeration); tree extract_constant(tree expression); tree constant_to_tree(const boot::constant_value& value, diff --git a/testsuite/runnable/enumeration_members.elna b/testsuite/runnable/enumeration_members.elna new file mode 100644 index 0000000..b865f98 --- /dev/null +++ b/testsuite/runnable/enumeration_members.elna @@ -0,0 +1,19 @@ +program() +type + E1 = (one, two, three, four) + E2 = (zero = 0, one, two, three) + E3 = (minus = -5, two = 2, ten = 10) + +begin + assert(cast(E1.one: Int) = 1); + assert(cast(E1.two: Int) = 2); + assert(cast(E1.four: Int) = 4); + + assert(cast(E2.zero: Int) = 0); + assert(cast(E2.one: Int) = 1); + assert(cast(E2.three: Int) = 3); + + assert(cast(E3.minus: Int) = -5); + assert(cast(E3.two: Int) = 2); + assert(cast(E3.ten: Int) = 10) +end. diff --git a/tools/tree-sitter/grammar.js b/tools/tree-sitter/grammar.js index a43c947..bfb4c26 100644 --- a/tools/tree-sitter/grammar.js +++ b/tools/tree-sitter/grammar.js @@ -360,10 +360,15 @@ module.exports = grammar({ enum_type: $ => seq( '(', - commaSep1(field('member', $.identifier)), + commaSep1(field('member', $.enum_member)), ')', ), + enum_member: $ => seq( + field('name', $.identifier), + optional(seq('=', field('value', $._expression))), + ), + generic_type: $ => seq( field('name', $.identifier), field('type_arguments', $.type_arguments), diff --git a/tools/tree-sitter/queries/elna/highlights.scm b/tools/tree-sitter/queries/elna/highlights.scm index b5e6d10..4cb3f7c 100644 --- a/tools/tree-sitter/queries/elna/highlights.scm +++ b/tools/tree-sitter/queries/elna/highlights.scm @@ -101,8 +101,8 @@ (traits_expression arguments: (identifier) @type) -(enum_type - member: (identifier) @constant) +(enum_member + name: (identifier) @constant) (procedure_declaration name: (identifier_definition |
