Support variable declaration initializer

This commit is contained in:
2025-08-19 22:58:39 +02:00
parent 569139d44a
commit 0c2a396320
10 changed files with 175 additions and 102 deletions

View File

@@ -41,5 +41,12 @@ and Mac OS. In the latter case GCC is patched with the patches used by Homebrew
rake boot 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 See `rake -T` for more tasks. The GCC source is under `build/tools`. The
installation path is `build/host/install`. installation path is `build/host/install`.

View File

@@ -408,9 +408,10 @@ namespace elna::boot
return this; return this;
} }
variable_declaration::variable_declaration(const struct position position, identifier_definition identifier, variable_declaration::variable_declaration(const struct position position,
std::shared_ptr<type_expression> variable_type) std::vector<identifier_definition>&& identifier, std::shared_ptr<type_expression> variable_type,
: declaration(position, identifier), m_variable_type(variable_type) expression *body)
: node(position), m_variable_type(variable_type), identifiers(std::move(identifier)), body(body)
{ {
} }

View File

@@ -53,7 +53,7 @@ namespace elna::boot
if (!declaration_visitor.errors().empty()) if (!declaration_visitor.errors().empty())
{ {
std::swap(outcome.errors(), parse_driver.errors()); std::swap(outcome.errors(), declaration_visitor.errors());
} }
outcome.unresolved = declaration_visitor.unresolved; outcome.unresolved = declaration_visitor.unresolved;

View File

@@ -135,7 +135,8 @@ along with GCC; see the file COPYING3. If not see
%type <std::vector<elna::boot::switch_case>> switch_cases; %type <std::vector<elna::boot::switch_case>> switch_cases;
%type <elna::boot::constant_declaration *> constant_declaration; %type <elna::boot::constant_declaration *> constant_declaration;
%type <std::vector<elna::boot::constant_declaration *>> constant_part constant_declarations; %type <std::vector<elna::boot::constant_declaration *>> constant_part constant_declarations;
%type <std::vector<elna::boot::variable_declaration *>> variable_declarations variable_part variable_declaration; %type <elna::boot::variable_declaration *> variable_declaration;
%type <std::vector<elna::boot::variable_declaration *>> variable_declarations variable_part;
%type <elna::boot::type_expression *> type_expression; %type <elna::boot::type_expression *> type_expression;
%type <std::vector<elna::boot::type_expression *>> type_expressions; %type <std::vector<elna::boot::type_expression *>> type_expressions;
%type <elna::boot::traits_expression *> traits_expression; %type <elna::boot::traits_expression *> traits_expression;
@@ -501,24 +502,23 @@ identifiers:
$$.emplace($$.cbegin(), std::move($1)); $$.emplace($$.cbegin(), std::move($1));
} }
| IDENTIFIER { $$.emplace_back(std::move($1)); } | IDENTIFIER { $$.emplace_back(std::move($1)); }
variable_declaration: identifier_definitions ":" type_expression ";" variable_declaration:
identifier_definitions ":" type_expression ";"
{ {
std::shared_ptr<boot::type_expression> shared_type{ $3 }; std::shared_ptr<boot::type_expression> shared_type{ $3 };
$$ = new boot::variable_declaration( boot::make_position(@2), std::move($1), shared_type);
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);
} }
| identifier_definitions ":" type_expression ":=" expression ";"
{
std::shared_ptr<boot::type_expression> shared_type{ $3 };
$$ = new boot::variable_declaration( boot::make_position(@2), std::move($1), shared_type, $5);
} }
variable_declarations: variable_declarations:
/* no variable declarations */ {} /* no variable declarations */ {}
| variable_declaration variable_declarations | variable_declaration variable_declarations
{ {
std::swap($$, $1); std::swap($$, $2);
$$.reserve($$.size() + $2.size()); $$.insert(std::cbegin($$), $1);
$$.insert(std::end($$), std::begin($2), std::end($2));
} }
variable_part: variable_part:
/* no variable declarations */ {} /* no variable declarations */ {}
@@ -531,7 +531,7 @@ constant_declarations:
constant_declaration constant_declarations constant_declaration constant_declarations
{ {
std::swap($$, $2); std::swap($$, $2);
$$.insert($$.cbegin(), $1); $$.insert(std::cbegin($$), $1);
} }
| /* no constant definitions */ {} | /* no constant definitions */ {}
constant_part: constant_part:

