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/ast.cc | 75 ++-- boot/semantic.cc | 524 ++++++++++++++++++++++-- boot/symbol.cc | 68 ++- gcc/gcc/elna-generic.cc | 119 +----- include/elna/boot/ast.h | 6 +- include/elna/boot/result.h | 5 + include/elna/boot/semantic.h | 79 +++- include/elna/boot/symbol.h | 7 +- source/lexer.elna | 8 +- testsuite/compilable/assign_record_to_base.elna | 16 + testsuite/compilable/pointer_cast.elna | 8 + 11 files changed, 714 insertions(+), 201 deletions(-) create mode 100644 testsuite/compilable/assign_record_to_base.elna create mode 100644 testsuite/compilable/pointer_cast.elna diff --git a/boot/ast.cc b/boot/ast.cc index 4270a3b..d0d19b7 100644 --- a/boot/ast.cc +++ b/boot/ast.cc @@ -19,184 +19,179 @@ along with GCC; see the file COPYING3. If not see namespace elna::boot { - void empty_visitor::not_implemented() - { - __builtin_unreachable(); - } - void empty_visitor::visit(array_type_expression *) { - not_implemented(); + __builtin_unreachable(); } void empty_visitor::visit(pointer_type_expression *) { - not_implemented(); + __builtin_unreachable(); } void empty_visitor::visit(type_declaration *) { - not_implemented(); + __builtin_unreachable(); } void empty_visitor::visit(record_type_expression *) { - not_implemented(); + __builtin_unreachable(); } void empty_visitor::visit(union_type_expression *) { - not_implemented(); + __builtin_unreachable(); } void empty_visitor::visit(procedure_type_expression *) { - not_implemented(); + __builtin_unreachable(); } void empty_visitor::visit(enumeration_type_expression *) { - not_implemented(); + __builtin_unreachable(); } void empty_visitor::visit(variable_declaration *) { - not_implemented(); + __builtin_unreachable(); } void empty_visitor::visit(constant_declaration *) { - not_implemented(); + __builtin_unreachable(); } void empty_visitor::visit(procedure_declaration *) { - not_implemented(); + __builtin_unreachable(); } void empty_visitor::visit(assign_statement *) { - not_implemented(); + __builtin_unreachable(); } void empty_visitor::visit(if_statement *) { - not_implemented(); + __builtin_unreachable(); } void empty_visitor::visit(import_declaration *) { - not_implemented(); + __builtin_unreachable(); } void empty_visitor::visit(while_statement *) { - not_implemented(); + __builtin_unreachable(); } void empty_visitor::visit(return_statement *) { - not_implemented(); + __builtin_unreachable(); } void empty_visitor::visit(defer_statement *) { - not_implemented(); + __builtin_unreachable(); } void empty_visitor::visit(empty_statement *) { - not_implemented(); + __builtin_unreachable(); } void empty_visitor::visit(case_statement *) { - not_implemented(); + __builtin_unreachable(); } void empty_visitor::visit(procedure_call *) { - not_implemented(); + __builtin_unreachable(); } void empty_visitor::visit(unit *) { - not_implemented(); + __builtin_unreachable(); } void empty_visitor::visit(cast_expression *) { - not_implemented(); + __builtin_unreachable(); } void empty_visitor::visit(traits_expression *) { - not_implemented(); + __builtin_unreachable(); } void empty_visitor::visit(binary_expression *) { - not_implemented(); + __builtin_unreachable(); } void empty_visitor::visit(unary_expression *) { - not_implemented(); + __builtin_unreachable(); } void empty_visitor::visit(named_expression *) { - not_implemented(); + __builtin_unreachable(); } void empty_visitor::visit(array_access_expression *) { - not_implemented(); + __builtin_unreachable(); } void empty_visitor::visit(field_access_expression *) { - not_implemented(); + __builtin_unreachable(); } void empty_visitor::visit(dereference_expression *) { - not_implemented(); + __builtin_unreachable(); } void empty_visitor::visit(literal *) { - not_implemented(); + __builtin_unreachable(); } void empty_visitor::visit(literal *) { - not_implemented(); + __builtin_unreachable(); } void empty_visitor::visit(literal *) { - not_implemented(); + __builtin_unreachable(); } void empty_visitor::visit(literal *) { - not_implemented(); + __builtin_unreachable(); } void empty_visitor::visit(literal *) { - not_implemented(); + __builtin_unreachable(); } void empty_visitor::visit(literal *) { - not_implemented(); + __builtin_unreachable(); } void empty_visitor::visit(literal *) { - not_implemented(); + __builtin_unreachable(); } void walking_visitor::visit(import_declaration *) 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); } } diff --git a/boot/symbol.cc b/boot/symbol.cc index 512cf04..b75cdc1 100644 --- a/boot/symbol.cc +++ b/boot/symbol.cc @@ -157,11 +157,6 @@ namespace elna::boot return *this; } - bool type::operator==(const std::nullptr_t&) - { - return empty(); - } - type::~type() { switch (tag) @@ -243,6 +238,57 @@ namespace elna::boot return tag == type_tag::enumeration ? this->enumeration : nullptr; } + bool type::operator==(const std::nullptr_t&) const + { + return empty(); + } + + bool type::operator==(const type& other) const + { + type resolved_this = inner_aliased_type(*this); + type resolved_that = inner_aliased_type(other); + + if (auto left_record = resolved_this.get()) + { + return left_record == resolved_that.get(); + } + if (auto left_primitive = resolved_this.get()) + { + auto right_primitive = resolved_that.get(); + + return right_primitive != nullptr && left_primitive->identifier == right_primitive->identifier; + } + if (auto left_union = resolved_this.get()) + { + return left_union == resolved_that.get(); + } + if (auto left_enumeration = resolved_this.get()) + { + return left_enumeration == resolved_that.get(); + } + if (auto left_pointer = resolved_this.get()) + { + auto right_pointer = resolved_that.get(); + + return right_pointer != nullptr && left_pointer->base == right_pointer->base; + } + if (auto left_array = resolved_this.get()) + { + auto right_array = resolved_that.get(); + + return right_array != nullptr && left_array->size == right_array->size + && left_array->base == right_array->base; + } + if (auto left_procedure = resolved_this.get()) + { + auto right_procedure = resolved_that.get(); + + return right_procedure != nullptr && left_procedure->return_type == right_procedure->return_type + && left_procedure->parameters == right_procedure->parameters; + } + return resolved_this.empty() && resolved_that.empty(); + } + bool type::empty() const { return tag == type_tag::empty; @@ -360,11 +406,19 @@ namespace elna::boot result->enter("Int", std::make_shared(type(std::make_shared("Int")))); result->enter("Word", std::make_shared(type(std::make_shared("Word")))); result->enter("Char", std::make_shared(type(std::make_shared("Char")))); - result->enter("Bool", std::make_shared(type(std::make_shared("Bool")))); result->enter("Pointer", std::make_shared(type(std::make_shared("Pointer")))); result->enter("Float", std::make_shared(type(std::make_shared("Float")))); result->enter("String", std::make_shared(type(std::make_shared("String")))); + type boolean = type(std::make_shared("Bool")); + result->enter("Bool", std::make_shared(boolean)); + + procedure_type assert_symbol{ procedure_type::return_t() }; + assert_symbol.parameters.push_back(boolean); + std::shared_ptr assert_info = std::make_shared(assert_symbol, + std::vector{ "condition" }); + result->enter("assert", assert_info); + return result; } @@ -435,7 +489,7 @@ namespace elna::boot return inner_aliased_type(alias->reference); } - type inner_aliased_type(type alias) + type inner_aliased_type(const type& alias) { if (auto aliased = alias.get()) { diff --git a/gcc/gcc/elna-generic.cc b/gcc/gcc/elna-generic.cc index 9c8990b..497bcc6 100644 --- a/gcc/gcc/elna-generic.cc +++ b/gcc/gcc/elna-generic.cc @@ -46,86 +46,29 @@ namespace elna::gcc vec *argument_trees = nullptr; tree symbol_type = TREE_TYPE(TREE_TYPE(procedure_address)); - tree current_parameter = TYPE_ARG_TYPES(symbol_type); - vec_alloc(argument_trees, arguments.size()); for (boot::expression *const argument : arguments) { - location_t argument_location = get_location(&argument->position()); - if (VOID_TYPE_P(TREE_VALUE(current_parameter))) - { - error_at(argument_location, "Too many arguments, expected %i, got %lu", - list_length(TYPE_ARG_TYPES(symbol_type)) - 1, arguments.size()); - this->current_expression = error_mark_node; - break; - } argument->accept(this); this->current_expression = prepare_rvalue(this->current_expression); - if (!is_assignable_from(TREE_VALUE(current_parameter), this->current_expression)) - { - error_at(argument_location, - "Cannot assign value of type '%s' to variable of type '%s'", - print_type(TREE_TYPE(this->current_expression)).c_str(), - print_type(TREE_VALUE(current_parameter)).c_str()); - this->current_expression = error_mark_node; - } - current_parameter = TREE_CHAIN(current_parameter); argument_trees->quick_push(this->current_expression); } - tree stmt = fold_build_call_array_loc(call_location, TREE_TYPE(symbol_type), + this->current_expression = fold_build_call_array_loc(call_location, TREE_TYPE(symbol_type), procedure_address, vec_safe_length(argument_trees), vec_safe_address(argument_trees)); - - if (!VOID_TYPE_P(TREE_VALUE(current_parameter))) - { - error_at(call_location, "Too few arguments, expected %i, got %lu", - list_length(TYPE_ARG_TYPES(symbol_type)) - 1, arguments.size()); - this->current_expression = error_mark_node; - } - else - { - this->current_expression = stmt; - } } - void generic_visitor::build_record_call(location_t call_location, - tree symbol, const std::vector& arguments) + void generic_visitor::build_record_call(location_t, tree symbol, + const std::vector& arguments) { vec *tree_arguments = nullptr; tree record_fields = TYPE_FIELDS(symbol); for (boot::expression *const argument : arguments) { - location_t argument_location = get_location(&argument->position()); - - if (is_void_type(record_fields)) - { - error_at(argument_location, "Too many arguments, expected %i, got %lu", - list_length(TYPE_FIELDS(symbol)), arguments.size()); - this->current_expression = error_mark_node; - break; - } argument->accept(this); - tree unqualified_field = get_qualified_type(TREE_TYPE(record_fields), TYPE_UNQUALIFIED); - if (!is_assignable_from(unqualified_field, this->current_expression)) - { - error_at(argument_location, - "Cannot assign value of type '%s' to variable of type '%s'", - print_type(TREE_TYPE(this->current_expression)).c_str(), - print_type(TREE_TYPE(record_fields)).c_str()); - this->current_expression = error_mark_node; - } CONSTRUCTOR_APPEND_ELT(tree_arguments, record_fields, this->current_expression); record_fields = TREE_CHAIN(record_fields); } - if (!is_void_type(record_fields)) - { - error_at(call_location, "Too few arguments, expected %i, got %lu", - list_length(TYPE_FIELDS(symbol)), arguments.size()); - this->current_expression = error_mark_node; - } - else - { - this->current_expression = build_constructor(symbol, tree_arguments); - } + this->current_expression = build_constructor(symbol, tree_arguments); } void generic_visitor::build_assert_builtin(location_t call_location, @@ -217,7 +160,7 @@ namespace elna::gcc void generic_visitor::visit(boot::cast_expression *expression) { - tree cast_target = get_inner_alias(expression->expression_type, this->symbols->scope()); + tree cast_target = get_inner_alias(expression->type_decoration, this->symbols->scope()); expression->value().accept(this); tree cast_source = TREE_TYPE(this->current_expression); @@ -746,16 +689,7 @@ namespace elna::gcc if (declaration->initializer != nullptr) { declaration->initializer->accept(this); - if (is_assignable_from(TREE_TYPE(declaration_tree), this->current_expression)) - { - DECL_INITIAL(declaration_tree) = this->current_expression; - } - else - { - error_at(declaration_location, "Cannot initialize variable of type '%s' with a value of type '%s'", - print_type(TREE_TYPE(declaration_tree)).c_str(), - print_type(TREE_TYPE(this->current_expression)).c_str()); - } + DECL_INITIAL(declaration_tree) = this->current_expression; } else if (!declaration->is_extern && POINTER_TYPE_P(TREE_TYPE(declaration_tree))) { @@ -1027,18 +961,12 @@ namespace elna::gcc error_at(statement_location, "Cannot modify a constant expression of type '%s'", print_type(TREE_TYPE(lvalue)).c_str()); } - else if (is_assignable_from(TREE_TYPE(lvalue), rvalue)) + else { tree assignment = build2_loc(statement_location, MODIFY_EXPR, void_type_node, lvalue, rvalue); append_statement(assignment); } - else - { - error_at(statement_location, "Cannot assign value of type '%s' to variable of type '%s'", - print_type(TREE_TYPE(rvalue)).c_str(), - print_type(TREE_TYPE(lvalue)).c_str()); - } this->current_expression = NULL_TREE; } @@ -1143,7 +1071,6 @@ namespace elna::gcc boot::expression *return_expression = &statement->return_expression(); location_t statement_position = get_location(&statement->position()); tree set_result{ NULL_TREE }; - tree return_type = TREE_TYPE(TREE_TYPE(current_function_decl)); if (TREE_THIS_VOLATILE(current_function_decl) == 1) { @@ -1157,26 +1084,9 @@ namespace elna::gcc set_result = build2(INIT_EXPR, void_type_node, DECL_RESULT(current_function_decl), this->current_expression); } - if (return_type == void_type_node && set_result != NULL_TREE) - { - error_at(statement_position, "Proper procedure is not allowed to return a value"); - } - else if (return_type != void_type_node && set_result == NULL_TREE) - { - error_at(statement_position, "Procedure is expected to return a value of type '%s'", - print_type(return_type).c_str()); - } - else if (return_type != void_type_node && !is_assignable_from(return_type, this->current_expression)) - { - error_at(statement_position, "Cannot return '%s' from a procedure returning '%s'", - print_type(return_type).c_str(), - print_type(TREE_TYPE(this->current_expression)).c_str()); - } - else - { - tree return_stmt = build1_loc(statement_position, RETURN_EXPR, void_type_node, set_result); - append_statement(return_stmt); - } + tree return_stmt = build1_loc(statement_position, RETURN_EXPR, void_type_node, set_result); + append_statement(return_stmt); + this->current_expression = NULL_TREE; } @@ -1215,14 +1125,7 @@ namespace elna::gcc case_label->accept(this); location_t case_location = get_location(&case_label->position()); - if (assert_constant(case_location) - && !is_assignable_from(unqualified_condition, this->current_expression)) - { - error_at(case_location, "Case type '%s' does not match the expression type '%s'", - print_type(TREE_TYPE(this->current_expression)).c_str(), - print_type(unqualified_condition).c_str()); - this->current_expression = error_mark_node; - } + assert_constant(case_location); tree case_label_declaration = create_artificial_label(case_location); tree case_expression = build_case_label(this->current_expression, NULL_TREE, case_label_declaration); diff --git a/include/elna/boot/ast.h b/include/elna/boot/ast.h index 1c27166..17a0dc1 100644 --- a/include/elna/boot/ast.h +++ b/include/elna/boot/ast.h @@ -135,8 +135,6 @@ namespace elna::boot */ class empty_visitor : public parser_visitor { - [[noreturn]] void not_implemented(); - public: [[noreturn]] virtual void visit(array_type_expression *) override; [[noreturn]] virtual void visit(pointer_type_expression *) override; @@ -250,6 +248,8 @@ namespace elna::boot class expression : public virtual node { public: + type type_decoration; + virtual cast_expression *is_cast(); virtual traits_expression *is_traits(); virtual binary_expression *is_binary(); @@ -501,8 +501,6 @@ namespace elna::boot expression *m_value; public: - type expression_type; - cast_expression(const struct position position, type_expression *target, expression *value); void accept(parser_visitor *visitor) override; cast_expression *is_cast() override; diff --git a/include/elna/boot/result.h b/include/elna/boot/result.h index 3a84229..1b04bfb 100644 --- a/include/elna/boot/result.h +++ b/include/elna/boot/result.h @@ -100,6 +100,11 @@ namespace elna::boot { } + bool operator==(const return_declaration& other) const + { + return this->proper_type == other.proper_type && this->no_return == other.no_return; + } + T proper_type{}; bool no_return{ false }; }; diff --git a/include/elna/boot/semantic.h b/include/elna/boot/semantic.h index 9d79b14..e351258 100644 --- a/include/elna/boot/semantic.h +++ b/include/elna/boot/semantic.h @@ -20,6 +20,7 @@ along with GCC; see the file COPYING3. If not see #include #include #include +#include #include #include "elna/boot/ast.h" @@ -29,29 +30,47 @@ along with GCC; see the file COPYING3. If not see namespace elna::boot { /** - * Undeclared identifier. + * Error declaring a symbol. */ - class undeclared_error : public error + class declaration_error : public error { const std::string identifier; public: - undeclared_error(const std::string& identifier, const struct position position); + enum class kind + { + undeclared, + already + }; + + declaration_error(const kind error_kind, + const std::string& identifier, const struct position position); std::string what() const override; + + private: + const kind error_kind; }; /** * A symbol was already declared in this scope. */ - class already_declared_error : public error + class type_expectation_error : public error { - const std::string identifier; - public: - already_declared_error(const std::string& identifier, const struct position position); + enum class kind + { + assignment, + result, + argument + }; + + type_expectation_error(const kind error_kind, const struct position position); std::string what() const override; + + private: + const kind error_kind; }; /** @@ -106,6 +125,22 @@ namespace elna::boot std::string what() const override; }; + /** + * Argument count in a procedure or record call doesn't match + * the expected number of parameters or fields. + */ + class argument_count_error : public error + { + const std::size_t expected; + const std::size_t actual; + + public: + argument_count_error(std::size_t expected, std::size_t actual, + const struct position position); + + std::string what() const override; + }; + /** * Checks types. */ @@ -113,17 +148,25 @@ namespace elna::boot { bool returns; symbol_bag bag; + std::shared_ptr current_procedure; bool check_unresolved_symbol(std::shared_ptr alias, std::vector& path); + std::size_t match_record_fields(const std::shared_ptr& record, + std::vector::const_iterator& argument_it, + const std::vector::const_iterator& argument_end); public: explicit type_analysis_visitor(symbol_bag bag); void visit(procedure_declaration *declaration) override; void visit(return_statement *statement) override; + void visit(assign_statement *statement) override; + void visit(variable_declaration *declaration) override; void visit(type_declaration *declaration) override; void visit(record_type_expression *expression) override; + void visit(procedure_call *call) override; + void visit(case_statement *statement) override; }; /** @@ -138,10 +181,15 @@ namespace elna::boot std::pair> build_procedure( procedure_type_expression& expression); - std::vector build_composite_type(const std::vector& fields); + std::vector build_composite_type(const std::vector& fields, + std::set& known_names); std::shared_ptr register_variable(const std::string& name, const bool is_extern, const struct position position); + type lookup_primitive_type(const std::string& name); + type type_of_constant(const constant_info::variant& value); + type lookup_field(const type& composite_type, const std::string& field_name); + public: name_analysis_visitor(symbol_bag bag); @@ -161,7 +209,12 @@ namespace elna::boot void visit(unit *unit) override; void visit(cast_expression *expression) override; void visit(traits_expression *trait) override; + void visit(binary_expression *expression) override; + void visit(unary_expression *expression) override; void visit(named_expression *expression) override; + void visit(array_access_expression *expression) override; + void visit(field_access_expression *expression) override; + void visit(dereference_expression *expression) override; void visit(literal *literal) override; void visit(literal *literal) override; void visit(literal *literal) override; @@ -185,4 +238,14 @@ namespace elna::boot void visit(unit *unit) override; void visit(type_declaration *declaration) override; }; + + /** + * Whether an expression of type \a assignment can be assigned to a variable + * of type \a assignee. + * + * \param assignee Assignee. + * \param assignment Assignment. + * \return Whether the assignment can be performed. + */ + bool is_assignable_from(const type& assignee, const type& assignment); } diff --git a/include/elna/boot/symbol.h b/include/elna/boot/symbol.h index 6258760..85532db 100644 --- a/include/elna/boot/symbol.h +++ b/include/elna/boot/symbol.h @@ -84,13 +84,14 @@ namespace elna::boot type(type&& other); type& operator=(type&& other); - bool operator==(const std::nullptr_t&); - ~type(); template std::shared_ptr get() const; + bool operator==(const std::nullptr_t&) const; + bool operator==(const type& other) const; + bool empty() const; }; @@ -466,6 +467,6 @@ namespace elna::boot * \param alias The type to lookup the innermost declaration for. * \return Innermost type declaration. */ - type inner_aliased_type(type alias); + type inner_aliased_type(const type& alias); type inner_aliased_type(std::shared_ptr alias); } diff --git a/source/lexer.elna b/source/lexer.elna index 9008139..bb8c8ef 100644 --- a/source/lexer.elna +++ b/source/lexer.elna @@ -408,7 +408,7 @@ begin text_length := cast(lexer^.current.iterator - lexer^.start.iterator + 1: Word); token^.value.stringKind := String(cast(malloc(text_length): ^Char), text_length); - memcpy(cast(token^.value.stringKind.ptr: Pointer), cast(lexer^.start.iterator: Pointer), text_length); + memcpy(token^.value.stringKind.ptr, lexer^.start.iterator, text_length); token^.kind := LexerKind.character end; @@ -416,7 +416,7 @@ begin text_length := cast(lexer^.current.iterator - lexer^.start.iterator + 1: Word); token^.value.stringKind := String(cast(malloc(text_length): ^Char), text_length); - memcpy(cast(token^.value.stringKind.ptr: Pointer), cast(lexer^.start.iterator: Pointer), text_length); + memcpy(token^.value.stringKind.ptr, lexer^.start.iterator, text_length); token^.kind := LexerKind.string end; @@ -429,7 +429,7 @@ begin token^.kind := LexerKind.identifier; token^.value.identifierKind[1] := cast(lexer^.current.iterator - lexer^.start.iterator: Char); - memcpy(cast(@token^.value.identifierKind[2]: Pointer), cast(lexer^.start.iterator: Pointer), cast(token^.value.identifierKind[1]: Word)); + memcpy(@token^.value.identifierKind[2], lexer^.start.iterator, cast(token^.value.identifierKind[1]: Word)); if compare_keyword("program", lexer^.start, lexer^.current.iterator) then token^.kind := LexerKind._program @@ -579,7 +579,7 @@ begin integer_length := cast(lexer^.current.iterator - lexer^.start.iterator: Word); memset(cast(token^.value.identifierKind.ptr: Pointer), 0, #size(Identifier)); - memcpy(cast(@token^.value.identifierKind[1]: Pointer), cast(lexer^.start.iterator: Pointer), integer_length); + memcpy(@token^.value.identifierKind[1], lexer^.start.iterator, integer_length); token^.value.identifierKind[cast(token^.value.identifierKind[1]: Int) + 2] := '\0'; token^.value.integerKind := atoi(@token^.value.identifierKind[2]) diff --git a/testsuite/compilable/assign_record_to_base.elna b/testsuite/compilable/assign_record_to_base.elna new file mode 100644 index 0000000..7583c70 --- /dev/null +++ b/testsuite/compilable/assign_record_to_base.elna @@ -0,0 +1,16 @@ +type + B = record + x: Int + end + R = record(B) + y: Int + end + +var + x: ^B := nil + y: ^R := nil + +begin + x := y; + return 0 +end. diff --git a/testsuite/compilable/pointer_cast.elna b/testsuite/compilable/pointer_cast.elna new file mode 100644 index 0000000..d10fb23 --- /dev/null +++ b/testsuite/compilable/pointer_cast.elna @@ -0,0 +1,8 @@ +var + c: ^Char := nil + p: Pointer := nil + +begin + p := c; + c := p +end. -- cgit v1.2.3