diff options
Diffstat (limited to 'boot')
| -rw-r--r-- | boot/ast.cc | 53 | ||||
| -rw-r--r-- | boot/dependency.cc | 9 | ||||
| -rw-r--r-- | boot/evaluator.cc | 340 | ||||
| -rw-r--r-- | boot/type_check.cc | 68 |
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{}); + } + } + } } |