View File

@@ -83,6 +83,16 @@ namespace elna::boot
return "Procedure '" + identifier + "' is expected to return, but does not have a return statement"; 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) type_analysis_visitor::type_analysis_visitor(const char *path)
: error_container(path) : error_container(path)
{ {
@@ -289,7 +299,10 @@ namespace elna::boot
{ {
declaration->variable_type().accept(this); declaration->variable_type().accept(this);
this->bag.enter(declaration->identifier.identifier, std::make_shared<variable_info>(this->current_type)); for (const auto& variable_identifier : declaration->identifiers)
{
this->bag.enter(variable_identifier.identifier, std::make_shared<variable_info>(this->current_type));
}
} }
void name_analysis_visitor::visit(constant_declaration *definition) void name_analysis_visitor::visit(constant_declaration *definition)
@@ -584,5 +597,33 @@ namespace elna::boot
add_error<already_declared_error>(type->identifier.identifier, this->input_file, type->position()); add_error<already_declared_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<variable_initializer_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);
}
} }
} }

View File

@@ -744,26 +744,42 @@ namespace elna::gcc
} }
void generic_visitor::visit(boot::variable_declaration *declaration) void generic_visitor::visit(boot::variable_declaration *declaration)
{
for (const auto& variable_identifier : declaration->identifiers)
{ {
this->current_expression = get_inner_alias( this->current_expression = get_inner_alias(
this->bag.lookup(declaration->identifier.identifier)->is_variable()->symbol, this->bag.lookup(variable_identifier.identifier)->is_variable()->symbol,
this->symbols); this->symbols);
location_t declaration_location = get_location(&declaration->position()); location_t declaration_location = get_location(&declaration->position());
tree declaration_tree = build_decl(declaration_location, VAR_DECL, tree declaration_tree = build_decl(declaration_location, VAR_DECL,
get_identifier(declaration->identifier.identifier.c_str()), this->current_expression); get_identifier(variable_identifier.identifier.c_str()), this->current_expression);
bool result = this->symbols->enter(declaration->identifier.identifier, declaration_tree); bool result = this->symbols->enter(variable_identifier.identifier, declaration_tree);
if (POINTER_TYPE_P(this->current_expression)) 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; DECL_INITIAL(declaration_tree) = elna_pointer_nil_node;
} }
TREE_PUBLIC(declaration_tree) = declaration->identifier.exported; TREE_PUBLIC(declaration_tree) = variable_identifier.exported;
this->current_expression = NULL_TREE; this->current_expression = NULL_TREE;
if (!result) if (!result)
{ {
error_at(declaration_location, "Variable '%s' already declared in this scope", error_at(declaration_location, "Variable '%s' already declared in this scope",
declaration->identifier.identifier.c_str()); variable_identifier.identifier.c_str());
} }
else if (lang_hooks.decls.global_bindings_p()) else if (lang_hooks.decls.global_bindings_p())
{ {
@@ -781,6 +797,7 @@ namespace elna::gcc
append_statement(declaration_statement); append_statement(declaration_statement);
} }
} }
}
void generic_visitor::visit(boot::variable_expression *expression) void generic_visitor::visit(boot::variable_expression *expression)
{ {

View File

@@ -340,17 +340,20 @@ namespace elna::boot
/** /**
* Variable declaration. * Variable declaration.
*/ */
class variable_declaration : public declaration class variable_declaration : public node
{ {
std::shared_ptr<type_expression> m_variable_type; std::shared_ptr<type_expression> m_variable_type;
public: public:
variable_declaration(const struct position position, identifier_definition identifier, variable_declaration(const struct position position,
std::shared_ptr<type_expression> variable_type); std::vector<identifier_definition>&& identifier, std::shared_ptr<type_expression> variable_type,
expression *body = nullptr);
void accept(parser_visitor *visitor) override; void accept(parser_visitor *visitor) override;
const std::vector<identifier_definition> identifiers;
type_expression& variable_type(); type_expression& variable_type();
expression *const body;
}; };
/** /**

View File

@@ -79,6 +79,14 @@ namespace elna::boot
std::string what() const override; 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. * Checks types.
*/ */
@@ -174,5 +182,7 @@ namespace elna::boot
void visit(program *program) override; void visit(program *program) override;
void visit(import_declaration *) override; void visit(import_declaration *) override;
void visit(unit *unit) override; void visit(unit *unit) override;
void visit(variable_declaration *declaration) override;
void visit(procedure_declaration *definition) override;
}; };
} }

