aboutsummaryrefslogtreecommitdiff
path: root/boot
diff options
context:
space:
mode:
authorEugen Wissner <belka@caraus.de>2026-07-26 19:47:59 +0200
committerEugen Wissner <belka@caraus.de>2026-07-26 19:47:59 +0200
commit5cdaceb77af6a1d98145f8afb34ce6884e21a3d6 (patch)
tree1f75fa1aeee67a72017dbaa2fb006e93c943700d /boot
parentdea1c177cd3592cc24fd15ad446a676da2e4da28 (diff)
downloadelna-5cdaceb77af6a1d98145f8afb34ce6884e21a3d6.tar.gz
Type check traits properly
Diffstat (limited to 'boot')
-rw-r--r--boot/ast.cc53
-rw-r--r--boot/dependency.cc9
-rw-r--r--boot/evaluator.cc340
-rw-r--r--boot/type_check.cc68
4 files changed, 435 insertions, 35 deletions
diff --git a/boot/ast.cc b/boot/ast.cc
index 69d72fb..a2f108a 100644
--- a/boot/ast.cc
+++ b/boot/ast.cc
@@ -800,6 +800,15 @@ 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)
@@ -1255,16 +1264,34 @@ namespace elna::boot
return this;
}
- expression& binary_expression::lhs()
+ expression& binary_expression::lhs() const
{
return *m_lhs;
}
- expression& binary_expression::rhs()
+ void binary_expression::lhs(expression& lhs)
+ {
+ if (this->m_lhs != &lhs)
+ {
+ delete this->m_lhs;
+ this->m_lhs = &lhs;
+ }
+ }
+
+ expression& binary_expression::rhs() const
{
return *m_rhs;
}
+ void binary_expression::rhs(expression& rhs)
+ {
+ if (this->m_rhs != &rhs)
+ {
+ delete this->m_rhs;
+ this->m_rhs = &rhs;
+ }
+ }
+
binary_operator binary_expression::operation() const
{
return m_operator;
@@ -1297,11 +1324,20 @@ namespace elna::boot
return this;
}
- expression& unary_expression::operand()
+ expression& unary_expression::operand() const
{
return *m_operand;
}
+ void unary_expression::operand(expression& operand)
+ {
+ if (this->m_operand != &operand)
+ {
+ delete this->m_operand;
+ this->m_operand = &operand;
+ }
+ }
+
unary_operator unary_expression::operation() const
{
return this->m_operator;
@@ -1367,11 +1403,20 @@ namespace elna::boot
return *m_target;
}
- expression& cast_expression::value()
+ expression& cast_expression::value() const
{
return *m_value;
}
+ void cast_expression::value(expression& value)
+ {
+ if (this->m_value != &value)
+ {
+ delete this->m_value;
+ this->m_value = &value;
+ }
+ }
+
cast_expression::~cast_expression()
{
delete m_target;
diff --git a/boot/dependency.cc b/boot/dependency.cc
index dc827fa..63d3952 100644
--- a/boot/dependency.cc
+++ b/boot/dependency.cc
@@ -56,7 +56,7 @@ namespace elna::boot
return outcome;
}
- error_list analyze_semantics(std::unique_ptr<unit>& tree, const symbol_bag& bag,
+ error_list analyze_semantics(std::unique_ptr<unit>& tree, symbol_bag& bag,
const target_info& target)
{
name_analysis_visitor name_analyser(bag);
@@ -73,6 +73,13 @@ namespace elna::boot
{
return std::move(type_analyzer.errors());
}
+ constant_folder folder(bag, target);
+ tree->accept(&folder);
+
+ if (folder.has_errors())
+ {
+ return std::move(folder.errors());
+ }
return error_list{};
}
diff --git a/boot/evaluator.cc b/boot/evaluator.cc
index bb50bd3..6d3153e 100644
--- a/boot/evaluator.cc
+++ b/boot/evaluator.cc
@@ -19,11 +19,22 @@ along with GCC; see the file COPYING3. If not see
#include "elna/boot/ast.h"
#include <algorithm>
+#include <cstddef>
#include <limits>
#include <ranges>
namespace elna::boot
{
+ non_constant_initializer_error::non_constant_initializer_error(const source_position position)
+ : error(position)
+ {
+ }
+
+ std::string non_constant_initializer_error::what() const
+ {
+ return "Variable initializers must be constant expressions";
+ }
+
std::optional<type_properties> get_type_properties(const type& subject, const target_info& target)
{
auto resolved = resolve_underlying_type(subject);
@@ -127,9 +138,8 @@ namespace elna::boot
};
}
- evaluator::evaluator(symbol_bag& bag, const target_info& target,
- const std::map<std::string, expression*>& evaluated_initializers)
- : bag(bag), target(target), evaluated_initializers(evaluated_initializers)
+ evaluator::evaluator(symbol_bag& bag, const target_info& target)
+ : bag(bag), target(target)
{
}
@@ -145,6 +155,18 @@ namespace elna::boot
{
return evaluate_named(*named);
}
+ if (auto *array_access = designator->is_array_access())
+ {
+ return evaluate_array_access(*array_access);
+ }
+ if (auto *field_access = designator->is_field_access())
+ {
+ return evaluate_field_access(*field_access);
+ }
+ if (auto *slicing = designator->is_slicing())
+ {
+ return evaluate_slicing(*slicing);
+ }
}
else if (auto *unary = subject.is_unary())
{
@@ -240,20 +262,109 @@ namespace elna::boot
{
return std::nullopt;
}
- auto initializer = this->evaluated_initializers.find(subject.name);
+ return variable->value;
+ }
+
+ std::optional<constant_value> evaluator::evaluate_array_access(array_access_expression& subject)
+ {
+ auto base = evaluate(subject.base());
+ auto index = evaluate(subject.index());
+ if (!base.has_value() || !index.has_value())
+ {
+ return std::nullopt;
+ }
+ auto *array = std::get_if<constant_aggregate<std::vector>>(&base.value());
+ if (array == nullptr)
+ {
+ return std::nullopt;
+ }
+ std::size_t position;
+ if (auto *int_index = std::get_if<std::int32_t>(&index.value()))
+ {
+ if (*int_index < 0)
+ {
+ return std::nullopt;
+ }
+ position = static_cast<std::size_t>(*int_index);
+ }
+ else if (auto *word_index = std::get_if<std::uint32_t>(&index.value()))
+ {
+ position = *word_index;
+ }
+ else
+ {
+ return std::nullopt;
+ }
+ if (position >= (*array)->size())
+ {
+ return std::nullopt;
+ }
+ return (*array)->at(position);
+ }
+
+ std::optional<constant_value> evaluator::evaluate_field_access(field_access_expression& subject)
+ {
+ auto base = evaluate(subject.base());
+ if (!base.has_value())
+ {
+ return std::nullopt;
+ }
+ auto *record = std::get_if<constant_aggregate<ordered_map>>(&base.value());
+ if (record == nullptr)
+ {
+ return std::nullopt;
+ }
+ auto pos = (*record)->find(subject.field().name());
+ if (pos == (*record)->end())
+ {
+ return std::nullopt;
+ }
+ return pos->second;
+ }
- return initializer == this->evaluated_initializers.end()
- ? std::nullopt
- : evaluate(*initializer->second);
+ std::optional<constant_value> evaluator::evaluate_slicing(slicing_expression& subject)
+ {
+ auto base = evaluate(subject.base());
+ auto start = evaluate(subject.start());
+ auto end = evaluate(subject.end());
+ if (!base.has_value() || !start.has_value() || !end.has_value())
+ {
+ return std::nullopt;
+ }
+ auto *array = std::get_if<constant_aggregate<std::vector>>(&base.value());
+ auto *start_idx = std::get_if<std::int32_t>(&start.value());
+ auto *end_idx = std::get_if<std::int32_t>(&end.value());
+ if (array == nullptr || start_idx == nullptr || end_idx == nullptr
+ || *start_idx < 0 || *end_idx < 0)
+ {
+ return std::nullopt;
+ }
+ auto start_pos = static_cast<std::size_t>(*start_idx);
+ auto end_pos = static_cast<std::size_t>(*end_idx);
+ if (start_pos > end_pos || end_pos > (*array)->size())
+ {
+ return std::nullopt;
+ }
+ auto slice_begin = std::next((*array)->begin(), static_cast<std::ptrdiff_t>(start_pos));
+ auto slice_end = std::next((*array)->begin(), static_cast<std::ptrdiff_t>(end_pos));
+
+ return constant_value{
+ constant_aggregate<std::vector>{ std::vector<constant_value>(slice_begin, slice_end) }
+ };
}
std::optional<constant_value> evaluator::evaluate_unary(unary_expression& subject)
{
if (subject.operation() == unary_operator::reference)
{
- // The address of a module-level entity is a compile-time
- // constant. The caller (type analysis) validates the context.
- return constant_value{ std::nullptr_t{} };
+ if (auto *designator = subject.operand().is_designator())
+ {
+ if (auto *named = designator->is_named())
+ {
+ return constant_value{ global_address{ .name = named->name } };
+ }
+ }
+ return std::nullopt;
}
auto operand = evaluate(subject.operand());
@@ -367,6 +478,19 @@ namespace elna::boot
return std::nullopt;
}
}
+ else if constexpr (std::is_same_v<T, global_address>)
+ {
+ switch (operation)
+ {
+ using enum binary_operator;
+ case equals:
+ return constant_value{ lhs == rhs };
+ case not_equals:
+ return constant_value{ lhs != rhs };
+ default:
+ return std::nullopt;
+ }
+ }
else
{
switch (operation)
@@ -645,4 +769,200 @@ namespace elna::boot
}
return std::nullopt;
}
+
+ /**
+ * Converts a constant_value to an AST literal expression.
+ *
+ * \param value Evaluated constant to convert.
+ * \param position Source position for the new literal node.
+ * \param decoration Type decoration to apply to the literal.
+ * \return A new literal expression, or \c nullptr if \p value
+ * is an aggregate (handled separately by the caller).
+ */
+ static expression *value_to_expression(const constant_value& value,
+ const source_position& position, const type& decoration)
+ {
+ return std::visit([&position, &decoration](auto&& value) -> expression*
+ {
+ using T = std::decay_t<decltype(value)>;
+
+ if constexpr (std::is_same_v<T, std::int32_t>)
+ {
+ auto *lit = new literal<std::int32_t>(position, value);
+ lit->type_decoration = decoration;
+ return lit;
+ }
+ else if constexpr (std::is_same_v<T, std::uint32_t>)
+ {
+ auto *lit = new literal<std::uint32_t>(position, value);
+ lit->type_decoration = decoration;
+ return lit;
+ }
+ else if constexpr (std::is_same_v<T, double>)
+ {
+ auto *lit = new literal<double>(position, value);
+ lit->type_decoration = decoration;
+ return lit;
+ }
+ else if constexpr (std::is_same_v<T, bool>)
+ {
+ auto *lit = new literal<bool>(position, value);
+ lit->type_decoration = decoration;
+ return lit;
+ }
+ else if constexpr (std::is_same_v<T, unsigned char>)
+ {
+ auto *lit = new literal<unsigned char>(position, value);
+ lit->type_decoration = decoration;
+ return lit;
+ }
+ else if constexpr (std::is_same_v<T, std::nullptr_t>
+ || std::is_same_v<T, global_address>)
+ {
+ auto *lit = new literal<std::nullptr_t>(position, nullptr);
+ lit->type_decoration = decoration;
+ return lit;
+ }
+ else
+ {
+ return nullptr;
+ }
+ }, value);
+ }
+
+ expression *evaluator::fold(expression& original)
+ {
+ auto value = evaluate(original);
+ if (!value.has_value())
+ {
+ return &original;
+ }
+ if (expression *literal = value_to_expression(value.value(), original.position(), original.type_decoration))
+ {
+ delete &original;
+ return literal;
+ }
+ if (auto *record = original.is_record_constructor())
+ {
+ for (auto& field_init : record->field_initializers)
+ {
+ fold_aggregate_field(field_init);
+ }
+ }
+ else if (auto *array = original.is_array_constructor())
+ {
+ for (auto& element : array->elements)
+ {
+ element = fold(*element);
+ }
+ }
+ return &original;
+ }
+
+ void evaluator::fold_aggregate_field(field_initializer& field_init)
+ {
+ auto value = evaluate(field_init.value());
+ if (!value.has_value())
+ {
+ return;
+ }
+ expression *literal = value_to_expression(value.value(),
+ field_init.value().position(), field_init.value().type_decoration);
+ if (literal != nullptr)
+ {
+ field_init.value(*literal);
+ return;
+ }
+ expression *folded = fold(field_init.value());
+ if (folded != &field_init.value())
+ {
+ field_init.value(*folded);
+ }
+ }
+
+ constant_folder::constant_folder(symbol_bag& bag, const target_info& target)
+ : bag(bag), target(target), constant_evaluator(this->bag, this->target)
+ {
+ }
+
+ void constant_folder::visit(variable_declaration* declaration)
+ {
+ if (declaration->initializer == nullptr || has_errors())
+ {
+ return;
+ }
+ auto computed = this->constant_evaluator.evaluate(*declaration->initializer);
+ if (!computed)
+ {
+ add_error<non_constant_initializer_error>(declaration->initializer->position());
+ return;
+ }
+ declaration->initializer = this->constant_evaluator.fold(*declaration->initializer);
+
+ for (const auto& identifier : declaration->identifiers)
+ {
+ auto symbol = this->bag.lookup(identifier.name());
+ if (symbol == nullptr)
+ {
+ continue;
+ }
+ if (auto var = symbol->is_variable();
+ resolve_aliases(var->symbol).get<constant_type>() != nullptr)
+ {
+ var->value = computed;
+ }
+ }
+ }
+
+ void constant_folder::visit(cast_expression *expr)
+ {
+ expr->target().accept(this);
+ expr->value().accept(this);
+ expr->value(fold_trait(expr->value()));
+ }
+
+ void constant_folder::visit(binary_expression *expr)
+ {
+ expr->lhs().accept(this);
+ expr->lhs(fold_trait(expr->lhs()));
+ expr->rhs().accept(this);
+ expr->rhs(fold_trait(expr->rhs()));
+ }
+
+ void constant_folder::visit(unary_expression *expr)
+ {
+ expr->operand().accept(this);
+ expr->operand(fold_trait(expr->operand()));
+ }
+
+ void constant_folder::visit(procedure_call *call)
+ {
+ call->callable().accept(this);
+ for (auto& argument : call->arguments)
+ {
+ argument->accept(this);
+ expression& folded = fold_trait(*argument);
+ if (&folded != argument)
+ {
+ delete argument;
+ argument = &folded;
+ }
+ }
+ }
+
+ expression& constant_folder::fold_trait(expression& expr)
+ {
+ if (auto *trait = expr.is_traits())
+ {
+ if (auto value = this->constant_evaluator.evaluate_traits(*trait))
+ {
+ if (auto *literal = value_to_expression(value.value(), trait->position(), trait->type_decoration))
+ {
+ return *literal;
+ }
+ }
+ }
+ return expr;
+ }
+
}
diff --git a/boot/type_check.cc b/boot/type_check.cc
index c88a7d1..2bb0ddd 100644
--- a/boot/type_check.cc
+++ b/boot/type_check.cc
@@ -22,14 +22,30 @@ along with GCC; see the file COPYING3. If not see
namespace elna::boot
{
- non_constant_initializer_error::non_constant_initializer_error(const source_position position)
- : error(position)
+ trait_error::trait_error(const source_position position, const std::string& trait_name,
+ payload_type payload)
+ : error(position), trait_name(trait_name), m_payload(payload)
{
}
- std::string non_constant_initializer_error::what() const
+ std::string trait_error::what() const
{
- return "Variable initializers must be constant expressions";
+ return std::visit([this](auto&& payload) -> std::string
+ {
+ using T = std::decay_t<decltype(payload)>;
+
+ if constexpr (std::is_same_v<T, argument_count>)
+ {
+ return "Trait #" + this->trait_name + " expects "
+ + std::to_string(payload.expected) + " argument"
+ + (payload.expected != 1 ? "s" : "") + ", got "
+ + std::to_string(payload.actual);
+ }
+ else
+ {
+ return "The second argument to the #" + this->trait_name + " trait must be a field name";
+ }
+ }, this->m_payload);
}
type_mismatch_error::type_mismatch_error(const source_position position,
@@ -453,22 +469,6 @@ namespace elna::boot
{
return;
}
- evaluator constant_evaluator(this->bag, this->target, this->evaluated_initializers);
- if (!constant_evaluator.evaluate(*declaration->initializer))
- {
- add_error<non_constant_initializer_error>(declaration->initializer->position());
- return;
- }
- // Record const variable initializers so later declarations
- // can chain through them.
- for (const auto& identifier : declaration->identifiers)
- {
- if (auto var = this->bag.lookup(identifier.name())->is_variable();
- resolve_aliases(var->symbol).get<constant_type>() != nullptr)
- {
- this->evaluated_initializers[identifier.name()] = declaration->initializer;
- }
- }
for (const identifier_definition& variable_identifier : declaration->identifiers)
{
auto variable_symbol = this->bag.lookup(variable_identifier.name())->is_variable();
@@ -864,4 +864,32 @@ namespace elna::boot
expression->lhs().type_decoration, expression->rhs().type_decoration, operation);
}
}
+
+ void type_analysis_visitor::visit(traits_expression *trait)
+ {
+ walking_visitor::visit(trait);
+
+ if (trait->name == "size" || trait->name == "alignment"
+ || trait->name == "min" || trait->name == "max")
+ {
+ if (trait->arguments.size() != 1)
+ {
+ add_error<trait_error>(trait->position(), trait->name.name(),
+ trait_error::argument_count{ .expected = 1, .actual = trait->arguments.size() });
+ }
+ }
+ else if (trait->name == "offset")
+ {
+ if (trait->arguments.size() != 2)
+ {
+ add_error<trait_error>(trait->position(), trait->name.name(),
+ trait_error::argument_count{ .expected = 2, .actual = trait->arguments.size() });
+ }
+ else if (trait->arguments.at(1)->is_named() == nullptr)
+ {
+ add_error<trait_error>(trait->arguments.at(1)->position(), trait->name.name(),
+ trait_error::offset_not_field_name{});
+ }
+ }
+ }
}