diff --git a/cpp/src/ast.hpp b/cpp/src/ast.hpp index 40e87e7..0150d9a 100644 --- a/cpp/src/ast.hpp +++ b/cpp/src/ast.hpp @@ -167,6 +167,22 @@ struct ast bool operator==(const for_statement& rhs) const = default; }; + struct struct_initialiser + { + std::string name; + std::vector> 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 @@ -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 @@ -251,7 +268,7 @@ struct ast struct node { - using payload_t = std::variant; + using payload_t = std::variant; payload_t payload = std::monostate{}; srcloc meta = {}; std::vector children = {}; diff --git a/cpp/src/codegen.cpp b/cpp/src/codegen.cpp index 9cc2fa0..70e9205 100644 --- a/cpp/src/codegen.cpp +++ b/cpp/src/codegen.cpp @@ -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(structnode.payload), error_code::codegen, "ruh roh raggy"); + + auto try_get_designated_initialiser = [&payload](std::string_view member_name)->std::optional + { + 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(node.payload)) + { + continue; + } + const auto& declexpr = std::get(node.payload); + if(!std::holds_alternative(declexpr.expr)) + { + continue; + } + const auto& member = std::get(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); @@ -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; @@ -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); diff --git a/cpp/src/parse.cpp b/cpp/src/parse.cpp index 337f8d8..71cb3da 100644 --- a/cpp/src/parse.cpp +++ b/cpp/src/parse.cpp @@ -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. @@ -563,6 +565,73 @@ namespace parse return true; } retr.undo(); + // how about a designated initialiser. + auto maybe_open_brace = retr.retrieve(); + if(maybe_open_brace.has_value() && maybe_open_brace->t == lex::type::open_brace) + { + std::size_t initial_idx = retr.get_offset(); + std::vector> designated_initialisers = {}; + std::optional close_brace = std::nullopt; + // keep trying to parse the close paren. + if(!retr.avail()){return false;} + while((close_brace = retr.retrieve(), !close_brace.has_value() || close_brace->t != lex::type::close_brace)) + { + retr.undo(); + if(!designated_initialisers.empty()) + { + // need a comma. + auto comma = retr.retrieve(); + if(!comma.has_value() || comma->t != lex::type::comma) + { + return false; + } + if(!retr.avail()){return false;} + } + + // need a dot. + auto dot = retr.retrieve(); + if(!dot.has_value() || dot->t != lex::type::dot) + { + return false; + } + + // designator + if(!retr.avail()){return false;} + auto lhs = retr.retrieve(); + if(!lhs.has_value()) + { + return false; + } + + // := + if(!retr.avail()){return false;} + auto initialiser = retr.retrieve(); + if(!initialiser.has_value() || initialiser->t != lex::type::initialiser) + { + return false; + } + + // designatee + if(!retr.avail()){return false;} + auto expr = retr.retrieve(); + 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 @@ -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(&meta); + + if(!retr.avail()){return false;} + bool capped = false; + + auto semicolon = retr.retrieve(); + 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}; @@ -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); diff --git a/cpp/src/semal.cpp b/cpp/src/semal.cpp index 423a4af..1123744 100644 --- a/cpp/src/semal.cpp +++ b/cpp/src/semal.cpp @@ -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 + { + 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) { @@ -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); diff --git a/samples/scratchpad.psy b/samples/scratchpad.psy index f278808..f4a27aa 100644 --- a/samples/scratchpad.psy +++ b/samples/scratchpad.psy @@ -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 { @@ -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) {