From 8ceb48a4e60b8ff89f28372da4f66cd152e2e0fe Mon Sep 17 00:00:00 2001 From: Eugen Wissner Date: Mon, 13 Jul 2026 23:59:36 +0200 Subject: Change record constructor syntax --- boot/ast.cc | 33 +++++++++++ boot/lexer.ll | 6 ++ boot/parser.yy | 16 ++++++ boot/semantic.cc | 80 ++++++++++++++------------- doc/appendix.tex | 13 +++-- doc/type-system.tex | 4 +- gcc/gcc/elna-generic.cc | 24 ++++++++ include/elna/boot/ast.h | 25 +++++++++ include/elna/boot/semantic.h | 6 +- include/elna/gcc/elna-generic.h | 1 + testsuite/compilable/record_construction.elna | 12 ++++ 11 files changed, 171 insertions(+), 49 deletions(-) create mode 100644 testsuite/compilable/record_construction.elna diff --git a/boot/ast.cc b/boot/ast.cc index 118d92c..36215c1 100644 --- a/boot/ast.cc +++ b/boot/ast.cc @@ -114,6 +114,11 @@ namespace elna::boot __builtin_unreachable(); } + void empty_visitor::visit(record_constructor_expression *) + { + __builtin_unreachable(); + } + void empty_visitor::visit(traits_expression *) { __builtin_unreachable(); @@ -403,6 +408,14 @@ namespace elna::boot expression->target().accept(this); } + void walking_visitor::visit(record_constructor_expression *expression) + { + for (const field_initializer& initializer : expression->field_initializers) + { + initializer.value->accept(this); + } + } + void walking_visitor::visit(traits_expression *trait) { if (!trait->parameters.empty()) @@ -623,6 +636,26 @@ namespace elna::boot return this; } + record_constructor_expression::record_constructor_expression(const struct position position, + std::string&& type_name, + std::vector&& field_initializers) + : node(position), type_name(std::move(type_name)), field_initializers(std::move(field_initializers)) + { + } + + void record_constructor_expression::accept(parser_visitor *visitor) + { + visitor->visit(this); + } + + record_constructor_expression::~record_constructor_expression() + { + for (const field_initializer& initializer : field_initializers) + { + delete initializer.value; + } + } + variable_declaration::variable_declaration(const struct position position, std::vector&& identifier, std::shared_ptr variable_type, expression *initializer) diff --git a/boot/lexer.ll b/boot/lexer.ll index 05015f6..4367caa 100644 --- a/boot/lexer.ll +++ b/boot/lexer.ll @@ -236,6 +236,12 @@ of { \] { return yy::parser::make_RIGHT_SQUARE(this->location); } +\{ { + return yy::parser::make_LEFT_BRACE(this->location); +} +\} { + return yy::parser::make_RIGHT_BRACE(this->location); +} \<\< { return yy::parser::make_SHIFT_LEFT(this->location); } diff --git a/boot/parser.yy b/boot/parser.yy index f1bf915..ae2e5a8 100644 --- a/boot/parser.yy +++ b/boot/parser.yy @@ -81,6 +81,7 @@ along with GCC; see the file COPYING3. If not see %token STRING %token BOOLEAN %token LEFT_PAREN "(" RIGHT_PAREN ")" LEFT_SQUARE "[" RIGHT_SQUARE "]" +%token LEFT_BRACE "{" RIGHT_BRACE "}" %token ASSIGNMENT ":=" EXCLAMATION "!" AT "@" HAT "^" @@ -148,6 +149,8 @@ along with GCC; see the file COPYING3. If not see %type > procedure_body; %type field_declaration; %type > optional_fields required_fields; +%type field_initializer; +%type > field_initializers; %type > elsif_then_statements elsif_do_statements; %type *> else_statements; %type cast_expression; @@ -258,6 +261,10 @@ simple_expression: | cast_expression { $$ = $1; } | call_expression { $$ = $1; } | "(" expression ")" { $$ = $2; } + | IDENTIFIER "{" field_initializers "}" + { + $$ = new boot::record_constructor_expression(boot::make_position(@1), std::move($1), std::move($3)); + } operand: unary_expression { $$ = $1; } | simple_expression { $$ = $1; } @@ -421,6 +428,15 @@ required_fields: optional_fields: required_fields { std::swap($$, $1); } | /* no fields */ {} +field_initializer: + IDENTIFIER ":" expression { $$.name = std::move($1); $$.value = $3; } +field_initializers: + field_initializer "," field_initializers + { + std::swap($$, $3); + $$.emplace($$.cbegin(), $1); + } + | field_initializer { $$.push_back($1); } type_expression: "[" INTEGER "]" type_expression { diff --git a/boot/semantic.cc b/boot/semantic.cc index 97c316a..f46dce9 100644 --- a/boot/semantic.cc +++ b/boot/semantic.cc @@ -288,37 +288,6 @@ 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); @@ -346,16 +315,30 @@ namespace elna::boot 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); + void type_analysis_visitor::visit(record_constructor_expression *expression) + { + auto record = inner_aliased_type(expression->type_decoration).get(); - if (call->arguments.size() != expected) + if (record == nullptr) + { + add_error(type_expectation_error::kind::result, expression->position()); + return; + } + for (const field_initializer& initializer : expression->field_initializers) + { + for (const type_field& field : record->fields) { - add_error(expected, call->arguments.size(), call->position()); + if (field.first == initializer.name) + { + if (!is_assignable_from(field.second, initializer.value->type_decoration)) + { + add_error(type_expectation_error::kind::argument, + initializer.value->position()); + } + break; + } } } } @@ -584,6 +567,27 @@ namespace elna::boot this->current_type = type(result_type); } + void name_analysis_visitor::visit(record_constructor_expression *expression) + { + if (auto type_symbol = this->bag.lookup(expression->type_name)) + { + if (auto type_info = type_symbol->is_type()) + { + expression->type_decoration = type_info->symbol; + } + } + for (const field_initializer& initializer : expression->field_initializers) + { + initializer.value->accept(this); + if (!expression->type_decoration.empty() + && lookup_field(expression->type_decoration, initializer.name).empty()) + { + add_error(declaration_error::kind::undeclared, + initializer.name, expression->position()); + } + } + } + void name_analysis_visitor::visit(procedure_type_expression *expression) { std::shared_ptr result_type = diff --git a/doc/appendix.tex b/doc/appendix.tex index e98da0c..71b0cc4 100644 --- a/doc/appendix.tex +++ b/doc/appendix.tex @@ -73,8 +73,13 @@ \alt{} \alt{} \alt{} + \alt{} \alt{} `(' `)'. + = `\{' [ \{`,' \}] `\}'. + + = `:' . + = | . = \{ \}. @@ -142,9 +147,7 @@ | | | | | | $\varepsilon{}$. - = [`begin' - \alt{} `return' - \alt{} `begin' `;' `return' ]. + = [`begin' ]. = `:=' . @@ -167,7 +170,7 @@ = `(' [ \{`;' \}] `)' . - = `end'. + = [`return' ] `end'. = `proc' \\ ( | `extern'). @@ -175,5 +178,5 @@ = \{\} - `end' `.\@'. + [`return' ] `end' `.\@'. \end{grammar} diff --git a/doc/type-system.tex b/doc/type-system.tex index e1497be..102b0e4 100644 --- a/doc/type-system.tex +++ b/doc/type-system.tex @@ -65,7 +65,7 @@ end. = `(' [ \{`;' \}] `)' . - = `end'. + = [`return' ] `end'. = `proc' \\ ( | `extern'). @@ -109,7 +109,7 @@ type var u: U; begin - u := U(0, 1, 2); + u := U{x: 0, y: 1, z: 2}; u.x := 3 end. \end{lstlisting} diff --git a/gcc/gcc/elna-generic.cc b/gcc/gcc/elna-generic.cc index 30dd68b..5749bbc 100644 --- a/gcc/gcc/elna-generic.cc +++ b/gcc/gcc/elna-generic.cc @@ -178,6 +178,30 @@ namespace elna::gcc } } + void generic_visitor::visit(boot::record_constructor_expression *expression) + { + tree type_decl = this->symbols->lookup(expression->type_name); + + if (type_decl == NULL_TREE) + { + error_at(get_location(&expression->position()), "Type '%s' not declared", + expression->type_name.c_str()); + this->current_expression = error_mark_node; + return; + } + tree record_type = TREE_TYPE(type_decl); + vec *tree_arguments = nullptr; + + for (const boot::field_initializer& initializer : expression->field_initializers) + { + tree field_decl = find_field_by_name(get_location(&expression->position()), + record_type, initializer.name); + initializer.value->accept(this); + CONSTRUCTOR_APPEND_ELT(tree_arguments, field_decl, this->current_expression); + } + this->current_expression = build_constructor(record_type, tree_arguments); + } + void generic_visitor::visit(boot::unit *unit) { for (boot::import_declaration *const declaration : unit->imports) diff --git a/include/elna/boot/ast.h b/include/elna/boot/ast.h index ab79c30..67e9fbe 100644 --- a/include/elna/boot/ast.h +++ b/include/elna/boot/ast.h @@ -60,6 +60,8 @@ namespace elna::boot class type_declaration; class procedure_call; class cast_expression; + class record_constructor_expression; + struct field_initializer; class assign_statement; class if_statement; class import_declaration; @@ -97,6 +99,7 @@ namespace elna::boot virtual void visit(type_declaration *) = 0; virtual void visit(procedure_call *) = 0; virtual void visit(cast_expression *) = 0; + virtual void visit(record_constructor_expression *) = 0; virtual void visit(traits_expression *) = 0; virtual void visit(assign_statement *) = 0; virtual void visit(if_statement *) = 0; @@ -152,6 +155,7 @@ namespace elna::boot [[noreturn]] virtual void visit(procedure_call *) override; [[noreturn]] virtual void visit(unit *) override; [[noreturn]] virtual void visit(cast_expression *) override; + [[noreturn]] virtual void visit(record_constructor_expression *) override; [[noreturn]] virtual void visit(traits_expression *) override; [[noreturn]] virtual void visit(binary_expression *) override; [[noreturn]] virtual void visit(unary_expression *) override; @@ -194,6 +198,7 @@ namespace elna::boot virtual void visit(procedure_call *call) override; virtual void visit(unit *unit) override; virtual void visit(cast_expression *expression) override; + virtual void visit(record_constructor_expression *expression) override; virtual void visit(traits_expression *trait) override; virtual void visit(binary_expression *expression) override; virtual void visit(unary_expression *expression) override; @@ -325,6 +330,26 @@ namespace elna::boot record_type_expression *is_record() override; }; + struct field_initializer + { + std::string name; + expression *value; + }; + + class record_constructor_expression : public expression + { + public: + const std::string type_name; + const std::vector field_initializers; + + record_constructor_expression(const struct position position, + std::string&& type_name, + std::vector&& field_initializers); + void accept(parser_visitor *visitor) override; + + virtual ~record_constructor_expression() override; + }; + /** * Enumeration type. */ diff --git a/include/elna/boot/semantic.h b/include/elna/boot/semantic.h index 1e81ebb..5ef4bc8 100644 --- a/include/elna/boot/semantic.h +++ b/include/elna/boot/semantic.h @@ -150,10 +150,6 @@ namespace elna::boot 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); @@ -165,6 +161,7 @@ namespace elna::boot void visit(record_type_expression *expression) override; void visit(procedure_call *call) override; void visit(case_statement *statement) override; + void visit(record_constructor_expression *expression) override; }; /** @@ -195,6 +192,7 @@ namespace elna::boot void visit(pointer_type_expression *expression) override; void visit(type_declaration *declaration) override; void visit(record_type_expression *expression) override; + void visit(record_constructor_expression *expression) override; void visit(procedure_type_expression *expression) override; void visit(enumeration_type_expression *expression) override; diff --git a/include/elna/gcc/elna-generic.h b/include/elna/gcc/elna-generic.h index 2fe4d48..f30f9d6 100644 --- a/include/elna/gcc/elna-generic.h +++ b/include/elna/gcc/elna-generic.h @@ -83,6 +83,7 @@ namespace elna::gcc void visit(boot::field_access_expression *expression) override; void visit(boot::dereference_expression *expression) override; void visit(boot::unit *unit) override; + void visit(boot::record_constructor_expression *expression) override; void visit(boot::assign_statement *statement) override; void visit(boot::if_statement *statement) override; void visit(boot::import_declaration *) override; diff --git a/testsuite/compilable/record_construction.elna b/testsuite/compilable/record_construction.elna new file mode 100644 index 0000000..00e60db --- /dev/null +++ b/testsuite/compilable/record_construction.elna @@ -0,0 +1,12 @@ +type + R = record + x, y: Int + end + +var + r: R := R{x: 1, y: 2} + +begin + assert(r.x = 1 & r.y = 2) +return 0 +end. -- cgit v1.2.3