commit 0bbc3338aeb7ede4ee32d37859e5309d1e3605d4
parent 9e883148e4350039f3af0f2552be44a84aeeaa96
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Sun, 17 May 2026 15:47:54 -0700
toy: add fat slices
Diffstat:
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];
+}