View File

@@ -17,7 +17,7 @@ def gcc_verbose(gcc_binary)
end end
def find_build_target def find_build_target
gcc_verbose('gcc') gcc_verbose(ENV.fetch 'CC', 'gcc')
.lines .lines
.find { |line| line.start_with? 'Target: ' } .find { |line| line.start_with? 'Target: ' }
.split(' ') .split(' ')

View File

@@ -183,40 +183,41 @@ proc lexer_escape(escape: Char, result: ^Char) -> Bool;
var var
successful: Bool; successful: Bool;
begin begin
if escape = 'n' then case escape of
'n':
result^ := '\n'; result^ := '\n';
successful := true successful := true
elsif escape = 'a' then | 'a':
result^ := '\a'; result^ := '\a';
successful := true successful := true
elsif escape = 'b' then | 'b':
result^ := '\b'; result^ := '\b';
successful := true successful := true
elsif escape = 't' then | 't':
result^ := '\t'; result^ := '\t';
successful := true successful := true
elsif escape = 'f' then | 'f':
result^ := '\f'; result^ := '\f';
successful := true successful := true
elsif escape = 'r' then | 'r':
result^ := '\r'; result^ := '\r';
successful := true successful := true
elsif escape = 'v' then | 'v':
result^ := '\v'; result^ := '\v';
successful := true successful := true
elsif escape = '\\' then | '\\':
result^ := '\\'; result^ := '\\';
successful := true successful := true
elsif escape = '\'' then | '\'':
result^ := '\''; result^ := '\'';
successful := true successful := true
elsif escape = '"' then | '"':
result^ := '"'; result^ := '"';
successful := true successful := true
elsif escape = '?' then | '?':
result^ := '\?'; result^ := '\?';
successful := true successful := true
elsif escape = '0' then | '0':
result^ := '\0'; result^ := '\0';
successful := true successful := true
else else
@@ -304,11 +305,9 @@ proc lexer_string(source_code: ^SourceCode, token_content: ^StringBuffer) -> Boo
var var
token_end, constructed_string: ^Char; token_end, constructed_string: ^Char;
token_length: Word; token_length: Word;
is_valid: Bool; is_valid: Bool := true;
next_char: Char; next_char: Char;
begin begin
is_valid := true;
while is_valid & ~source_code_empty(source_code) & source_code_head(source_code^) <> '"' do while is_valid & ~source_code_empty(source_code) & source_code_head(source_code^) <> '"' do
is_valid := lexer_character(source_code, @next_char); is_valid := lexer_character(source_code, @next_char);
@@ -625,9 +624,8 @@ end;
proc parse(tokens: ^Token, tokens_size: Word); proc parse(tokens: ^Token, tokens_size: Word);
var var
current_token: ^Token; current_token: ^Token;
i: Word; i: Word := 0;
begin begin
i := 0u;
while i < tokens_size do while i < tokens_size do
current_token := tokens + i; current_token := tokens + i;
@@ -787,11 +785,9 @@ end;
proc compile_in_stages(command_line: ^CommandLine, source_code: SourceCode) -> Int; proc compile_in_stages(command_line: ^CommandLine, source_code: SourceCode) -> Int;
var var
return_code: Int; return_code: Int := 0;
lexer: Tokenizer; lexer: Tokenizer;
begin begin
return_code := 0;
if command_line^.lex or command_line^.parse then if command_line^.lex or command_line^.parse then
lexer := lexer_text(source_code) lexer := lexer_text(source_code)
end; end;
@@ -808,11 +804,9 @@ var
tokens_size: Word; tokens_size: Word;
source_code: SourceCode; source_code: SourceCode;
command_line: ^CommandLine; command_line: ^CommandLine;
return_code: Int; return_code: Int := 0;
source_file: ^SourceFile; source_file: ^SourceFile;
begin begin
return_code := 0;
command_line := parse_command_line(argc, argv); command_line := parse_command_line(argc, argv);
if command_line = nil then if command_line = nil then
return_code := 2 return_code := 2