From 275fa4bff6d153019b6914fed7127a7d919ca177 Mon Sep 17 00:00:00 2001 From: Eugen Wissner Date: Thu, 23 Jul 2026 01:01:03 +0200 Subject: Implement for loop --- boot/ast.cc | 54 +++++++++++ boot/evaluator.cc | 2 +- boot/lexer.ll | 6 ++ boot/name_analysis.cc | 259 ++++++++++++++++++++++++++++---------------------- boot/parser.yy | 55 ++++++----- boot/type_check.cc | 50 ++++++++++ 6 files changed, 283 insertions(+), 143 deletions(-) (limited to 'boot') diff --git a/boot/ast.cc b/boot/ast.cc index ec5b27d..a50d3d3 100644 --- a/boot/ast.cc +++ b/boot/ast.cc @@ -91,6 +91,11 @@ namespace elna::boot __builtin_unreachable(); } + void empty_visitor::visit(for_statement *) + { + __builtin_unreachable(); + } + void empty_visitor::visit(defer_statement *) { __builtin_unreachable(); @@ -286,6 +291,20 @@ namespace elna::boot } } + void walking_visitor::visit(for_statement *statement) + { + statement->initial_value().accept(this); + statement->final_value().accept(this); + if (statement->step != nullptr) + { + statement->step->accept(this); + } + for (auto *body_statement : statement->body) + { + body_statement->accept(this); + } + } + void walking_visitor::visit(defer_statement *statement) { for (auto *block_statement : statement->statements) @@ -1522,6 +1541,41 @@ namespace elna::boot } } + for_statement::for_statement(const source_position position, identifier&& control_variable, + expression *initial_value, expression *final_value, + std::vector&& body, expression *step) + : node(position), m_initial_value(initial_value), m_final_value(final_value), + control_variable(std::move(control_variable)), body(std::move(body)), step(step) + { + } + + for_statement::~for_statement() + { + delete this->m_initial_value; + delete this->m_final_value; + delete this->step; + + for (const statement *body_statement : this->body) + { + delete body_statement; + } + } + + void for_statement::accept(parser_visitor *visitor) + { + visitor->visit(this); + } + + expression& for_statement::initial_value() + { + return *this->m_initial_value; + } + + expression& for_statement::final_value() + { + return *this->m_final_value; + } + const char *print_binary_operator(const binary_operator operation) { switch (operation) diff --git a/boot/evaluator.cc b/boot/evaluator.cc index 77a56ad..4b37668 100644 --- a/boot/evaluator.cc +++ b/boot/evaluator.cc @@ -186,7 +186,7 @@ namespace elna::boot return std::visit([](auto value) -> std::optional { using T = std::decay_t; - if constexpr (std::is_integral_v) + if constexpr (std::is_integral_v && !std::is_same_v) { return constant_value{ ~value }; } diff --git a/boot/lexer.ll b/boot/lexer.ll index 9fe0398..c363ddd 100644 --- a/boot/lexer.ll +++ b/boot/lexer.ll @@ -83,6 +83,12 @@ elsif { while { return yy::parser::make_WHILE(this->location); } +for { + return yy::parser::make_FOR(this->location); +} +by { + return yy::parser::make_BY(this->location); +} do { return yy::parser::make_DO(this->location); } diff --git a/boot/name_analysis.cc b/boot/name_analysis.cc index 907f318..f932a5d 100644 --- a/boot/name_analysis.cc +++ b/boot/name_analysis.cc @@ -21,127 +21,120 @@ along with GCC; see the file COPYING3. If not see namespace elna::boot { - declaration_error::declaration_error(const kind error_kind, - const boot::identifier& identifier) - : error(identifier.position()), identifier(identifier.name()), error_kind(error_kind) + symbol_error::symbol_error(const source_position position, payload_type payload) + : error(position), payload(std::move(payload)) { } - std::string declaration_error::what() const + std::string symbol_error::what() const { - switch (this->error_kind) - { - case kind::undeclared: - return "Type '" + identifier + "' not declared"; - case kind::local_export: - return "Local symbol '" + this->identifier + "' cannot be exported"; - default: - __builtin_unreachable(); - } - } - - redefinition_error::redefinition_error(const boot::identifier& identifier, - std::optional original) - : error(identifier.position()), identifier(identifier.name()), original(original) - { - } - - std::string redefinition_error::what() const - { - return "Symbol '" + identifier + "' has been already defined"; + return std::visit([](const auto& pay) -> std::string { + using T = std::decay_t; + if constexpr (std::is_same_v) + { + return "Type '" + pay.name + "' not declared"; + } + else if constexpr (std::is_same_v) + { + return "Local symbol '" + pay.name + "' cannot be exported"; + } + else if constexpr (std::is_same_v) + { + return "Symbol '" + pay.name + "' has been already defined"; + } + }, payload); } - std::optional> redefinition_error::note() const + std::optional> symbol_error::note() const { - if (original.has_value() && original->start().available()) + if (const auto *redef = std::get_if(&payload)) { - return std::make_pair("previously declared here", *original); + if (redef->original.has_value() && redef->original->start().available()) + { + return std::make_pair("previously declared here", *redef->original); + } } return std::nullopt; } - const_array_error::const_array_error(const source_position position) - : error(position) - { - } - - std::string const_array_error::what() const - { - return "const must be written before the array size, not after"; - } - - double_const_error::double_const_error(const source_position position) - : error(position) - { - } - - std::string double_const_error::what() const - { - return "Duplicate 'const' qualifier is not allowed"; - } - - field_not_found_error::field_not_found_error(const identifier& field_name, - type composite_type) - : error(field_name.position()), field_name(field_name.name()), composite_type(std::move(composite_type)) + const_qualifier_error::const_qualifier_error(const source_position position, kind error_kind) + : error(position), error_kind(error_kind) { } - std::string field_not_found_error::what() const + std::string const_qualifier_error::what() const { - type const resolved = resolve_underlying_type(composite_type); - bool const is_enum = resolved.get() != nullptr; - bool const is_record = resolved.get() != nullptr; - - if (is_enum || is_record) + switch (error_kind) { - std::string message = is_enum ? "Enumeration" : "Record"; - - if (auto alias = composite_type.get()) - { - message += " '" + alias->name + "'"; - } - message += " does not have a "; - message += is_enum ? "member" : "field"; - message += " named '" + field_name + "'"; - return message; + case kind::array_position: + return "const must be written before the array size, not after"; + case kind::duplicate: + return "Duplicate 'const' qualifier is not allowed"; + default: + __builtin_unreachable(); } - return "Type '" + composite_type.to_string() - + "' does not have a field named '" + field_name + "'"; } - duplicate_member_error::duplicate_member_error(const boot::identifier& member_name, - type aggregate, std::optional original, - std::optional base_name) - : error(member_name.position()), member_name(member_name.name()), aggregate(std::move(aggregate)), - original(original), base_name(std::move(base_name)) + member_error::member_error(const source_position position, payload_type payload) + : error(position), payload(std::move(payload)) { } - std::string duplicate_member_error::what() const + std::string member_error::what() const { - type const resolved = resolve_underlying_type(aggregate); - bool const is_enum = resolved.get() != nullptr; - std::string const kind = is_enum ? "member" : "field"; - std::string message = is_enum ? "Enumeration" : "Record"; + return std::visit([](const auto& pay) -> std::string { + using T = std::decay_t; + if constexpr (std::is_same_v) + { + const type resolved = resolve_underlying_type(pay.composite); + const bool is_enum = resolved.get() != nullptr; + const bool is_record = resolved.get() != nullptr; - if (auto alias = aggregate.get()) - { - message += " '" + alias->name + "'"; - } - message += " already has a " + kind + " named '" + member_name + "'"; + if (is_enum || is_record) + { + std::string message = is_enum ? "Enumeration" : "Record"; + if (auto alias = pay.composite.template get()) + { + message += " '" + alias->name + "'"; + } + message += " does not have a "; + message += is_enum ? "member" : "field"; + message += " named '" + pay.name + "'"; + return message; + } + return "Type '" + pay.composite.to_string() + + "' does not have a field named '" + pay.name + "'"; + } + else if constexpr (std::is_same_v) + { + const type resolved = resolve_underlying_type(pay.aggregate); + const bool is_enum = resolved.get() != nullptr; + const std::string kind = is_enum ? "member" : "field"; + std::string message = is_enum ? "Enumeration" : "Record"; - if (base_name.has_value()) - { - message += " (defined in base type '" + *base_name + "')"; - } - return message; + if (auto alias = pay.aggregate.template get()) + { + message += " '" + alias->name + "'"; + } + message += " already has a " + kind + " named '" + pay.name + "'"; + + if (pay.base.has_value()) + { + message += " (defined in base type '" + *pay.base + "')"; + } + return message; + } + }, payload); } - std::optional> duplicate_member_error::note() const + std::optional> member_error::note() const { - if (original.has_value() && original->start().available()) + if (const auto *dup = std::get_if(&payload)) { - return std::make_pair("previously declared here", *original); + if (dup->original.has_value() && dup->original->start().available()) + { + return std::make_pair("previously declared here", *dup->original); + } } return std::nullopt; } @@ -231,7 +224,7 @@ namespace elna::boot type name_analysis_visitor::lookup_field(const type& composite_type, const std::string& field_name) { - type const resolved_type = resolve_underlying_type(composite_type); + const type resolved_type = resolve_underlying_type(composite_type); if (auto record = resolved_type.get()) { @@ -287,7 +280,8 @@ namespace elna::boot if (this->current_type.get() != nullptr) { - add_error(expression->position()); + add_error(expression->position(), + const_qualifier_error::kind::duplicate); } this->current_type = type(std::make_shared(this->current_type)); } @@ -298,7 +292,8 @@ namespace elna::boot if (this->current_type.get() != nullptr) { - add_error(expression->position()); + add_error(expression->position(), + const_qualifier_error::kind::array_position); } this->current_type = type(std::make_shared(this->current_type, expression->size)); } @@ -355,8 +350,9 @@ namespace elna::boot base_name = alias->name; } } - add_error(field_name, aggregate, - existing->second.declaration, base_name); + add_error(field_name.position(), + member_error::duplicate{.name = field_name.name(), .aggregate = aggregate, + .original = existing->second.declaration, .base = base_name}); } else { @@ -392,8 +388,8 @@ namespace elna::boot } else { - add_error(declaration_error::kind::undeclared, - expression->base.value()); + add_error(expression->base.value().position(), + symbol_error::undeclared{.name = expression->base.value().name()}); this->current_type = type(); return; } @@ -421,8 +417,8 @@ namespace elna::boot } else { - add_error(declaration_error::kind::undeclared, - expression->type_name); + add_error(expression->type_name.position(), + symbol_error::undeclared{.name = expression->type_name.name()}); } for (const field_initializer& initializer : expression->field_initializers) { @@ -430,8 +426,8 @@ namespace elna::boot if (!expression->type_decoration.empty() && lookup_field(expression->type_decoration, initializer.name()).empty()) { - add_error(declaration_error::kind::undeclared, - initializer.id()); + add_error(initializer.id().position(), + symbol_error::undeclared{.name = initializer.id().name()}); } } } @@ -485,14 +481,16 @@ for (const auto& member : expression->members) std::shared_ptr const result_type = std::make_shared( member_names); std::map seen; - type const aggregate(result_type); + const type aggregate(result_type); for (const auto& member : expression->members) { auto existing = seen.find(member.name()); if (existing != seen.end()) { - add_error(member, aggregate, existing->second); + add_error(member.position(), + member_error::duplicate{.name = member.name(), .aggregate = aggregate, + .original = existing->second, .base = std::nullopt}); } else { @@ -511,8 +509,8 @@ for (const auto& member : expression->members) if (!this->bag.enter(name, variable_symbol)) { auto original = this->bag.lookup(name); - add_error(boot::identifier(name, position), - original->position); + add_error(position, + symbol_error::redefinition{.name = name, .original = original->position}); } return variable_symbol; } @@ -642,7 +640,7 @@ for (const auto& member : expression->members) if (!trait->type_decoration.empty()) { - type const resolved = resolve_underlying_type(trait->type_decoration); + const type resolved = resolve_underlying_type(trait->type_decoration); if (resolved.get() == nullptr && !is_primitive_type(resolved, "Float") @@ -656,8 +654,8 @@ for (const auto& member : expression->members) } else { - add_error(declaration_error::kind::undeclared, - trait->name); + add_error(trait->name.position(), + symbol_error::undeclared{.name = trait->name.name()}); } } @@ -742,7 +740,9 @@ for (const auto& member : expression->members) } if (expression->type_decoration.empty()) { - add_error(expression->field(), expression->base().type_decoration); + add_error(expression->field().position(), + member_error::not_found{.name = expression->field().name(), + .composite = expression->base().type_decoration}); } else { @@ -761,6 +761,27 @@ for (const auto& member : expression->members) } } + void name_analysis_visitor::visit(for_statement *statement) + { + statement->initial_value().accept(this); + const type control_variable_type = type(std::make_shared(this->current_type)); + auto initial_value_info = std::make_shared(control_variable_type, false); + + statement->final_value().accept(this); + if (statement->step != nullptr) + { + statement->step->accept(this); + } + statement->symbols = this->bag.enter(); + + this->bag.enter(statement->control_variable.name(), initial_value_info); + for (auto *body_statement : statement->body) + { + body_statement->accept(this); + } + this->bag.leave(); + } + void name_analysis_visitor::visit(cast_expression *expression) { walking_visitor::visit(expression); @@ -792,29 +813,33 @@ for (const auto& member : expression->members) } else { - add_error(declaration_error::kind::undeclared, - boot::identifier(expression->name, expression->position())); + add_error(expression->position(), + symbol_error::undeclared{.name = expression->name}); } } void name_analysis_visitor::visit(literal *literal) { literal->type_decoration = lookup_primitive_type("Int"); + this->current_type = literal->type_decoration; } void name_analysis_visitor::visit(literal *literal) { literal->type_decoration = lookup_primitive_type("Word"); + this->current_type = literal->type_decoration; } void name_analysis_visitor::visit(literal *literal) { literal->type_decoration = lookup_primitive_type("Float"); + this->current_type = literal->type_decoration; } void name_analysis_visitor::visit(literal *literal) { literal->type_decoration = lookup_primitive_type("Bool"); + this->current_type = literal->type_decoration; } void name_analysis_visitor::visit(literal *literal) @@ -831,6 +856,7 @@ for (const auto& member : expression->members) { literal->type_decoration = type(std::make_shared( type(std::make_shared(lookup_primitive_type("Char"))))); + this->current_type = literal->type_decoration; } declaration_visitor::declaration_visitor() @@ -864,8 +890,9 @@ for (const auto& member : expression->members) if (!this->unresolved.insert({ type_identifier, std::make_shared(type_identifier) }).second) { - add_error(declaration->identifier.id(), - declaration->position()); + add_error(declaration->identifier.id().position(), + symbol_error::redefinition{.name = declaration->identifier.id().name(), + .original = declaration->position()}); } } @@ -887,8 +914,8 @@ for (const auto& member : expression->members) { if (variable_identifier.exported()) { - add_error(declaration_error::kind::local_export, - variable_identifier.id()); + add_error(variable_identifier.id().position(), + symbol_error::local_export{.name = variable_identifier.id().name()}); } } } diff --git a/boot/parser.yy b/boot/parser.yy index bc2ddb5..7d93dde 100644 --- a/boot/parser.yy +++ b/boot/parser.yy @@ -99,12 +99,8 @@ along with GCC; see the file COPYING3. If not see TYPE "type" RECORD "record" EXTERN "extern" - IF "if" - WHILE "while" - DO "do" - THEN "then" - ELSE "else" - ELSIF "elsif" + IF "if" THEN "then" ELSE "else" ELSIF "elsif" + WHILE "while" DO "do" FOR "for" BY "by" RETURN "return" IMPORT "import" BEGIN_BLOCK "begin" @@ -135,7 +131,7 @@ along with GCC; see the file COPYING3. If not see %type type_expression; %type > type_expressions; %type traits_expression; -%type expression operand simple_expression procedure_return; +%type expression operand simple_expression procedure_return by_step; %type unary_expression; %type binary_expression; %type > expressions actual_parameter_list; @@ -156,7 +152,6 @@ along with GCC; see the file COPYING3. If not see %type > field_initializers; %type > elsif_then_statements elsif_do_statements; %type *> else_statements; -%type cast_expression; %type > identifier; %type > identifier_definition; %type > identifier_definitions; @@ -175,7 +170,7 @@ procedure_body: { $$ = std::make_unique($1, $2, $3); } statement_part: - /* no statements */ {} + %empty {} | "begin" statements { $$ = $2; } identifier: IDENTIFIER { $$ = std::make_unique($1, boot::make_position(@1)); } @@ -190,7 +185,7 @@ identifier_definitions: } | identifier_definition { $$.emplace_back(std::move(*$1)); } return_declaration: - /* proper procedure */ {} + %empty {} | ":" "!" { $$ = boot::procedure_type_expression::return_t(std::monostate{}); } | ":" type_expression { $$ = boot::procedure_type_expression::return_t($2); } procedure_heading: "(" optional_fields ")" return_declaration @@ -205,7 +200,7 @@ procedure_declaration: $$ = new boot::procedure_declaration(boot::make_position(@$), std::move(*$2), $3); } procedure_part: - /* no procedure declarations */ {} + %empty {} | procedure_declaration procedure_part { $$ = $2; @@ -215,8 +210,9 @@ call_expression: designator_expression actual_parameter_list { $$ = new boot::procedure_call(boot::make_position(@$), $1, $2); } -cast_expression: "cast" "(" expression ":" type_expression ")" - { $$ = new boot::cast_expression(boot::make_position(@$), $5, $3); } +by_step: + "by" expression { $$ = $2; } + | %empty { $$ = nullptr; } elsif_do_statements: "elsif" expression "do" statements elsif_do_statements { @@ -224,10 +220,10 @@ elsif_do_statements: $$ = $5; $$.emplace($$.begin(), branch); } - | /* no branches */ {} + | %empty {} else_statements: "else" statements { $$ = new std::vector($2); } - | { $$ = nullptr; } + | %empty { $$ = nullptr; } elsif_then_statements: "elsif" expression "then" statements elsif_then_statements { @@ -235,7 +231,7 @@ elsif_then_statements: $$ = $5; $$.emplace($$.begin(), branch); } - | /* no branches */ {} + | %empty {} procedure_return: "return" expression { $$ = $2; } | "return" { $$ = nullptr; } @@ -257,7 +253,10 @@ simple_expression: literal { $$ = $1; } | designator_expression { $$ = $1; } | traits_expression { $$ = $1; } - | cast_expression { $$ = $1; } + | "cast" "(" expression ":" type_expression ")" + { + $$ = new boot::cast_expression(boot::make_position(@$), $5, $3); + } | call_expression { $$ = $1; } | "(" expression ")" { $$ = $2; } | identifier "{" field_initializers "}" @@ -363,6 +362,7 @@ expressions: $$.emplace($$.cbegin(), $1); } | expression { $$.push_back($1); } + | %empty { $$ = std::vector(); } type_expressions: type_expression "," type_expressions { @@ -389,6 +389,10 @@ statement: boot::conditional_statements *body = new boot::conditional_statements($2, $4); $$ = new boot::while_statement(boot::make_position(@$), body, $5); } + | "for" identifier ":=" expression "to" expression by_step "do" statements "end" + { + $$ = new boot::for_statement(boot::make_position(@$), std::move(*$2), $4, $6, $9, $7); + } | "if" expression "then" statements elsif_then_statements else_statements "end" { boot::conditional_statements *then = new boot::conditional_statements($2, $4); @@ -399,7 +403,7 @@ statement: { $$ = new boot::defer_statement(boot::make_position(@$), $2); } | "case" expression "of" switch_cases else_statements "end" { $$ = new boot::case_statement(boot::make_position(@$), $2, $4, $5); } - | { $$ = new boot::empty_statement(boot::make_position(@$)); } + | %empty { $$ = new boot::empty_statement(boot::make_position(@$)); } switch_case: case_labels ":" statements { $$ = { .labels = $1, .statements = $3 }; } switch_cases: @@ -434,7 +438,7 @@ required_fields: | field_declaration { $$.emplace_back($1); } optional_fields: required_fields { $$ = $1; } - | /* no fields */ {} + | %empty {} field_initializer: identifier ":" expression { $$ = std::make_unique(std::move(*$1), $3); } field_initializers: @@ -505,14 +509,14 @@ variable_declaration: $$ = new boot::variable_declaration( boot::make_position(@$), $1, shared_type, $5); } variable_declarations: - /* no variable declarations */ {} + %empty {} | variable_declaration variable_declarations { $$ = $2; $$.insert(std::cbegin($$), $1); } variable_part: - /* no variable declarations */ {} + %empty {} | "var" variable_declarations { $$ = $2; } import_declaration: IDENTIFIER "." import_declaration @@ -532,7 +536,7 @@ import_declarations: $$.emplace_back(new boot::import_declaration(boot::make_position(@$), $1)); } import_part: - /* no import declarations */ {} + %empty {} | "import" import_declarations { $$ = $2; } type_declaration: identifier_definition "=" type_expression { @@ -544,13 +548,12 @@ type_declarations: $$ = $2; $$.insert($$.cbegin(), $1); } - | /* no type definitions */ {} + | %empty {} type_part: - /* no type definitions */ {} + %empty {} | "type" type_declarations { $$ = $2; } actual_parameter_list: - "(" ")" {} - | "(" expressions ")" { $$ = $2; } + "(" expressions ")" { $$ = $2; } %% void yy::parser::error(const location_type& loc, const std::string& message) diff --git a/boot/type_check.cc b/boot/type_check.cc index 7989925..1689e65 100644 --- a/boot/type_check.cc +++ b/boot/type_check.cc @@ -465,6 +465,42 @@ namespace elna::boot } } + void type_analysis_visitor::visit(for_statement *statement) + { + statement->initial_value().accept(this); + type const initial_type = resolve_underlying_type(statement->initial_value().type_decoration); + + if (!is_integral_type(initial_type)) + { + add_error( + statement->initial_value().position(), + statement->initial_value().type_decoration); + } + statement->final_value().accept(this); + if (!is_assignable_from(initial_type, statement->final_value().type_decoration)) + { + add_error( + statement->final_value().position(), + initial_type, statement->final_value().type_decoration); + } + if (statement->step != nullptr) + { + statement->step->accept(this); + if (!is_assignable_from(initial_type, statement->step->type_decoration)) + { + add_error( + statement->step->position(), + initial_type, statement->step->type_decoration); + } + } + this->bag.enter(statement->symbols); + for (auto *body_statement : statement->body) + { + body_statement->accept(this); + } + this->bag.leave(); + } + void type_analysis_visitor::visit(type_declaration *declaration) { std::vector alias_path; @@ -649,6 +685,18 @@ namespace elna::boot } } + for_loop_type_error::for_loop_type_error(const source_position position, + type actual) + : error(position), actual(std::move(actual)) + { + } + + std::string for_loop_type_error::what() const + { + return "for-loop variable must be Int or Word, but got '" + + actual.to_string() + "'"; + } + binary_operation_error::binary_operation_error(const source_position position, type left, type right, binary_operator operation) : error(position), left(std::move(left)), right(std::move(right)), op(operation) @@ -749,6 +797,8 @@ namespace elna::boot valid = is_integral_type(lhs_resolved) && is_primitive_type(rhs_resolved, "Word"); break; + default: + __builtin_unreachable(); } if (!valid) { -- cgit v1.2.3