From 0c2a3963205d8c1eff33cdd927cdfc4090e5e31c Mon Sep 17 00:00:00 2001 From: Eugen Wissner Date: Tue, 19 Aug 2025 22:58:39 +0200 Subject: [PATCH] Support variable declaration initializer --- README.md | 7 +++ boot/ast.cc | 7 +-- boot/dependency.cc | 2 +- boot/parser.yy | 26 +++++----- boot/semantic.cc | 43 ++++++++++++++++- gcc/elna-generic.cc | 79 +++++++++++++++++++------------ include/elna/boot/ast.h | 9 ++-- include/elna/boot/semantic.h | 10 ++++ rakelib/boot.rake | 2 +- source/main.elna | 92 +++++++++++++++++------------------- 10 files changed, 175 insertions(+), 102 deletions(-) diff --git a/README.md b/README.md index 553df60..a157d30 100644 --- a/README.md +++ b/README.md @@ -41,5 +41,12 @@ and Mac OS. In the latter case GCC is patched with the patches used by Homebrew rake boot ``` +`gcc` binary is used by default, but a different gcc version can be specified +by passing `CC` and `CXX` environment variables to rake, e.g.: + +```sh +rake CC=gcc-15 CXX=g++-15 boot +``` + See `rake -T` for more tasks. The GCC source is under `build/tools`. The installation path is `build/host/install`. diff --git a/boot/ast.cc b/boot/ast.cc index a0c21ed..3014848 100644 --- a/boot/ast.cc +++ b/boot/ast.cc @@ -408,9 +408,10 @@ namespace elna::boot return this; } - variable_declaration::variable_declaration(const struct position position, identifier_definition identifier, - std::shared_ptr variable_type) - : declaration(position, identifier), m_variable_type(variable_type) + variable_declaration::variable_declaration(const struct position position, + std::vector&& identifier, std::shared_ptr variable_type, + expression *body) + : node(position), m_variable_type(variable_type), identifiers(std::move(identifier)), body(body) { } diff --git a/boot/dependency.cc b/boot/dependency.cc index 07f3525..88e9bd5 100644 --- a/boot/dependency.cc +++ b/boot/dependency.cc @@ -53,7 +53,7 @@ namespace elna::boot if (!declaration_visitor.errors().empty()) { - std::swap(outcome.errors(), parse_driver.errors()); + std::swap(outcome.errors(), declaration_visitor.errors()); } outcome.unresolved = declaration_visitor.unresolved; diff --git a/boot/parser.yy b/boot/parser.yy index af3f7a3..4e09e53 100644 --- a/boot/parser.yy +++ b/boot/parser.yy @@ -135,7 +135,8 @@ along with GCC; see the file COPYING3. If not see %type > switch_cases; %type constant_declaration; %type > constant_part constant_declarations; -%type > variable_declarations variable_part variable_declaration; +%type variable_declaration; +%type > variable_declarations variable_part; %type type_expression; %type > type_expressions; %type traits_expression; @@ -501,24 +502,23 @@ identifiers: $$.emplace($$.cbegin(), std::move($1)); } | IDENTIFIER { $$.emplace_back(std::move($1)); } -variable_declaration: identifier_definitions ":" type_expression ";" +variable_declaration: + identifier_definitions ":" type_expression ";" { std::shared_ptr shared_type{ $3 }; - - for (boot::identifier_definition& identifier : $1) - { - boot::variable_declaration *declaration = new boot::variable_declaration( - boot::make_position(@2), std::move(identifier), shared_type); - $$.push_back(declaration); - } + $$ = new boot::variable_declaration( boot::make_position(@2), std::move($1), shared_type); + } + | identifier_definitions ":" type_expression ":=" expression ";" + { + std::shared_ptr shared_type{ $3 }; + $$ = new boot::variable_declaration( boot::make_position(@2), std::move($1), shared_type, $5); } variable_declarations: /* no variable declarations */ {} | variable_declaration variable_declarations { - std::swap($$, $1); - $$.reserve($$.size() + $2.size()); - $$.insert(std::end($$), std::begin($2), std::end($2)); + std::swap($$, $2); + $$.insert(std::cbegin($$), $1); } variable_part: /* no variable declarations */ {} @@ -531,7 +531,7 @@ constant_declarations: constant_declaration constant_declarations { std::swap($$, $2); - $$.insert($$.cbegin(), $1); + $$.insert(std::cbegin($$), $1); } | /* no constant definitions */ {} constant_part: diff --git a/boot/semantic.cc b/boot/semantic.cc index 79f2047..25c44ed 100644 --- a/boot/semantic.cc +++ b/boot/semantic.cc @@ -83,6 +83,16 @@ namespace elna::boot return "Procedure '" + identifier + "' is expected to return, but does not have a return statement"; } + variable_initializer_error::variable_initializer_error(const char *path, const struct position position) + : error(path, position) + { + } + + std::string variable_initializer_error::what() const + { + return "Only one variable can be initialized"; + } + type_analysis_visitor::type_analysis_visitor(const char *path) : error_container(path) { @@ -289,7 +299,10 @@ namespace elna::boot { declaration->variable_type().accept(this); - this->bag.enter(declaration->identifier.identifier, std::make_shared(this->current_type)); + for (const auto& variable_identifier : declaration->identifiers) + { + this->bag.enter(variable_identifier.identifier, std::make_shared(this->current_type)); + } } void name_analysis_visitor::visit(constant_declaration *definition) @@ -584,5 +597,33 @@ namespace elna::boot add_error(type->identifier.identifier, this->input_file, type->position()); } } + for (variable_declaration *const variable : unit->variables) + { + variable->accept(this); + } + for (procedure_declaration *const procedure : unit->procedures) + { + procedure->accept(this); + } + } + + void declaration_visitor::visit(variable_declaration *declaration) + { + if (declaration->body != nullptr && declaration->identifiers.size() > 1) + { + add_error(this->input_file, declaration->position()); + } + } + + void declaration_visitor::visit(procedure_declaration *definition) + { + if (!definition->body.has_value()) + { + return; + } + for (boot::variable_declaration *const variable : definition->body.value().variables()) + { + variable->accept(this); + } } } diff --git a/gcc/elna-generic.cc b/gcc/elna-generic.cc index 2a23311..779f2fe 100644 --- a/gcc/elna-generic.cc +++ b/gcc/elna-generic.cc @@ -745,40 +745,57 @@ namespace elna::gcc void generic_visitor::visit(boot::variable_declaration *declaration) { - this->current_expression = get_inner_alias( - this->bag.lookup(declaration->identifier.identifier)->is_variable()->symbol, - this->symbols); + for (const auto& variable_identifier : declaration->identifiers) + { + this->current_expression = get_inner_alias( + this->bag.lookup(variable_identifier.identifier)->is_variable()->symbol, + this->symbols); - location_t declaration_location = get_location(&declaration->position()); - tree declaration_tree = build_decl(declaration_location, VAR_DECL, - get_identifier(declaration->identifier.identifier.c_str()), this->current_expression); - bool result = this->symbols->enter(declaration->identifier.identifier, declaration_tree); + location_t declaration_location = get_location(&declaration->position()); + tree declaration_tree = build_decl(declaration_location, VAR_DECL, + get_identifier(variable_identifier.identifier.c_str()), this->current_expression); + bool result = this->symbols->enter(variable_identifier.identifier, declaration_tree); - if (POINTER_TYPE_P(this->current_expression)) - { - DECL_INITIAL(declaration_tree) = elna_pointer_nil_node; - } - TREE_PUBLIC(declaration_tree) = declaration->identifier.exported; - this->current_expression = NULL_TREE; - if (!result) - { - error_at(declaration_location, "Variable '%s' already declared in this scope", - declaration->identifier.identifier.c_str()); - } - else if (lang_hooks.decls.global_bindings_p()) - { - TREE_STATIC(declaration_tree) = 1; - varpool_node::get_create(declaration_tree); - varpool_node::finalize_decl(declaration_tree); - } - else - { - DECL_CONTEXT(declaration_tree) = current_function_decl; - f_names = chainon(f_names, declaration_tree); + if (declaration->body != nullptr) + { + declaration->body->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()); + } + } + else if (POINTER_TYPE_P(this->current_expression)) + { + DECL_INITIAL(declaration_tree) = elna_pointer_nil_node; + } + TREE_PUBLIC(declaration_tree) = variable_identifier.exported; + this->current_expression = NULL_TREE; + if (!result) + { + error_at(declaration_location, "Variable '%s' already declared in this scope", + variable_identifier.identifier.c_str()); + } + else if (lang_hooks.decls.global_bindings_p()) + { + TREE_STATIC(declaration_tree) = 1; + varpool_node::get_create(declaration_tree); + varpool_node::finalize_decl(declaration_tree); + } + else + { + DECL_CONTEXT(declaration_tree) = current_function_decl; + f_names = chainon(f_names, declaration_tree); - auto declaration_statement = build1_loc(declaration_location, DECL_EXPR, - void_type_node, declaration_tree); - append_statement(declaration_statement); + auto declaration_statement = build1_loc(declaration_location, DECL_EXPR, + void_type_node, declaration_tree); + append_statement(declaration_statement); + } } } diff --git a/include/elna/boot/ast.h b/include/elna/boot/ast.h index 2f015df..7da9b53 100644 --- a/include/elna/boot/ast.h +++ b/include/elna/boot/ast.h @@ -340,17 +340,20 @@ namespace elna::boot /** * Variable declaration. */ - class variable_declaration : public declaration + class variable_declaration : public node { std::shared_ptr m_variable_type; public: - variable_declaration(const struct position position, identifier_definition identifier, - std::shared_ptr variable_type); + variable_declaration(const struct position position, + std::vector&& identifier, std::shared_ptr variable_type, + expression *body = nullptr); void accept(parser_visitor *visitor) override; + const std::vector identifiers; type_expression& variable_type(); + expression *const body; }; /** diff --git a/include/elna/boot/semantic.h b/include/elna/boot/semantic.h index ecedb27..6648d60 100644 --- a/include/elna/boot/semantic.h +++ b/include/elna/boot/semantic.h @@ -79,6 +79,14 @@ namespace elna::boot std::string what() const override; }; + class variable_initializer_error : public error + { + public: + variable_initializer_error(const char *path, const struct position position); + + std::string what() const override; + }; + /** * Checks types. */ @@ -174,5 +182,7 @@ namespace elna::boot void visit(program *program) override; void visit(import_declaration *) override; void visit(unit *unit) override; + void visit(variable_declaration *declaration) override; + void visit(procedure_declaration *definition) override; }; } diff --git a/rakelib/boot.rake b/rakelib/boot.rake index 9f3d271..afafa2c 100644 --- a/rakelib/boot.rake +++ b/rakelib/boot.rake @@ -17,7 +17,7 @@ def gcc_verbose(gcc_binary) end def find_build_target - gcc_verbose('gcc') + gcc_verbose(ENV.fetch 'CC', 'gcc') .lines .find { |line| line.start_with? 'Target: ' } .split(' ') diff --git a/source/main.elna b/source/main.elna index 94a0c32..6741eab 100644 --- a/source/main.elna +++ b/source/main.elna @@ -183,44 +183,45 @@ proc lexer_escape(escape: Char, result: ^Char) -> Bool; var successful: Bool; begin - if escape = 'n' then - result^ := '\n'; - successful := true - elsif escape = 'a' then - result^ := '\a'; - successful := true - elsif escape = 'b' then - result^ := '\b'; - successful := true - elsif escape = 't' then - result^ := '\t'; - successful := true - elsif escape = 'f' then - result^ := '\f'; - successful := true - elsif escape = 'r' then - result^ := '\r'; - successful := true - elsif escape = 'v' then - result^ := '\v'; - successful := true - elsif escape = '\\' then - result^ := '\\'; - successful := true - elsif escape = '\'' then - result^ := '\''; - successful := true - elsif escape = '"' then - result^ := '"'; - successful := true - elsif escape = '?' then - result^ := '\?'; - successful := true - elsif escape = '0' then - result^ := '\0'; - successful := true - else - successful := false + case escape of + 'n': + result^ := '\n'; + successful := true + | 'a': + result^ := '\a'; + successful := true + | 'b': + result^ := '\b'; + successful := true + | 't': + result^ := '\t'; + successful := true + | 'f': + result^ := '\f'; + successful := true + | 'r': + result^ := '\r'; + successful := true + | 'v': + result^ := '\v'; + successful := true + | '\\': + result^ := '\\'; + successful := true + | '\'': + result^ := '\''; + successful := true + | '"': + result^ := '"'; + successful := true + | '?': + result^ := '\?'; + successful := true + | '0': + result^ := '\0'; + successful := true + else + successful := false end; return successful end; @@ -304,11 +305,9 @@ proc lexer_string(source_code: ^SourceCode, token_content: ^StringBuffer) -> Boo var token_end, constructed_string: ^Char; token_length: Word; - is_valid: Bool; + is_valid: Bool := true; next_char: Char; begin - is_valid := true; - while is_valid & ~source_code_empty(source_code) & source_code_head(source_code^) <> '"' do is_valid := lexer_character(source_code, @next_char); @@ -625,9 +624,8 @@ end; proc parse(tokens: ^Token, tokens_size: Word); var current_token: ^Token; - i: Word; + i: Word := 0; begin - i := 0u; while i < tokens_size do current_token := tokens + i; @@ -787,11 +785,9 @@ end; proc compile_in_stages(command_line: ^CommandLine, source_code: SourceCode) -> Int; var - return_code: Int; + return_code: Int := 0; lexer: Tokenizer; begin - return_code := 0; - if command_line^.lex or command_line^.parse then lexer := lexer_text(source_code) end; @@ -808,11 +804,9 @@ var tokens_size: Word; source_code: SourceCode; command_line: ^CommandLine; - return_code: Int; + return_code: Int := 0; source_file: ^SourceFile; begin - return_code := 0; - command_line := parse_command_line(argc, argv); if command_line = nil then return_code := 2