Skip to content

Commit

Permalink
[cpp] implemented struct initialisers. seems to work pretty damn good
Browse files Browse the repository at this point in the history
  • Loading branch information
harrand committed May 23, 2024
1 parent 6d11e3a commit 5e168fb
Show file tree
Hide file tree
Showing 5 changed files with 247 additions and 14 deletions.
21 changes: 19 additions & 2 deletions cpp/src/ast.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,22 @@ struct ast
bool operator==(const for_statement& rhs) const = default;
};

struct struct_initialiser
{
std::string name;
std::vector<std::pair<std::string, boxed_expression>> designated_initialisers;
std::string to_string() const
{
std::string inits = "";
for(const auto& [name, expr] : designated_initialisers)
{
inits += std::format(".{} := {}", name, expr->to_string());
}
return std::format("struct_initialiser({}{{{}}})", name, inits);
}
bool operator==(const struct_initialiser& rhs) const = default;
};

struct expression
{
std::variant
Expand All @@ -184,7 +200,8 @@ struct ast
ast::function_call,
ast::return_statement,
ast::if_statement,
ast::for_statement
ast::for_statement,
ast::struct_initialiser
> expr;
bool capped = false;
std::string to_string() const
Expand Down Expand Up @@ -251,7 +268,7 @@ struct ast

struct node
{
using payload_t = std::variant<std::monostate, integer_literal, decimal_literal, bool_literal, null_literal, identifier, member_access, array_access, function_call, if_statement, for_statement, expression, return_statement, variable_declaration, function_definition, struct_definition, block, meta_region>;
using payload_t = std::variant<std::monostate, integer_literal, decimal_literal, bool_literal, null_literal, identifier, member_access, array_access, function_call, if_statement, for_statement, struct_initialiser, expression, return_statement, variable_declaration, function_definition, struct_definition, block, meta_region>;
payload_t payload = std::monostate{};
srcloc meta = {};
std::vector<node> children = {};
Expand Down
88 changes: 76 additions & 12 deletions cpp/src/codegen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1296,6 +1296,73 @@ namespace code
return {};
}

value struct_initialiser(const data& d, ast::struct_initialiser payload)
{
const semal::struct_t* structdata = d.state.try_find_struct(payload.name);
d.ctx.assert_that(structdata != nullptr, error_code::codegen, "struct type \"{}\" via struct initialiser was not recognised as a valid struct", payload.name);
const auto& structnode = structdata->ctx.node();
d.ctx.assert_that(std::holds_alternative<ast::struct_definition>(structnode.payload), error_code::codegen, "ruh roh raggy");

auto try_get_designated_initialiser = [&payload](std::string_view member_name)->std::optional<ast::expression>
{
for(const auto& [desig_member_name, expr] : payload.designated_initialisers)
{
if(desig_member_name == member_name)
{
return *expr;
}
}
return std::nullopt;
};

type structty = type::from_struct(structdata->ty);
value temp
{
.llv = builder->CreateAlloca(as_llvm_type(structty, d.state)),
.ty = structty.pointer_to(),
.is_variable = true
};

std::size_t member_idx = 0;
auto blk_node = structnode.children.front();
for(std::size_t i = 0; i < blk_node.children.size(); i++)
{
const auto& node = blk_node.children[i];
if(!std::holds_alternative<ast::expression>(node.payload))
{
continue;
}
const auto& declexpr = std::get<ast::expression>(node.payload);
if(!std::holds_alternative<ast::variable_declaration>(declexpr.expr))
{
continue;
}
const auto& member = std::get<ast::variable_declaration>(declexpr.expr);
// use either the default initialiser of the struct, or the expr used by the struct initialiser.
auto init_expr = try_get_designated_initialiser(member.var_name);
if(!init_expr.has_value())
{
// struct initialiser didnt initialise this member, fallback to the default expr if it has one.
if(member.initialiser.has_value())
{
init_expr = *member.initialiser.value();
}
else
{
// theres no default initialiser either...
}
}
if(init_expr.has_value())
{
value exprval = expression(d, init_expr.value());
llvm::Value* member_ptr = builder->CreateStructGEP(as_llvm_type(structty, d.state), temp.llv, member_idx++);
builder->CreateStore(exprval.llv, member_ptr);
}
}

return temp;
}

value return_statement(const data& d, ast::return_statement payload)
{
const semal::function_t* func = d.state.try_find_parent_function(*d.ctx.tree, d.ctx.path);
Expand Down Expand Up @@ -1357,20 +1424,13 @@ namespace code
llvm::AllocaInst* llvm_var = builder->CreateAlloca(llvm_ty, nullptr, payload.var_name);
if(payload.initialiser.has_value())
{
if(var->ty.is_struct())
{
d.ctx.error(error_code::nyi, "inline initialiser of a struct-typed variable is not yet implemented.");
}
else
value init_value = expression(d, *payload.initialiser.value());
if(init_value.is_variable)
{
value init_value = expression(d, *payload.initialiser.value());
if(init_value.is_variable)
{
init_value = get_variable_val(init_value, d);
}
d.ctx.assert_that(init_value.llv != nullptr, error_code::ice, "variable declaration \"{}\"'s initialiser expression codegen'd to nullptr.", payload.var_name);
builder->CreateStore(init_value.llv, llvm_var);
init_value = get_variable_val(init_value, d);
}
d.ctx.assert_that(init_value.llv != nullptr, error_code::ice, "variable declaration \"{}\"'s initialiser expression codegen'd to nullptr.", payload.var_name);
builder->CreateStore(init_value.llv, llvm_var);
}

var->userdata = llvm_var;
Expand Down Expand Up @@ -1567,6 +1627,10 @@ namespace code
{
ret = for_statement(d, forst);
},
[&](ast::struct_initialiser structinit)
{
ret = struct_initialiser(d, structinit);
},
[&](ast::return_statement returnst)
{
ret = return_statement(d, returnst);
Expand Down
93 changes: 93 additions & 0 deletions cpp/src/parse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ namespace parse
bool reduce_from_if_statement(std::size_t offset);
// given an ast::for_statement subtree at the offset, try to reduce its surrounding tokens/atoms into something bigger. returns true on success, false otherwise.
bool reduce_from_for_statement(std::size_t offset);
// given an ast::struct_initialiser subtree at the offset, try to reduce its surrounding tokens/atoms into something bigger. returns true on success, false otherwise.
bool reduce_from_struct_initialiser(std::size_t offset);
// given an ast::expression subtree at the offset, try to reduce its surrounding tokens/atoms into something bigger. returns true on success, false otherwise.
bool reduce_from_expression(std::size_t offset);
// given an ast::function_definition subtree at the offset, try to reduce its surrounding tokens/atoms into something bigger. returns true on success, false otherwise.
Expand Down Expand Up @@ -563,6 +565,73 @@ namespace parse
return true;
}
retr.undo();
// how about a designated initialiser.
auto maybe_open_brace = retr.retrieve<lex::token>();
if(maybe_open_brace.has_value() && maybe_open_brace->t == lex::type::open_brace)
{
std::size_t initial_idx = retr.get_offset();
std::vector<std::pair<std::string, ast::boxed_expression>> designated_initialisers = {};
std::optional<lex::token> close_brace = std::nullopt;
// keep trying to parse the close paren.
if(!retr.avail()){return false;}
while((close_brace = retr.retrieve<lex::token>(), !close_brace.has_value() || close_brace->t != lex::type::close_brace))
{
retr.undo();
if(!designated_initialisers.empty())
{
// need a comma.
auto comma = retr.retrieve<lex::token>();
if(!comma.has_value() || comma->t != lex::type::comma)
{
return false;
}
if(!retr.avail()){return false;}
}

// need a dot.
auto dot = retr.retrieve<lex::token>();
if(!dot.has_value() || dot->t != lex::type::dot)
{
return false;
}

// designator
if(!retr.avail()){return false;}
auto lhs = retr.retrieve<ast::identifier>();
if(!lhs.has_value())
{
return false;
}

// :=
if(!retr.avail()){return false;}
auto initialiser = retr.retrieve<lex::token>();
if(!initialiser.has_value() || initialiser->t != lex::type::initialiser)
{
return false;
}

// designatee
if(!retr.avail()){return false;}
auto expr = retr.retrieve<ast::expression>();
if(!expr.has_value())
{
return false;
}
designated_initialisers.push_back({lhs->iden, expr.value()});
}
// we're done. note a struct initialiser must contain *at least one* designated initialiser, otherwise all blocks could in theory reduce to a struct initialiser due to braces being very context sentitive.
if(designated_initialisers.size())
{
retr.reduce_to(ast::struct_initialiser{.name = value.iden, .designated_initialisers = designated_initialisers}, meta);
return true;
}
while(retr.get_offset() > initial_idx)
{
retr.undo();
}
}
retr.undo();

// getting difficult here...
// at some point we need to know whether an identifier should remain an identifier or become an expression
Expand Down Expand Up @@ -702,6 +771,26 @@ namespace parse
return false;
}

bool parser_state::reduce_from_struct_initialiser(std::size_t offset)
{
retriever retr{*this, offset};
srcloc meta;
auto value = retr.must_retrieve<ast::struct_initialiser>(&meta);

if(!retr.avail()){return false;}
bool capped = false;

auto semicolon = retr.retrieve<lex::token>();
capped = (semicolon.has_value() && semicolon->t == lex::type::semicolon);
if(!capped)
{
retr.undo();
}

retr.reduce_to(ast::expression{.expr = value, .capped = capped}, meta);
return true;
}

bool parser_state::reduce_from_expression(std::size_t offset)
{
retriever retr{*this, offset};
Expand Down Expand Up @@ -1275,6 +1364,10 @@ namespace parse
{
ret = this->reduce_from_for_statement(i);
},
[&](ast::struct_initialiser arg)
{
ret = this->reduce_from_struct_initialiser(i);
},
[&](ast::expression arg)
{
ret = this->reduce_from_expression(i);
Expand Down
33 changes: 33 additions & 0 deletions cpp/src/semal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,35 @@ namespace semal
return type::undefined();
}

type struct_initialiser(const data& d, const ast::struct_initialiser& payload)
{
type ret = d.state.get_type_from_name(payload.name);
d.assert_that(!ret.is_undefined(), std::format("struct initialiser type \"{}\" is not recognised as a valid type.", payload.name));
d.assert_that(ret.is_struct(), std::format("struct initialiser type \"{}\" is not recognised as a struct type.", payload.name));

auto try_get_member = [structty = ret.as_struct()](std::string_view member_name)->std::optional<type>
{
for(const auto& member : structty.data_members)
{
if(member.member_name == member_name)
{
return *member.ty;
}
}
return std::nullopt;
};

// go through each initialiser and make sure both that the member exists and type checks.
for(const auto& [member, init] : payload.designated_initialisers)
{
auto member_ty = try_get_member(member);
d.assert_that(member_ty.has_value(), std::format("in designated initialiser for struct \"{}\": no such data member \"{}\"", payload.name, member));
type expr_ty = expression(d, *init);
d.assert_that(typecon_valid(member_ty.value().is_implicitly_convertible_to(expr_ty)), std::format("designator \"{}\" for struct \"{}\" is of type \"{}\". the initialiser you passed is of type \"{}\" which is not implicitly convertible. did you forget to cast?", member, payload.name, member_ty->name(), expr_ty.name()));
}
return ret;
}

// node types.
type binary_operator(const data& d, const ast::binary_operator& payload)
{
Expand Down Expand Up @@ -863,6 +892,10 @@ namespace semal
{
ret = for_statement(d, forst);
},
[&](ast::struct_initialiser strinit)
{
ret = struct_initialiser(d, strinit);
},
[&](ast::return_statement returnst)
{
ret = return_statement(d, returnst);
Expand Down
26 changes: 26 additions & 0 deletions samples/scratchpad.psy
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,27 @@ is_odd :: (val : i64) -> bool
return (half == low_half);
}

animal :: struct
{
id : i64;
}

get_fox :: () -> animal
{
//ret : animal := animal
//{
// .id := 2
//};
//ret : animal;
//ret.id = 5;
//return ret;

return animal
{
.id := 8
};
}

puts :: (str : i8&) -> u0 := extern;
print_hello :: () -> u0
{
Expand All @@ -35,6 +56,11 @@ print_hello :: () -> u0
main :: () -> i64
{
print_hello();

putchar(10);
my_fox : animal := get_fox();
print_digit(my_fox.id);
putchar(10);
i : i64 weak := 0;
for i = 0, i != 10, i = (i + 1)
{
Expand Down

0 comments on commit 5e168fb

Please sign in to comment.