From 14d4977e2ab2409bb7344395ca01d19e49f130f1 Mon Sep 17 00:00:00 2001 From: Eugen Wissner Date: Sat, 11 Jul 2026 22:33:03 +0200 Subject: Implement record extension --- boot/semantic.cc | 524 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 497 insertions(+), 27 deletions(-) (limited to 'boot/semantic.cc') diff --git a/boot/semantic.cc b/boot/semantic.cc index 42b7b3c..df6e70e 100644 --- a/boot/semantic.cc +++ b/boot/semantic.cc @@ -18,28 +18,46 @@ along with GCC; see the file COPYING3. If not see #include "elna/boot/semantic.h" #include -#include namespace elna::boot { - undeclared_error::undeclared_error(const std::string& identifier, const struct position position) - : error(position), identifier(identifier) + declaration_error::declaration_error(const kind error_kind, + const std::string& identifier, const struct position position) + : error(position), identifier(identifier), error_kind(error_kind) { } - std::string undeclared_error::what() const + std::string declaration_error::what() const { - return "Type '" + identifier + "' not declared"; + switch (this->error_kind) + { + case kind::undeclared: + return "Type '" + identifier + "' not declared"; + case kind::already: + return "Symbol '" + identifier + "' has been already declared"; + default: + __builtin_unreachable(); + } } - already_declared_error::already_declared_error(const std::string& identifier, const struct position position) - : error(position), identifier(identifier) + type_expectation_error::type_expectation_error(const kind error_kind, const struct position position) + : error(position), error_kind(error_kind) { } - std::string already_declared_error::what() const + std::string type_expectation_error::what() const { - return "Symbol '" + identifier + "' has been already declared"; + switch (this->error_kind) + { + case kind::assignment: + return "Assinging an incompatible type"; + case kind::result: + return "Procedure return type and type of the return expression are not compatible"; + case kind::argument: + return "Argument type does not match parameter"; + default: + __builtin_unreachable(); + } } field_duplication_error::field_duplication_error(const std::string& field_name, const struct position position) @@ -91,6 +109,26 @@ namespace elna::boot return "Record base type '" + this->base + "' is not a record."; } + argument_count_error::argument_count_error(std::size_t expected, std::size_t actual, + const struct position position) + : error(position), expected(expected), actual(actual) + { + } + + std::string argument_count_error::what() const + { + if (actual > expected) + { + return "Too many arguments, expected " + std::to_string(expected) + + ", got " + std::to_string(actual); + } + else + { + return "Too few arguments, expected " + std::to_string(expected) + + ", got " + std::to_string(actual); + } + } + type_analysis_visitor::type_analysis_visitor(symbol_bag bag) : error_container(), bag(bag) { @@ -98,24 +136,101 @@ namespace elna::boot void type_analysis_visitor::visit(procedure_declaration *declaration) { + this->current_procedure = this->bag.lookup(declaration->identifier.name)->is_procedure(); this->returns = false; + + if (declaration->body.has_value()) + { + this->bag.enter(this->current_procedure->scope); + } walking_visitor::visit(declaration); if (declaration->body.has_value()) { + this->bag.leave(); if (!this->returns && declaration->heading().return_type.proper_type != nullptr) { add_error(declaration->identifier.name, declaration->position()); } } + this->current_procedure.reset(); } void type_analysis_visitor::visit(return_statement *statement) { walking_visitor::visit(statement); + type return_type; + + if (this->current_procedure == nullptr) + { + // In the main function. + return_type = this->bag.lookup("Int")->is_type()->symbol; + } + else + { + return_type = this->current_procedure->symbol.return_type.proper_type; + } + if (!return_type.empty()) + { + if (!is_assignable_from(return_type, statement->return_expression().type_decoration)) + { + add_error(type_expectation_error::kind::result, statement->position()); + } + } + else + { + add_error(type_expectation_error::kind::result, statement->position()); + } this->returns = true; } + void type_analysis_visitor::visit(assign_statement *statement) + { + walking_visitor::visit(statement); + + if (!is_assignable_from(statement->lvalue().type_decoration, statement->rvalue().type_decoration)) + { + add_error(type_expectation_error::kind::assignment, statement->position()); + } + } + + void type_analysis_visitor::visit(variable_declaration *declaration) + { + walking_visitor::visit(declaration); + + if (declaration->initializer == nullptr) + { + return; + } + for (const identifier_definition& variable_identifier : declaration->identifiers) + { + auto variable_symbol = this->bag.lookup(variable_identifier.name)->is_variable(); + if (!is_assignable_from(variable_symbol->symbol, declaration->initializer->type_decoration)) + { + add_error(type_expectation_error::kind::assignment, + declaration->initializer->position()); + } + } + } + + void type_analysis_visitor::visit(case_statement *statement) + { + walking_visitor::visit(statement); + type condition_type = inner_aliased_type(statement->condition().type_decoration); + + for (const switch_case& case_block : statement->cases) + { + for (expression *const case_label : case_block.labels) + { + if (!is_assignable_from(condition_type, case_label->type_decoration)) + { + add_error(type_expectation_error::kind::assignment, + case_label->position()); + } + } + } + } + bool type_analysis_visitor::check_unresolved_symbol(std::shared_ptr alias, std::vector& alias_path) { @@ -160,6 +275,78 @@ namespace elna::boot walking_visitor::visit(expression); } + std::size_t type_analysis_visitor::match_record_fields( + const std::shared_ptr& record, + std::vector::const_iterator& argument_it, + const std::vector::const_iterator& argument_end) + { + std::size_t expected = 0; + + if (!record->base.empty()) + { + if (auto base = inner_aliased_type(record->base).get()) + { + expected += match_record_fields(base, argument_it, argument_end); + } + } + for (auto& field : record->fields) + { + ++expected; + if (argument_it != argument_end) + { + (*argument_it)->accept(this); + if (!is_assignable_from(field.second, (*argument_it)->type_decoration)) + { + add_error(type_expectation_error::kind::argument, + (*argument_it)->position()); + } + ++argument_it; + } + } + return expected; + } + + void type_analysis_visitor::visit(procedure_call *call) + { + call->callable().accept(this); + + if (auto procedure = call->callable().type_decoration.get()) + { + std::vector::const_iterator argument_iterator = std::cbegin(call->arguments); + std::vector::const_iterator type_iterator = std::cbegin(procedure->parameters); + + while (argument_iterator != std::cend(call->arguments) + && type_iterator != std::cend(procedure->parameters)) + { + (*argument_iterator)->accept(this); + if (!is_assignable_from(*type_iterator, (*argument_iterator)->type_decoration)) + { + add_error(type_expectation_error::kind::argument, + (*argument_iterator)->position()); + } + ++argument_iterator; + ++type_iterator; + } + if (call->arguments.size() != procedure->parameters.size()) + { + add_error(procedure->parameters.size(), + call->arguments.size(), call->position()); + } + } + else if (auto callable_record = inner_aliased_type(call->type_decoration).get()) + { + auto argument_it = std::cbegin(call->arguments); + auto argument_end = std::cend(call->arguments); + + std::size_t expected = match_record_fields(callable_record, argument_it, argument_end); + + if (call->arguments.size() != expected) + { + add_error(expected, call->arguments.size(), call->position()); + } + } + } + name_analysis_visitor::name_analysis_visitor(symbol_bag bag) : error_container(), bag(bag) { @@ -198,6 +385,93 @@ namespace elna::boot return result_type; } + type name_analysis_visitor::lookup_primitive_type(const std::string& name) + { + return this->bag.lookup(name)->is_type()->symbol; + } + + type name_analysis_visitor::type_of_constant(const constant_info::variant& value) + { + return std::visit([this](auto&& arg) -> type { + using T = std::decay_t; + + if constexpr (std::is_same_v) + { + return lookup_primitive_type("Int"); + } + else if constexpr (std::is_same_v) + { + return lookup_primitive_type("Word"); + } + else if constexpr (std::is_same_v) + { + return lookup_primitive_type("Float"); + } + else if constexpr (std::is_same_v) + { + return lookup_primitive_type("Bool"); + } + else if constexpr (std::is_same_v) + { + return lookup_primitive_type("Char"); + } + else if constexpr (std::is_same_v) + { + return lookup_primitive_type("Pointer"); + } + else if constexpr (std::is_same_v) + { + return lookup_primitive_type("String"); + } + else + { + static_assert(!sizeof(T*), "Unhandled constant type"); + } + }, value); + } + + type name_analysis_visitor::lookup_field(const type& composite_type, const std::string& field_name) + { + type resolved_type = inner_aliased_type(composite_type); + + if (auto record = resolved_type.get()) + { + for (auto& field : record->fields) + { + if (field.first == field_name) + { + return field.second; + } + } + if (!record->base.empty()) + { + return lookup_field(record->base, field_name); + } + } + else if (auto primitive = resolved_type.get(); primitive != nullptr && primitive->identifier == "String") + { + if (field_name == "length") + { + return lookup_primitive_type("Word"); + } + else if (field_name == "ptr") + { + return type(std::make_shared(lookup_primitive_type("Char"))); + } + } + else if (auto composite = resolved_type.get()) + { + for (auto& field : composite->fields) + { + if (field.first == field_name) + { + return field.second; + } + } + } + return type(); + } + void name_analysis_visitor::visit(type_declaration *declaration) { walking_visitor::visit(declaration); @@ -222,23 +496,42 @@ namespace elna::boot this->current_type = type(result_type); } - std::vector name_analysis_visitor::build_composite_type(const std::vector& fields) + /** + * Collects field names from a record type recursively, base first. + */ + static void collect_field_names(const type& composite_type, std::set& names) + { + auto record = inner_aliased_type(composite_type).get(); + if (record == nullptr) + { + return; + } + if (!record->base.empty()) + { + collect_field_names(record->base, names); + } + for (auto& field : record->fields) + { + names.insert(field.first); + } + } + + std::vector name_analysis_visitor::build_composite_type( + const std::vector& fields, std::set& field_names) { std::vector result; - std::set field_names; for (auto& field : fields) { field.second->accept(this); for (auto& field_name : field.first) { - if (field_names.find(field_name) != field_names.cend()) + if (!field_names.insert(field_name).second) { add_error(field_name, field.second->position()); } else { - field_names.insert(field_name); result.push_back(std::make_pair(field_name, this->current_type)); } } @@ -270,7 +563,8 @@ namespace elna::boot } else { - add_error(expression->base.value(), expression->position()); + add_error(declaration_error::kind::undeclared, + expression->base.value(), expression->position()); this->current_type = type(); return; } @@ -279,7 +573,10 @@ namespace elna::boot { result_type = std::make_shared(); } - result_type->fields = build_composite_type(expression->fields); + + std::set field_names; + collect_field_names(result_type->base, field_names); + result_type->fields = build_composite_type(expression->fields, field_names); this->current_type = type(result_type); } @@ -288,7 +585,8 @@ namespace elna::boot { auto result_type = std::make_shared(); - result_type->fields = build_composite_type(expression->fields); + std::set field_names; + result_type->fields = build_composite_type(expression->fields, field_names); this->current_type = type(result_type); } @@ -315,7 +613,7 @@ namespace elna::boot if (!this->bag.enter(name, variable_symbol)) { - add_error(name, position); + add_error(declaration_error::kind::already, name, position); } return variable_symbol; } @@ -385,11 +683,14 @@ namespace elna::boot void name_analysis_visitor::visit(procedure_call *call) { - auto name_expression = call->callable().is_named(); - - if (name_expression == nullptr || name_expression->name != "assert") + call->callable().accept(this); + if (auto procedure = call->callable().type_decoration.get()) + { + call->type_decoration = procedure->return_type.proper_type; + } + else if (call->callable().is_named() != nullptr) { - call->callable().accept(this); + call->type_decoration = this->current_type; } for (expression *const argument : call->arguments) { @@ -418,10 +719,10 @@ namespace elna::boot if (unit->entry_point.has_value()) { this->bag.enter(); - auto variable_type = this->bag.lookup("Int")->is_type()->symbol; + auto variable_type = lookup_primitive_type("Int"); this->bag.enter("count", std::make_shared(variable_type, false)); - variable_type = this->bag.lookup("Char")->is_type()->symbol; + variable_type = lookup_primitive_type("Char"); variable_type = type(std::make_shared(variable_type)); variable_type = type(std::make_shared(variable_type)); this->bag.enter("parameters", std::make_shared(variable_type, false)); @@ -441,16 +742,99 @@ namespace elna::boot trait->parameters.front()->accept(this); trait->types.push_back(this->current_type); } + + if (trait->name == "size" || trait->name == "alignment" || trait->name == "offset") + { + trait->type_decoration = lookup_primitive_type("Word"); + } + else if (trait->name == "min" || trait->name == "max") + { + trait->type_decoration = trait->types.empty() ? type() : trait->types.front(); + } + } + + void name_analysis_visitor::visit(binary_expression *expression) + { + walking_visitor::visit(expression); + + switch (expression->operation()) + { + case binary_operator::equals: + case binary_operator::not_equals: + case binary_operator::less: + case binary_operator::greater: + case binary_operator::less_equal: + case binary_operator::greater_equal: + expression->type_decoration = lookup_primitive_type("Bool"); + break; + default: + expression->type_decoration = expression->lhs().type_decoration; + break; + } + } + + void name_analysis_visitor::visit(unary_expression *expression) + { + walking_visitor::visit(expression); + + if (expression->operation() == unary_operator::reference) + { + expression->type_decoration = this->current_type + = type(std::make_shared(expression->operand().type_decoration)); + } + else + { + expression->type_decoration = expression->operand().type_decoration; + } + } + + void name_analysis_visitor::visit(array_access_expression *expression) + { + walking_visitor::visit(expression); + auto resolved_base= inner_aliased_type(expression->base().type_decoration); + + if (auto array = resolved_base.get()) + { + expression->type_decoration = array->base; + } + else if (resolved_base == lookup_primitive_type("String")) + { + expression->type_decoration = lookup_primitive_type("Char"); + } + } + + void name_analysis_visitor::visit(field_access_expression *expression) + { + walking_visitor::visit(expression); + expression->type_decoration = lookup_field(expression->base().type_decoration, expression->field()); + auto is_designator = expression->base().is_designator(); + + if (expression->type_decoration.empty() && is_designator != nullptr && is_designator->is_named() != nullptr) + { + expression->type_decoration = this->current_type; + } + } + + void name_analysis_visitor::visit(dereference_expression *expression) + { + walking_visitor::visit(expression); + + if (auto pointer = inner_aliased_type(expression->base().type_decoration).get()) + { + expression->type_decoration = pointer->base; + } } void name_analysis_visitor::visit(cast_expression *expression) { walking_visitor::visit(expression); - expression->expression_type = this->current_type; + expression->type_decoration = this->current_type; } void name_analysis_visitor::visit(named_expression *expression) { + this->current_type = type(); + if (auto unresolved_alias = this->bag.declared(expression->name)) { this->current_type = type(unresolved_alias); @@ -461,47 +845,66 @@ namespace elna::boot { this->current_type = type_symbol->symbol; } + else if (auto variable_symbol = from_symbol_table->is_variable()) + { + expression->type_decoration = variable_symbol->symbol; + } + else if (auto constant_symbol = from_symbol_table->is_constant()) + { + expression->type_decoration = type_of_constant(constant_symbol->symbol); + } + else if (auto procedure_symbol = from_symbol_table->is_procedure()) + { + expression->type_decoration = type(std::make_shared(procedure_symbol->symbol)); + } } else { - add_error(expression->name, expression->position()); - this->current_type = type(); + add_error(declaration_error::kind::undeclared, + expression->name, expression->position()); } } void name_analysis_visitor::visit(literal *literal) { this->current_literal = literal->value; + literal->type_decoration = lookup_primitive_type("Int"); } void name_analysis_visitor::visit(literal *literal) { this->current_literal = literal->value; + literal->type_decoration = lookup_primitive_type("Word"); } void name_analysis_visitor::visit(literal *literal) { this->current_literal = literal->value; + literal->type_decoration = lookup_primitive_type("Float"); } void name_analysis_visitor::visit(literal *literal) { this->current_literal = literal->value; + literal->type_decoration = lookup_primitive_type("Bool"); } void name_analysis_visitor::visit(literal *literal) { this->current_literal = literal->value; + literal->type_decoration = lookup_primitive_type("Char"); } void name_analysis_visitor::visit(literal *literal) { this->current_literal = literal->value; + literal->type_decoration = lookup_primitive_type("Pointer"); } void name_analysis_visitor::visit(literal *literal) { this->current_literal = literal->value; + literal->type_decoration = lookup_primitive_type("String"); } declaration_visitor::declaration_visitor() @@ -531,7 +934,74 @@ namespace elna::boot if (!this->unresolved.insert({ type_identifier, std::make_shared(type_identifier) }).second) { - add_error(declaration->identifier.name, declaration->position()); + add_error(declaration_error::kind::already, + declaration->identifier.name, declaration->position()); + } + } + + bool is_base_of(std::shared_ptr base, std::shared_ptr derived) + { + type current = derived->base; + + while (!current.empty()) + { + auto current_record = inner_aliased_type(current).get(); + if (current_record == nullptr) + { + return false; + } + if (current_record == base) + { + return true; + } + current = current_record->base; + } + return false; + } + + bool are_compatible_pointers(type lhs_type, type rhs_type) + { + std::shared_ptr lhs_primitive = lhs_type.get(); + std::shared_ptr rhs_primitive = rhs_type.get(); + + bool lhs_is_primitive_pointer = lhs_primitive != nullptr && lhs_primitive->identifier == "Pointer"; + bool rhs_is_primitive_pointer = rhs_primitive != nullptr && rhs_primitive->identifier == "Pointer"; + + return (lhs_is_primitive_pointer && rhs_type.get() != nullptr) + || (rhs_is_primitive_pointer && lhs_type.get() != nullptr) + || (lhs_is_primitive_pointer && rhs_type.get() != nullptr) + || (rhs_is_primitive_pointer && lhs_type.get() != nullptr) + || (lhs_is_primitive_pointer && rhs_is_primitive_pointer); + } + + bool is_assignable_from(const type& assignee, const type& assignment) + { + type resolved_assignee = inner_aliased_type(assignee); + type resolved_assignment = inner_aliased_type(assignment); + + if (resolved_assignee == resolved_assignment) + { + return true; } + // If the types do not match, only some pointers can be assigned. + if (are_compatible_pointers(resolved_assignee, resolved_assignment)) + { + return true; + } + std::shared_ptr assignee_pointer = resolved_assignee.get(); + std::shared_ptr assignment_pointer = resolved_assignment.get(); + + if (assignee_pointer == nullptr || assignment_pointer == nullptr) + { + return false; + } + // A pointer to a record can be assigned to a pointer to its base type. + type assignee_pointee = inner_aliased_type(assignee_pointer->base); + type assignment_pointee = inner_aliased_type(assignment_pointer->base); + std::shared_ptr assignee_record = assignee_pointee.get(); + std::shared_ptr assignment_record = assignment_pointee.get(); + + return assignee_record != nullptr && assignment_record != nullptr + && is_base_of(assignee_record, assignment_record); } } -- cgit v1.2.3