kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit 0bbc3338aeb7ede4ee32d37859e5309d1e3605d4
parent 9e883148e4350039f3af0f2552be44a84aeeaa96
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Sun, 17 May 2026 15:47:54 -0700

toy: add fat slices

Diffstat:
Mdoc/TOY.md | 14++++++++++++++
Mlang/toy/expr.c | 201+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mlang/toy/internal.h | 13+++++++++++++
Mlang/toy/parser.c | 100+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mlang/toy/types.c | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/toy/cases/124_slices.expected | 1+
Atest/toy/cases/124_slices.toy | 13+++++++++++++
Atest/toy/err/invalid_slice_base.expected | 1+
Atest/toy/err/invalid_slice_base.toy | 5+++++
Atest/toy/err/invalid_slice_index_type.expected | 1+
Atest/toy/err/invalid_slice_index_type.toy | 5+++++
Atest/toy/err/invalid_slice_len_assign.expected | 1+
Atest/toy/err/invalid_slice_len_assign.toy | 6++++++
13 files changed, 406 insertions(+), 8 deletions(-)

diff --git a/doc/TOY.md b/doc/TOY.md @@ -81,6 +81,7 @@ Compound and user types: - Pointer: `*T` - Address-space pointer: `*addrspace(N) T` - Array: `[N]T` +- Slice: `[]T` - Function: `fn(T, U): R` - Variadic function: `fn(T, ...): R` - Transparent alias: `type Name = T;` @@ -128,6 +129,12 @@ representation must have the same size and alignment as the source pointer type. Function types are not first-class values by themselves. Source values use pointer-to-function types such as `*fn(i32): i32` or `*AliasToFunction`. +Slices are fat values with the source spelling `[]T`. They lower as a record +with readonly fields `ptr: *T` and `len: usize`, which are accessible to user +code as `slice.ptr` and `slice.len`. A slice does not own its elements and does +not extend the lifetime of the array or slice it was derived from. Slice values +may be copied, passed, returned, indexed, and sliced. + Type qualifiers are prefix forms: ```toy @@ -345,6 +352,13 @@ a `*Record` implicitly dereferences the pointer; Toy has no `->` operator. `&arr` produces `*[N]T`, a pointer to the whole array. `&arr[0]` produces `*T`, a pointer to the first element. +Slicing uses `expr[start:end]`, where `start` and `end` are `isize` +expressions. Arrays and slices can be sliced. Slicing `[N]T` or `[]T` produces +`[]T` with `.ptr` pointing at element `start` and `.len` equal to `end - start`. +Indexing arrays, pointers, and slices requires an `isize` index. Indexing a +slice with `slice[index]` indexes through `.ptr`. Toy does not +insert runtime bounds checks for indexing or slicing. + ## Statements Blocks introduce lexical local scopes: diff --git a/lang/toy/expr.c b/lang/toy/expr.c @@ -457,6 +457,117 @@ CfreeCgTypeId toy_emit_var_lvalue(ToyParser* p, CfreeSym name) { return CFREE_CG_TYPE_NONE; } +static CfreeCgTypeId toy_type_id_cg_or_none(ToyParser* p, ToyTypeId id) { + CfreeCgTypeId ty = toy_type_resolved_cg(p, id); + return ty != CFREE_CG_TYPE_NONE ? ty : toy_type_cg(p, id); +} + +static void toy_store_tos_to_local(ToyParser* p, CfreeCgLocal local, + CfreeCgTypeId ty) { + cfree_cg_push_local(p->cg, local); + cfree_cg_swap(p->cg); + cfree_cg_store(p->cg, toy_mem_access(p, ty)); +} + +CfreeCgTypeId toy_emit_slice_index_lvalue(ToyParser* p, CfreeCgTypeId slice_ty, + ToyTypeId slice_toy_type, + ToyTypeId* elem_toy_out) { + ToyTypeId elem_toy = toy_type_slice_elem(p, slice_toy_type); + CfreeCgTypeId elem_ty = toy_type_id_cg_or_none(p, elem_toy); + CfreeCgField ptr_field; + CfreeCgLocal idx_slot; + if (elem_ty == CFREE_CG_TYPE_NONE || + cfree_cg_type_kind(p->c, slice_ty) != CFREE_CG_TYPE_RECORD || + cfree_cg_type_record_field(p->c, slice_ty, 0, &ptr_field, NULL) != 0) { + toy_error(p, p->cur.loc, "cannot index non-array/non-pointer"); + return CFREE_CG_TYPE_NONE; + } + idx_slot = cfree_cg_local(p->cg, p->int_type, toy_slot_attrs(0)); + toy_store_tos_to_local(p, idx_slot, p->int_type); + cfree_cg_field(p->cg, 0); + cfree_cg_load(p->cg, toy_mem_access(p, ptr_field.type)); + cfree_cg_push_local(p->cg, idx_slot); + cfree_cg_load(p->cg, toy_mem_access(p, p->int_type)); + cfree_cg_index(p->cg, 0); + if (elem_toy_out) *elem_toy_out = elem_toy; + return elem_ty; +} + +CfreeCgTypeId toy_emit_slice_value(ToyParser* p, CfreeCgTypeId base_ty, + ToyTypeId base_toy_type, + CfreeCgTypeId start_ty, + CfreeCgTypeId end_ty, + ToyTypeId* slice_toy_out) { + ToyTypeId elem_toy = TOY_TYPE_NONE; + CfreeCgTypeId elem_ty = CFREE_CG_TYPE_NONE; + ToyTypeId slice_toy; + CfreeCgTypeId slice_ty; + CfreeCgField ptr_field; + CfreeCgLocal start_slot; + CfreeCgLocal end_slot; + CfreeCgLocal result_slot; + + if (start_ty != p->int_type || end_ty != p->int_type) { + toy_error(p, p->cur.loc, "slice bounds must be isize"); + return CFREE_CG_TYPE_NONE; + } + + if (cfree_cg_type_kind(p->c, base_ty) == CFREE_CG_TYPE_ARRAY) { + elem_ty = cfree_cg_type_array_elem(p->c, base_ty); + elem_toy = toy_type_array_elem(p, base_toy_type); + } else if (toy_type_is_slice(p, base_toy_type)) { + elem_toy = toy_type_slice_elem(p, base_toy_type); + elem_ty = toy_type_id_cg_or_none(p, elem_toy); + } + if (elem_toy == TOY_TYPE_NONE && elem_ty != CFREE_CG_TYPE_NONE) + elem_toy = toy_type_from_cg(p, elem_ty); + if (elem_ty == CFREE_CG_TYPE_NONE || elem_toy == TOY_TYPE_NONE) { + toy_error(p, p->cur.loc, "cannot slice non-array/non-slice"); + return CFREE_CG_TYPE_NONE; + } + + slice_toy = toy_type_register_slice(p, elem_ty, elem_toy); + slice_ty = toy_type_cg(p, slice_toy); + if (slice_ty == CFREE_CG_TYPE_NONE || + cfree_cg_type_record_field(p->c, slice_ty, 0, &ptr_field, NULL) != 0) { + toy_error(p, p->cur.loc, "failed to create slice type"); + return CFREE_CG_TYPE_NONE; + } + + end_slot = cfree_cg_local(p->cg, p->int_type, toy_slot_attrs(0)); + toy_store_tos_to_local(p, end_slot, p->int_type); + start_slot = cfree_cg_local(p->cg, p->int_type, toy_slot_attrs(0)); + toy_store_tos_to_local(p, start_slot, p->int_type); + + result_slot = cfree_cg_local(p->cg, slice_ty, toy_slot_attrs(0)); + if (toy_type_is_slice(p, base_toy_type)) { + cfree_cg_field(p->cg, 0); + cfree_cg_load(p->cg, toy_mem_access(p, ptr_field.type)); + } + cfree_cg_push_local(p->cg, start_slot); + cfree_cg_load(p->cg, toy_mem_access(p, p->int_type)); + cfree_cg_index(p->cg, 0); + cfree_cg_addr(p->cg); + cfree_cg_push_local(p->cg, result_slot); + cfree_cg_field(p->cg, 0); + cfree_cg_swap(p->cg); + cfree_cg_store(p->cg, toy_mem_access(p, ptr_field.type)); + + cfree_cg_push_local(p->cg, result_slot); + cfree_cg_field(p->cg, 1); + cfree_cg_push_local(p->cg, end_slot); + cfree_cg_load(p->cg, toy_mem_access(p, p->int_type)); + cfree_cg_push_local(p->cg, start_slot); + cfree_cg_load(p->cg, toy_mem_access(p, p->int_type)); + cfree_cg_int_binop(p->cg, CFREE_CG_INT_SUB, 0); + cfree_cg_store(p->cg, toy_mem_access(p, p->int_type)); + + cfree_cg_push_local(p->cg, result_slot); + if (slice_toy_out) *slice_toy_out = slice_toy; + p->last_type = slice_toy; + return slice_ty; +} + int toy_parse_va_list_addr_arg(ToyParser* p) { CfreeSym name; ToyVar* v; @@ -606,6 +717,7 @@ static CfreeCgTypeId toy_parse_expr_primary(ToyParser* p) { if (v && (cfree_cg_type_kind(p->c, v->type) == CFREE_CG_TYPE_ARRAY || cfree_cg_type_kind(p->c, v->type) == CFREE_CG_TYPE_RECORD)) { toy_push_var_lvalue(p, v); + p->last_type = v->toy_type; return v->type; } { @@ -614,6 +726,24 @@ static CfreeCgTypeId toy_parse_expr_primary(ToyParser* p) { (cfree_cg_type_kind(p->c, g->type) == CFREE_CG_TYPE_ARRAY || cfree_cg_type_kind(p->c, g->type) == CFREE_CG_TYPE_RECORD)) { cfree_cg_push_symbol_lvalue(p->cg, g->sym, 0); + p->last_type = g->toy_type; + return g->type; + } + } + } + + { + ToyVar* v = toy_find_var(p, name); + if (v && toy_type_is_slice(p, v->toy_type)) { + toy_push_var_lvalue(p, v); + p->last_type = v->toy_type; + return v->type; + } + { + ToyGlobal* g = toy_find_global(p, name); + if (g && toy_type_is_slice(p, g->toy_type)) { + cfree_cg_push_symbol_lvalue(p->cg, g->sym, 0); + p->last_type = g->toy_type; return g->type; } } @@ -663,12 +793,27 @@ static CfreeCgTypeId toy_parse_expr_postfix(ToyParser* p) { if (toy_parser_match(p, TOK_LBRACKET)) { CfreeCgTypeId idx_ty = toy_parse_expr(p); if (idx_ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (toy_parser_match(p, TOK_COLON)) { + CfreeCgTypeId end_ty = toy_parse_expr(p); + ToyTypeId slice_toy_type = TOY_TYPE_NONE; + if (end_ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + if (!toy_parser_expect(p, TOK_RBRACKET)) { + toy_error(p, p->cur.loc, "expected ']' after slice"); + return CFREE_CG_TYPE_NONE; + } + ty = toy_emit_slice_value(p, ty, toy_ty, idx_ty, end_ty, + &slice_toy_type); + if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + toy_ty = slice_toy_type; + p->last_type = toy_ty; + continue; + } if (!toy_parser_expect(p, TOK_RBRACKET)) { toy_error(p, p->cur.loc, "expected ']' after index"); return CFREE_CG_TYPE_NONE; } if (idx_ty != p->int_type) { - toy_error(p, p->cur.loc, "index must be i64"); + toy_error(p, p->cur.loc, "index must be isize"); return CFREE_CG_TYPE_NONE; } if (cfree_cg_type_kind(p->c, ty) == CFREE_CG_TYPE_PTR) { @@ -692,6 +837,12 @@ static CfreeCgTypeId toy_parse_expr_postfix(ToyParser* p) { } else if (cfree_cg_type_kind(p->c, ty) == CFREE_CG_TYPE_ARRAY) { ty = cfree_cg_type_array_elem(p->c, ty); toy_ty = toy_type_array_elem(p, toy_ty); + } else if (toy_type_is_slice(p, toy_ty)) { + ty = toy_emit_slice_index_lvalue(p, ty, toy_ty, &toy_ty); + if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + cfree_cg_load(p->cg, toy_mem_access(p, ty)); + p->last_type = toy_ty != TOY_TYPE_NONE ? toy_ty : toy_type_from_cg(p, ty); + continue; } else { toy_error(p, p->cur.loc, "cannot index non-array/non-pointer"); return CFREE_CG_TYPE_NONE; @@ -852,21 +1003,44 @@ static CfreeCgTypeId toy_parse_expr_unary(ToyParser* p) { if (p->cur.kind == TOK_LBRACKET || p->cur.kind == TOK_DOTSTAR || p->cur.kind == TOK_DOT) { + ToyTypeId ty_toy = TOY_TYPE_NONE; if (p->cur.kind == TOK_DOT) { ToyVar* v = toy_find_var(p, name); ToyGlobal* g = toy_find_global(p, name); if (v && cfree_cg_type_kind(p->c, v->type) == CFREE_CG_TYPE_RECORD) { toy_push_var_lvalue(p, v); ty = v->type; + ty_toy = v->toy_type; } else if (g && cfree_cg_type_kind(p->c, g->type) == CFREE_CG_TYPE_RECORD) { cfree_cg_push_symbol_lvalue(p->cg, g->sym, 0); ty = g->type; + ty_toy = g->toy_type; } else { ty = toy_push_named_rvalue(p, name); + ty_toy = p->last_type; + } + } else if (p->cur.kind == TOK_LBRACKET) { + ToyVar* v = toy_find_var(p, name); + ToyGlobal* g = toy_find_global(p, name); + if (v && (cfree_cg_type_kind(p->c, v->type) == CFREE_CG_TYPE_ARRAY || + cfree_cg_type_kind(p->c, v->type) == CFREE_CG_TYPE_RECORD)) { + toy_push_var_lvalue(p, v); + ty = v->type; + ty_toy = v->toy_type; + } else if (g && + (cfree_cg_type_kind(p->c, g->type) == CFREE_CG_TYPE_ARRAY || + cfree_cg_type_kind(p->c, g->type) == CFREE_CG_TYPE_RECORD)) { + cfree_cg_push_symbol_lvalue(p->cg, g->sym, 0); + ty = g->type; + ty_toy = g->toy_type; + } else { + ty = toy_push_named_rvalue(p, name); + ty_toy = p->last_type; } } else { ty = toy_push_named_rvalue(p, name); + ty_toy = p->last_type; } if (ty == CFREE_CG_TYPE_NONE) { toy_error(p, p->cur.loc, "undefined variable"); @@ -881,7 +1055,7 @@ static CfreeCgTypeId toy_parse_expr_unary(ToyParser* p) { return CFREE_CG_TYPE_NONE; } if (idx_ty != p->int_type) { - toy_error(p, p->cur.loc, "index must be i64"); + toy_error(p, p->cur.loc, "index must be isize"); return CFREE_CG_TYPE_NONE; } if (cfree_cg_type_kind(p->c, ty) == CFREE_CG_TYPE_PTR) { @@ -896,11 +1070,19 @@ static CfreeCgTypeId toy_parse_expr_unary(ToyParser* p) { cfree_cg_push_local(p->cg, idx_slot); cfree_cg_load(p->cg, toy_mem_access(p, p->int_type)); ty = cfree_cg_type_array_elem(p->c, pointee); + ty_toy = toy_type_array_elem(p, toy_type_pointee(p, ty_toy)); } else { ty = pointee; + ty_toy = toy_type_pointee(p, ty_toy); } } else if (cfree_cg_type_kind(p->c, ty) == CFREE_CG_TYPE_ARRAY) { ty = cfree_cg_type_array_elem(p->c, ty); + ty_toy = toy_type_array_elem(p, ty_toy); + } else if (toy_type_is_slice(p, ty_toy)) { + ToyTypeId elem_toy = TOY_TYPE_NONE; + ty = toy_emit_slice_index_lvalue(p, ty, ty_toy, &elem_toy); + if (ty == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + ty_toy = elem_toy; } else { toy_error(p, p->cur.loc, "cannot index non-array/non-pointer"); return CFREE_CG_TYPE_NONE; @@ -914,23 +1096,27 @@ static CfreeCgTypeId toy_parse_expr_unary(ToyParser* p) { return CFREE_CG_TYPE_NONE; } ty = cfree_cg_type_ptr_pointee(p->c, ty); + ty_toy = toy_type_pointee(p, ty_toy); cfree_cg_indirect(p->cg); continue; } if (toy_parser_match(p, TOK_DOT)) { CfreeCgField field; uint32_t field_index = 0; + ToyNamedType* named; if (cfree_cg_type_kind(p->c, ty) == CFREE_CG_TYPE_PTR && cfree_cg_type_kind(p->c, cfree_cg_type_ptr_pointee(p->c, ty)) == CFREE_CG_TYPE_RECORD) { ty = cfree_cg_type_ptr_pointee(p->c, ty); + ty_toy = toy_type_pointee(p, ty_toy); cfree_cg_indirect(p->cg); } if (cfree_cg_type_kind(p->c, ty) != CFREE_CG_TYPE_RECORD) { toy_error(p, p->cur.loc, "field access on non-record"); return CFREE_CG_TYPE_NONE; } + named = toy_find_named_type_by_type(p, ty); if (p->cur.kind == TOK_NUMBER && !p->cur.is_float) { if (p->cur.int_value < 0 || p->cur.int_value >= @@ -959,12 +1145,21 @@ static CfreeCgTypeId toy_parse_expr_unary(ToyParser* p) { } cfree_cg_field(p->cg, field_index); ty = field.type; + ty_toy = (named && field_index < named->nfields) + ? named->fields[field_index].toy_type + : toy_type_from_cg(p, ty); continue; } break; } cfree_cg_addr(p->cg); - return cfree_cg_type_ptr(p->c, ty, 0); + { + CfreeCgTypeId ptr_ty = cfree_cg_type_ptr(p->c, ty, 0); + p->last_type = toy_type_register_ptr( + p, ptr_ty, + ty_toy != TOY_TYPE_NONE ? ty_toy : toy_type_from_cg(p, ty), 0); + return ptr_ty; + } } { diff --git a/lang/toy/internal.h b/lang/toy/internal.h @@ -89,6 +89,7 @@ typedef enum ToyTypeKind { TOY_TYPE_TUPLE_RECORD, TOY_TYPE_ENUM, TOY_TYPE_ARRAY, + TOY_TYPE_SLICE, TOY_TYPE_PTR, TOY_TYPE_FUNC, TOY_TYPE_QUALIFIED, @@ -318,6 +319,8 @@ ToyTypeId toy_type_register_qualified(ToyParser* p, CfreeCgTypeId cg, CfreeCgTypeId base, uint32_t quals); ToyTypeId toy_type_register_ptr(ToyParser* p, CfreeCgTypeId cg, ToyTypeId pointee, uint32_t address_space); +ToyTypeId toy_type_register_slice(ToyParser* p, CfreeCgTypeId elem_cg, + ToyTypeId elem); ToyTypeId toy_type_register_func(ToyParser* p, CfreeCgTypeId cg, ToyTypeId ret, const ToyTypeId* params, size_t nparams, int variadic); @@ -325,6 +328,16 @@ int toy_type_accepts_type(ToyParser* p, ToyTypeId expected, ToyTypeId actual); CfreeCgTypeId toy_type_resolved_cg(ToyParser* p, ToyTypeId id); ToyTypeId toy_type_pointee(ToyParser* p, ToyTypeId id); ToyTypeId toy_type_array_elem(ToyParser* p, ToyTypeId id); +ToyTypeId toy_type_slice_elem(ToyParser* p, ToyTypeId id); +int toy_type_is_slice(ToyParser* p, ToyTypeId id); +CfreeCgTypeId toy_emit_slice_index_lvalue(ToyParser* p, CfreeCgTypeId slice_ty, + ToyTypeId slice_toy_type, + ToyTypeId* elem_toy_out); +CfreeCgTypeId toy_emit_slice_value(ToyParser* p, CfreeCgTypeId base_ty, + ToyTypeId base_toy_type, + CfreeCgTypeId start_ty, + CfreeCgTypeId end_ty, + ToyTypeId* slice_toy_out); int toy_record_field_index(ToyParser* p, CfreeCgTypeId record_ty, CfreeSym field_name, uint32_t* index_out, CfreeCgField* field_out); diff --git a/lang/toy/parser.c b/lang/toy/parser.c @@ -114,6 +114,61 @@ static int toy_store_record_value_as(ToyParser* p, CfreeCgTypeId src_ty, return 1; } +static int toy_copy_record_lvalue_to_local(ToyParser* p, CfreeCgTypeId src_ty, + CfreeCgLocal dst_slot, + CfreeCgTypeId dst_ty) { + uint32_t i; + uint32_t nfields; + if (!toy_records_have_matching_storage(p, dst_ty, src_ty)) { + toy_error(p, p->cur.loc, "record storage mismatch"); + return 0; + } + nfields = cfree_cg_type_record_nfields(p->c, dst_ty); + for (i = 0; i < nfields; ++i) { + CfreeCgField field; + if (cfree_cg_type_record_field(p->c, dst_ty, i, &field, NULL)) return 0; + cfree_cg_dup(p->cg); + cfree_cg_field(p->cg, i); + cfree_cg_load(p->cg, toy_mem_access(p, field.type)); + cfree_cg_push_local(p->cg, dst_slot); + cfree_cg_field(p->cg, i); + cfree_cg_swap(p->cg); + cfree_cg_store(p->cg, toy_mem_access(p, field.type)); + } + cfree_cg_drop(p->cg); + return 1; +} + +static int toy_copy_record_lvalue_to_var(ToyParser* p, CfreeCgTypeId src_ty, + const ToyVar* dst_var, + const ToyGlobal* dst_global) { + CfreeCgTypeId dst_ty = dst_var ? dst_var->type : dst_global->type; + uint32_t i; + uint32_t nfields; + if (!toy_records_have_matching_storage(p, dst_ty, src_ty)) { + toy_error(p, p->cur.loc, "record storage mismatch"); + return 0; + } + nfields = cfree_cg_type_record_nfields(p->c, dst_ty); + for (i = 0; i < nfields; ++i) { + CfreeCgField field; + if (cfree_cg_type_record_field(p->c, dst_ty, i, &field, NULL)) return 0; + cfree_cg_dup(p->cg); + cfree_cg_field(p->cg, i); + cfree_cg_load(p->cg, toy_mem_access(p, field.type)); + if (dst_var) { + toy_push_var_lvalue(p, dst_var); + } else { + cfree_cg_push_symbol_lvalue(p->cg, dst_global->sym, 0); + } + cfree_cg_field(p->cg, i); + cfree_cg_swap(p->cg); + cfree_cg_store(p->cg, toy_mem_access(p, field.type)); + } + cfree_cg_drop(p->cg); + return 1; +} + static int toy_parse_array_initializer(ToyParser* p, CfreeCgLocal slot, CfreeCgTypeId arr_ty, ToyTypeId arr_toy_type) { @@ -587,6 +642,7 @@ static int toy_parse_let_stmt(ToyParser* p) { CfreeCgTypeId ty = CFREE_CG_TYPE_NONE; CfreeCgTypeId init_ty = CFREE_CG_TYPE_NONE; ToyTypeId toy_ty = TOY_TYPE_NONE; + ToyTypeId init_toy_type = TOY_TYPE_NONE; CfreeCgLocal slot; int has_init = 0; int inferred = 0; @@ -705,6 +761,7 @@ static int toy_parse_let_stmt(ToyParser* p) { ((cfree_cg_type_kind(p->c, ty) == CFREE_CG_TYPE_ARRAY && toy_lexer_peek(&p->lex).kind == TOK_LBRACKET) || (cfree_cg_type_kind(p->c, ty) == CFREE_CG_TYPE_RECORD && + !toy_type_is_slice(p, toy_ty) && (toy_lexer_peek(&p->lex).kind == TOK_IDENT || toy_lexer_peek(&p->lex).kind == TOK_LBRACE)))) { toy_parser_advance(p); /* = */ @@ -831,7 +888,6 @@ static int toy_parse_let_stmt(ToyParser* p) { } if (toy_parser_match(p, TOK_EQ)) { - ToyTypeId init_toy_type; has_init = 1; if (!inferred && toy_type_is_ptr(p, ty) && p->cur.kind == TOK_IDENT && toy_sym_is(p, toy_tok_sym(p, p->cur), "NULL") && @@ -867,7 +923,9 @@ static int toy_parse_let_stmt(ToyParser* p) { if (!toy_add_local_typed(p, name, ty, toy_ty, slot, is_var)) return 0; if (has_init) { - if (copy_record_init) { + if (toy_type_is_slice(p, init_toy_type)) { + if (!toy_copy_record_lvalue_to_local(p, init_ty, slot, ty)) return 0; + } else if (copy_record_init) { if (!toy_store_record_value_as(p, init_ty, slot, ty)) return 0; } else { cfree_cg_push_local(p->cg, slot); @@ -1377,6 +1435,7 @@ static int toy_parse_stmt(ToyParser* p) { ToyTypeId lhs_toy_type = TOY_TYPE_NONE; CfreeCgTypeId root_ty = CFREE_CG_TYPE_NONE; int root_mutable = 1; + int lhs_slice_metadata = 0; toy_parser_advance(p); { ToyVar* v = toy_find_var(p, name); @@ -1390,10 +1449,13 @@ static int toy_parse_stmt(ToyParser* p) { lhs_toy_type = g->toy_type; root_mutable = g->mutable; } - if (v && cfree_cg_type_kind(p->c, v->type) == CFREE_CG_TYPE_RECORD) { + if (v && (cfree_cg_type_kind(p->c, v->type) == CFREE_CG_TYPE_ARRAY || + cfree_cg_type_kind(p->c, v->type) == CFREE_CG_TYPE_RECORD)) { toy_push_var_lvalue(p, v); lhs_ty = v->type; - } else if (g && cfree_cg_type_kind(p->c, g->type) == CFREE_CG_TYPE_RECORD) { + } else if (g && + (cfree_cg_type_kind(p->c, g->type) == CFREE_CG_TYPE_ARRAY || + cfree_cg_type_kind(p->c, g->type) == CFREE_CG_TYPE_RECORD)) { cfree_cg_push_symbol_lvalue(p->cg, g->sym, 0); lhs_ty = g->type; } else { @@ -1408,13 +1470,14 @@ static int toy_parse_stmt(ToyParser* p) { for (;;) { if (toy_parser_match(p, TOK_LBRACKET)) { CfreeCgTypeId idx_ty = toy_parse_expr(p); + lhs_slice_metadata = 0; if (idx_ty == CFREE_CG_TYPE_NONE) return 0; if (!toy_parser_expect(p, TOK_RBRACKET)) { toy_error(p, p->cur.loc, "expected ']' after index"); return 0; } if (idx_ty != p->int_type) { - toy_error(p, p->cur.loc, "index must be i64"); + toy_error(p, p->cur.loc, "index must be isize"); return 0; } if (cfree_cg_type_kind(p->c, lhs_ty) == CFREE_CG_TYPE_PTR) { @@ -1438,6 +1501,10 @@ static int toy_parse_stmt(ToyParser* p) { } else if (cfree_cg_type_kind(p->c, lhs_ty) == CFREE_CG_TYPE_ARRAY) { lhs_ty = cfree_cg_type_array_elem(p->c, lhs_ty); lhs_toy_type = toy_type_array_elem(p, lhs_toy_type); + } else if (toy_type_is_slice(p, lhs_toy_type)) { + lhs_ty = toy_emit_slice_index_lvalue(p, lhs_ty, lhs_toy_type, + &lhs_toy_type); + if (lhs_ty == CFREE_CG_TYPE_NONE) return 0; } else { toy_error(p, p->cur.loc, "cannot index non-array/non-pointer"); return 0; @@ -1446,6 +1513,7 @@ static int toy_parse_stmt(ToyParser* p) { continue; } if (toy_parser_match(p, TOK_DOTSTAR)) { + lhs_slice_metadata = 0; if (cfree_cg_type_kind(p->c, lhs_ty) != CFREE_CG_TYPE_PTR) { toy_error(p, p->cur.loc, "cannot dereference non-pointer"); return 0; @@ -1499,6 +1567,8 @@ static int toy_parse_stmt(ToyParser* p) { } } cfree_cg_field(p->cg, field_index); + lhs_slice_metadata = toy_type_is_slice(p, lhs_toy_type) && + (field_index == 0 || field_index == 1); lhs_ty = field.type; lhs_toy_type = (named && field_index < named->nfields) ? named->fields[field_index].toy_type @@ -1517,6 +1587,10 @@ static int toy_parse_stmt(ToyParser* p) { toy_error(p, p->cur.loc, "cannot assign to immutable local"); return 0; } + if (lhs_slice_metadata) { + toy_error(p, p->cur.loc, "cannot assign to slice metadata"); + return 0; + } { CfreeCgTypeId expr_ty = toy_parse_expr(p); ToyTypeId expr_toy_type = p->last_type; @@ -1559,6 +1633,14 @@ static int toy_parse_stmt(ToyParser* p) { toy_error(p, p->cur.loc, "type mismatch in assignment"); return 0; } + if (toy_type_is_slice(p, v->toy_type)) { + if (!toy_copy_record_lvalue_to_var(p, expr_ty, v, NULL)) return 0; + if (!toy_parser_expect(p, TOK_SEMI)) { + toy_error(p, p->cur.loc, "expected ';' after assignment"); + return 0; + } + return 1; + } toy_push_var_lvalue(p, v); cfree_cg_swap(p->cg); if (expr_ty != v->type) cfree_cg_bitcast(p->cg, v->type); @@ -1577,6 +1659,14 @@ static int toy_parse_stmt(ToyParser* p) { toy_error(p, p->cur.loc, "type mismatch in assignment"); return 0; } + if (toy_type_is_slice(p, g->toy_type)) { + if (!toy_copy_record_lvalue_to_var(p, expr_ty, NULL, g)) return 0; + if (!toy_parser_expect(p, TOK_SEMI)) { + toy_error(p, p->cur.loc, "expected ';' after assignment"); + return 0; + } + return 1; + } cfree_cg_push_symbol_lvalue(p->cg, g->sym, 0); cfree_cg_swap(p->cg); if (expr_ty != g->type) cfree_cg_bitcast(p->cg, g->type); diff --git a/lang/toy/types.c b/lang/toy/types.c @@ -184,6 +184,15 @@ fn_done: uint64_t count; CfreeCgTypeId elem; ToyTypeId elem_toy_type; + if (toy_parser_match(p, TOK_RBRACKET)) { + ToyTypeId slice_toy_type; + elem = toy_parse_type(p); + if (elem == CFREE_CG_TYPE_NONE) return CFREE_CG_TYPE_NONE; + elem_toy_type = p->last_type; + slice_toy_type = toy_type_register_slice(p, elem, elem_toy_type); + return toy_type_finish(p, toy_type_cg(p, slice_toy_type), + slice_toy_type); + } if (p->cur.kind != TOK_NUMBER || p->cur.is_float || p->cur.int_value < 0) { toy_error(p, p->cur.loc, "expected array count"); @@ -559,6 +568,32 @@ ToyTypeId toy_type_register_ptr(ToyParser* p, CfreeCgTypeId cg, return toy_type_add(p, &type); } +ToyTypeId toy_type_register_slice(ToyParser* p, CfreeCgTypeId elem_cg, + ToyTypeId elem) { + CfreeCgField fields[2]; + CfreeCgTypeId ptr_ty; + ToyType type; + size_t i; + if (elem == TOY_TYPE_NONE || elem_cg == CFREE_CG_TYPE_NONE) + return TOY_TYPE_NONE; + for (i = 0; i < p->type_table.ntypes; ++i) { + ToyType* existing = &p->type_table.types[i]; + if (existing->kind == TOY_TYPE_SLICE && existing->elem == elem) + return (ToyTypeId)(i + 1u); + } + ptr_ty = cfree_cg_type_ptr(p->c, elem_cg, 0); + memset(fields, 0, sizeof fields); + fields[0].name = cfree_sym_intern(p->c, "ptr"); + fields[0].type = ptr_ty; + fields[1].name = cfree_sym_intern(p->c, "len"); + fields[1].type = p->int_type; + memset(&type, 0, sizeof type); + type.kind = TOY_TYPE_SLICE; + type.cg = cfree_cg_type_record(p->c, 0, fields, 2); + type.elem = elem; + return toy_type_add(p, &type); +} + ToyTypeId toy_type_register_func(ToyParser* p, CfreeCgTypeId cg, ToyTypeId ret, const ToyTypeId* params, size_t nparams, int variadic) { @@ -611,6 +646,22 @@ ToyTypeId toy_type_array_elem(ToyParser* p, ToyTypeId id) { return type->kind == TOY_TYPE_ARRAY ? type->elem : TOY_TYPE_NONE; } +ToyTypeId toy_type_slice_elem(ToyParser* p, ToyTypeId id) { + const ToyType* type = toy_type_get(p, id); + if (!type) return TOY_TYPE_NONE; + if (type->kind == TOY_TYPE_ALIAS || type->kind == TOY_TYPE_QUALIFIED) + return toy_type_slice_elem(p, type->base); + return type->kind == TOY_TYPE_SLICE ? type->elem : TOY_TYPE_NONE; +} + +int toy_type_is_slice(ToyParser* p, ToyTypeId id) { + const ToyType* type = toy_type_get(p, id); + if (!type) return 0; + if (type->kind == TOY_TYPE_ALIAS || type->kind == TOY_TYPE_QUALIFIED) + return toy_type_is_slice(p, type->base); + return type->kind == TOY_TYPE_SLICE; +} + static int toy_anon_record_types_match(ToyParser* p, CfreeCgTypeId expected, CfreeCgTypeId actual) { uint32_t i; @@ -657,6 +708,8 @@ int toy_type_accepts_type(ToyParser* p, ToyTypeId expected, ToyTypeId actual) { return exp->count == act->count && toy_type_accepts_type(p, exp->elem, act->elem); } + if (exp->kind == TOY_TYPE_SLICE && act->kind == TOY_TYPE_SLICE) + return toy_type_accepts_type(p, exp->elem, act->elem); if (exp->kind == TOY_TYPE_FUNC && act->kind == TOY_TYPE_FUNC) { size_t i; if (exp->nparams != act->nparams || exp->variadic != act->variadic) diff --git a/test/toy/cases/124_slices.expected b/test/toy/cases/124_slices.expected @@ -0,0 +1 @@ +42 diff --git a/test/toy/cases/124_slices.toy b/test/toy/cases/124_slices.toy @@ -0,0 +1,13 @@ +fn sum(xs: []i64): i64 { + return xs[0] + xs[1] + xs.len; +} + +fn main(): i64 { + var xs: [5]i64 = [10, 20, 30, 40, 50]; + let sl_tail: []i64 = xs[1:4]; + let mid: []i64 = sl_tail[1:3]; + var moving: []i64 = xs[0:1]; + xs[3] = xs[3] - 30; + moving = mid; + return sum(moving) + sl_tail.ptr[2] - 10; +} diff --git a/test/toy/err/invalid_slice_base.expected b/test/toy/err/invalid_slice_base.expected @@ -0,0 +1 @@ +cannot slice non-array/non-slice diff --git a/test/toy/err/invalid_slice_base.toy b/test/toy/err/invalid_slice_base.toy @@ -0,0 +1,5 @@ +fn main(): i64 { + let x: i64 = 1; + let s: []i64 = x[0:1]; + return s.len; +} diff --git a/test/toy/err/invalid_slice_index_type.expected b/test/toy/err/invalid_slice_index_type.expected @@ -0,0 +1 @@ +index must be isize diff --git a/test/toy/err/invalid_slice_index_type.toy b/test/toy/err/invalid_slice_index_type.toy @@ -0,0 +1,5 @@ +fn main(): i64 { + var xs: [2]i64 = [1, 2]; + let s: []i64 = xs[0:2]; + return s[true]; +} diff --git a/test/toy/err/invalid_slice_len_assign.expected b/test/toy/err/invalid_slice_len_assign.expected @@ -0,0 +1 @@ +cannot assign to slice metadata diff --git a/test/toy/err/invalid_slice_len_assign.toy b/test/toy/err/invalid_slice_len_assign.toy @@ -0,0 +1,6 @@ +fn main(): i64 { + var xs: [2]i64 = [1, 2]; + var s: []i64 = xs[0:2]; + s.len = 1; + return s[0]; +}