From: Brendan Hansen Date: Tue, 7 Feb 2023 02:52:43 +0000 (-0600) Subject: re-wrote much of core.iter to be cleaner X-Git-Url: https://git.brendanfh.com/?a=commitdiff_plain;h=ea235821caf3f5d110122310b9ede7bf518ed021;p=onyx.git re-wrote much of core.iter to be cleaner --- diff --git a/compiler/src/checker.c b/compiler/src/checker.c index 87f1bd14..ae5cdbde 100644 --- a/compiler/src/checker.c +++ b/compiler/src/checker.c @@ -2534,7 +2534,29 @@ CheckStatus check_overloaded_function(AstOverloadedFunction* ofunc) { } if (ofunc->expected_return_node) { - ofunc->expected_return_type = type_build_from_ast(context.ast_alloc, ofunc->expected_return_node); + AstType *expected_return_node = (AstType *) strip_aliases((AstNode *) ofunc->expected_return_node); + + if (expected_return_node->kind == Ast_Kind_Poly_Struct_Type) { + // + // When you declare the expected return type of a #match'ed procedure to + // be a polymorphic structure type, a special case has to happen. By trying + // to build the type, the polymorphic structure will be given a type-id. + // type_build_from_ast() will never return a polymorphic structure type + // because that is never valid in the type system. However, we can by-pass + // this and look it up directly using type_lookup_by_id. + type_build_from_ast(context.ast_alloc, expected_return_node); + + if (expected_return_node->type_id) { + ofunc->expected_return_type = type_lookup_by_id(expected_return_node->type_id); + + // Return early here because the following code does not work with a + // polymorphic expected return type. + bh_imap_free(&all_overloads); + return Check_Success; + } + } + + ofunc->expected_return_type = type_build_from_ast(context.ast_alloc, expected_return_node); if (!ofunc->expected_return_type) YIELD(ofunc->token->pos, "Waiting to construct expected return type."); bh_arr_each(bh__imap_entry, entry, all_overloads.entries) { diff --git a/compiler/src/symres.c b/compiler/src/symres.c index fbca2f71..bbd01ace 100644 --- a/compiler/src/symres.c +++ b/compiler/src/symres.c @@ -1493,8 +1493,12 @@ static SymresStatus symres_process_directive(AstNode* directive) { Scope *scope = get_scope_from_node_or_create((AstNode *) inject->dest); if (scope == NULL) { - onyx_report_error(inject->token->pos, Error_Critical, "Cannot #inject here."); - return Symres_Error; + if (context.cycle_almost_detected >= 1) { + onyx_report_error(inject->token->pos, Error_Critical, "Cannot #inject here."); + return Symres_Error; + } + + return Symres_Yield_Macro; } AstBinding *binding = onyx_ast_node_new(context.ast_alloc, sizeof(AstBinding), Ast_Kind_Binding); diff --git a/compiler/src/types.c b/compiler/src/types.c index e0db986b..296bcaec 100644 --- a/compiler/src/types.c +++ b/compiler/src/types.c @@ -1068,11 +1068,16 @@ const char* type_get_name(Type* type) { case Type_Kind_Basic: return type->Basic.name; case Type_Kind_Pointer: return bh_aprintf(global_scratch_allocator, "^%s", type_get_name(type->Pointer.elem)); case Type_Kind_Array: return bh_aprintf(global_scratch_allocator, "[%d] %s", type->Array.count, type_get_name(type->Array.elem)); + + case Type_Kind_PolyStruct: + return type->PolyStruct.name; + case Type_Kind_Struct: if (type->Struct.name) return type->Struct.name; else return ""; + case Type_Kind_Enum: if (type->Enum.name) return type->Enum.name; diff --git a/compiler/src/utils.c b/compiler/src/utils.c index 608a63f2..fe06c8db 100644 --- a/compiler/src/utils.c +++ b/compiler/src/utils.c @@ -620,6 +620,19 @@ static TypeMatch ensure_overload_returns_correct_type_job(void *raw_data) { Type *return_type = func->type->Function.return_type; if (return_type == &type_auto_return) return TYPE_MATCH_YIELD; + // See the note about using Polymorphic Structures as expected return types, + // in check_overloaded_function(). + if (expected_type->kind == Type_Kind_PolyStruct) { + if ( return_type->kind == Type_Kind_Struct + && return_type->Struct.constructed_from + && return_type->Struct.constructed_from->type_id == expected_type->id) { + return TYPE_MATCH_SUCCESS; + } + + report_incorrect_overload_expected_type(return_type, expected_type, func->token, data->group); + return TYPE_MATCH_FAILED; + } + if (!types_are_compatible(return_type, expected_type)) { report_incorrect_overload_expected_type(return_type, expected_type, func->token, data->group); return TYPE_MATCH_FAILED; diff --git a/core/container/iter.onyx b/core/container/iter.onyx index f9bd4e9e..06cac02f 100644 --- a/core/container/iter.onyx +++ b/core/container/iter.onyx @@ -1,20 +1,75 @@ package core.iter use core {memory, alloc, array, Pair} +use core.intrinsics.types {type_is_struct} + + +// +// Iterator is a builtin type known by the compiler, as Iterators +// can be used in for-loops natively without any translation. +#inject Iterator { + filter :: filter; + map :: map; + zip :: zip; + + take_one :: take_one; + take :: take; + take_while :: take_while; + skip :: skip; + skip_while :: skip_while; + + enumerate :: enumerate; + + fold :: fold; + count :: count; + some :: some; + every :: every; +} -as_iter :: #match {} + +// +// Thge standard function to convert something to an Iterator. +// For-loops currently do not use this function to determine +// how to iterate over something unknown, but that could be +// a feature down the line. +as_iter :: #match -> Iterator {} + +// +// Helper interface to test if something can be passed to +// as_iter successfully. Iterable :: interface (t: $T) { { as_iter(t) } -> Iterator; } -close :: (it: Iterator($T)) { +// +// Helper function to get the next value out of an iterator. +next :: (it: Iterator) -> (it.Iter_Type, bool) { + return it.next(it.data); +} + +// +// Helper function to close an iterator, if a close function +// is defined. +close :: (it: Iterator) { if it.close != null_proc { it.close(it.data); } } -// Implicit iterator +// +// Implicit iterator creation +// +// The following overloads of as_iter all for an automatic +// definition of how to declare an iterator, provided the +// type has the necessary methods. +// + +#overload #precedence 10000 +as_iter :: (x: ^$T/ImplicitIterator) => { + x->iter_open(); + return generator_no_copy(x, T.iter_next, T.iter_close); +} #local ImplicitIterator :: interface (t: $T) { @@ -30,497 +85,284 @@ ImplicitIterator :: interface (t: $T) { } -> bool; } -#overload #precedence 10000 -as_iter :: (x: ^$T/ImplicitIterator) => { - x->iter_open(); - return generator_no_copy(x, T.iter_next, T.iter_close); -} - -// nocheckin -// Does it make sense for the language to have -// the iterator "methods" as actual methods -// on every iterator type? Or should you -// just have to use the `iter.` variants -// instead? -#inject Iterator { - filter :: filter; - map :: map; - zip :: zip; - take_one :: take_one; - take :: take; - take_while :: take_while; - skip :: skip; - skip_while :: skip_while; - - enumerate :: enumerate; +#overload #precedence 10000 +as_iter :: macro (x: $T/HasAsIter) => x->as_iter(); - fold :: fold; - count :: count; - some :: some; - every :: every; +#local +HasAsIter :: interface (t: $T) { + { t->as_iter() } -> Iterator; } -// Iterator Modifiers +// +// Iterator Transformers +// +// Most of these procedures come in two variants, +// one that takes a context paramter, and one that does not. + +// +// Only yields the values for which the predicate is true. filter :: #match #local {} + #overload -filter :: (it: Iterator($T), predicate: (T) -> bool, allocator := context.temp_allocator) -> Iterator(T) { - FilterIterator :: struct (T: type_expr) { - iterator: Iterator(T); - predicate: (T) -> bool; - allocator: Allocator; - } +filter :: (it: Iterator($T), predicate: (T) -> bool) => + generator( + ^.{ iterator = it, predicate = predicate }, + + fi => { + value, cont := next(fi.iterator); + if cont { + while !fi.predicate(value) { + value, cont = next(fi.iterator); + if !cont do return value, false; + } - filter_iterator := new(FilterIterator(T), allocator=allocator); - filter_iterator.iterator = it; - filter_iterator.predicate = predicate; - filter_iterator.allocator = allocator; - - next :: (fi: ^FilterIterator($T)) -> (T, bool) { - value, cont := fi.iterator.next(fi.iterator.data); - if cont { - while !fi.predicate(value) { - value, cont = fi.iterator.next(fi.iterator.data); - if !cont do return value, false; + return value, true; } - return value, true; - } else { return value, false; - } - } - - close :: (fi: ^FilterIterator($T)) { - if fi.iterator.close != null_proc do fi.iterator.close(fi.iterator.data); - raw_free(fi.allocator, fi); - } + }, - return .{ - data = filter_iterator, - next = #solidify next { T=T }, - close = #solidify close { T=T }, - }; -} + fi => { close(fi.iterator); }); #overload -filter :: (it: Iterator($T), ctx: $Ctx, predicate: (T, Ctx) -> bool, allocator := context.temp_allocator) -> Iterator(T) { - FilterIterator :: struct (T: type_expr, Ctx: type_expr) { - iterator: Iterator(T); - predicate: (T, Ctx) -> bool; - ctx: Ctx; - allocator: Allocator; - } +filter :: (it: Iterator($T), ctx: $Ctx, predicate: (T, Ctx) -> bool) => + generator( + ^.{ iterator = it, predicate = predicate, ctx = ctx }, + + fi => { + value, cont := next(fi.iterator); + if cont { + while !fi.predicate(value, fi.ctx) { + value, cont = next(fi.iterator); + if !cont do return value, false; + } - filter_iterator := new(FilterIterator(T, Ctx), allocator=allocator); - filter_iterator.iterator = it; - filter_iterator.predicate = predicate; - filter_iterator.ctx = ctx; - filter_iterator.allocator = allocator; - - next :: (fi: ^FilterIterator($T, $_)) -> (T, bool) { - value, cont := fi.iterator.next(fi.iterator.data); - if cont { - while !fi.predicate(value, fi.ctx) { - value, cont = fi.iterator.next(fi.iterator.data); - if !cont do return value, false; + return value, true; } - return value, true; - } else { return value, false; - } - } + }, - close :: (fi: ^FilterIterator($T, $_)) { - if fi.iterator.close != null_proc do fi.iterator.close(fi.iterator.data); - raw_free(fi.allocator, fi); - } + fi => { close(fi.iterator); }); - return .{ - data = filter_iterator, - next = #solidify next { T=T, _=Ctx }, - close = #solidify close { T=T, _=Ctx }, - }; -} +// +// Transforms every value that comes out of an iterator +// using the transform function. map :: #match #local {} #overload -map :: (it: Iterator($T), transform: (T) -> $R, allocator := context.temp_allocator) -> Iterator(R) { - MapIterator :: struct (T: type_expr, R: type_expr) { - iterator: Iterator(T); - transform: (T) -> R; - allocator: Allocator; - } +map :: (it: Iterator($T), transform: (T) -> $R) => + generator( + ^.{ iterator = it, transform = transform }, - map_iterator := new(MapIterator(T, R), allocator=allocator); - map_iterator.iterator = it; - map_iterator.transform = transform; - map_iterator.allocator = allocator; - - next :: (mi: ^MapIterator($T, $R)) -> (R, bool) { - value, cont := mi.iterator.next(mi.iterator.data); - if !cont do return .{}, false; - - return mi.transform(value), true; - } + mi => { + value, cont := next(mi.iterator); + if cont { + return mi.transform(value), true; + } - close :: (mi: ^MapIterator($T, $R)) { - if mi.iterator.close != null_proc do mi.iterator.close(mi.iterator.data); - raw_free(mi.allocator, mi); - } + return .{}, false; + }, - return .{ - data = map_iterator, - next = #solidify next { T=T, R=R }, - close = #solidify close { T=T, R=R }, - }; -} + mi => { close(mi.iterator); }) #overload -map :: (it: Iterator($T), ctx: $Ctx, transform: (T, Ctx) -> $R, allocator := context.temp_allocator) -> Iterator(R) { - MapIterator :: struct (T: type_expr, R: type_expr, Ctx: type_expr) { - iterator: Iterator(T); - transform: (T, Ctx) -> R; - ctx: Ctx; - allocator: Allocator; - } - - map_iterator := new(MapIterator(T, R, Ctx), allocator=allocator); - map_iterator.iterator = it; - map_iterator.transform = transform; - map_iterator.ctx = ctx; - map_iterator.allocator = allocator; - - next :: (mi: ^MapIterator($T, $R, $Ctx)) -> (R, bool) { - value, cont := mi.iterator.next(mi.iterator.data); - if !cont do return .{}, false; +map :: (it: Iterator($T), ctx: $Ctx, transform: (T, Ctx) -> $R) => + generator( + ^.{ iterator = it, transform = transform, ctx = ctx }, - return mi.transform(value, mi.ctx), true; - } + mi => { + value, cont := next(mi.iterator); + if cont { + return mi.transform(value, mi.ctx), true; + } - close :: (mi: ^MapIterator($T, $R, $Ctx)) { - if mi.iterator.close != null_proc do mi.iterator.close(mi.iterator.data); - raw_free(mi.allocator, mi); - } + return .{}, false; + }, - return .{ - data = map_iterator, - next = #solidify next { T=T, R=R, Ctx=Ctx }, - close = #solidify close { T=T, R=R, Ctx=Ctx }, - }; -} + mi => { close(mi.iterator); }) -take_one :: (it: Iterator($T), no_close := false) -> (T, bool) { - ret, cont := it.next(it.data); - if !cont && !no_close { - if it.close != null_proc do it.close(it.data); - } - return ret, cont; -} -// Macro that allows you to extract elements from an iterator in a simple way: -// -// value: i32; -// iterator: Iterator(i32) = ...; // -// if #(value) << iterator { -// ...iterater closed... -// } -#operator << macro (dest: Code, it: Iterator($T)) -> bool { - take_one :: take_one - - cont: bool; - (#unquote dest), cont = take_one(it); - return !cont; -} - -take :: (it: Iterator($T), count: u32, allocator := context.temp_allocator) -> Iterator(T) { - TakeIterator :: struct (T: type_expr) { - iterator: Iterator(T); - remaining: u32; - allocator: Allocator; - } - - take_iterator := new(TakeIterator(T), allocator=allocator); - take_iterator.iterator = it; - take_iterator.remaining = count; - take_iterator.allocator = allocator; - - next :: ($T: type_expr, ti: ^TakeIterator(T)) -> (T, bool) { - if ti.remaining == 0 do return .{}, false; - - ti.remaining -= 1; - return ti.iterator.next(ti.iterator.data); - } +// Only yields the first `count` values, then closes. +take :: (it: Iterator($T), count: u32) -> Iterator(T) { + return generator( + ^.{ iterator = it, remaining = count }, - close :: ($T: type_expr, ti: ^TakeIterator(T)) { - if ti.iterator.close != null_proc do ti.iterator.close(ti.iterator.data); - raw_free(ti.allocator, ti); - } + ti => { + if ti.remaining > 0 { + ti.remaining -= 1; + return next(ti.iterator); + } + + return .{}, false; + }, - return .{ - data = take_iterator, - next = #solidify next { T=T }, - close = #solidify close { T=T }, - }; + ti => { close(ti.iterator); }); } -take_while :: (it: Iterator($T), predicate: (T) -> bool, allocator := context.temp_allocator) -> Iterator(T) { - TakeIterator :: struct (T: type_expr) { - iterator: Iterator(T); - predicate: (T) -> bool; - allocator: Allocator; - } - take_iterator := new(TakeIterator(T), allocator=allocator); - take_iterator.iterator = it; - take_iterator.predicate = predicate; - take_iterator.allocator = allocator; +// +// Yields values while the predicate returns true. +take_while :: (it: Iterator($T), predicate: (T) -> bool) -> Iterator(T) { + return generator( + ^.{ iterator = it, predicate = predicate }, - next :: ($T: type_expr, ti: ^TakeIterator(T)) -> (T, bool) { - value, cont := ti.iterator.next(ti.iterator.data); - if !cont do return value, false; + ti => { + value, cont := next(ti.iterator); + if !cont do return value, false; - return value, ti.predicate(value); - } - - close :: ($T: type_expr, ti: ^TakeIterator(T)) { - if ti.iterator.close != null_proc do ti.iterator.close(ti.iterator.data); - raw_free(ti.allocator, ti); - } + return value, ti.predicate(value); + }, - return .{ - data = take_iterator, - next = #solidify next { T=T }, - close = #solidify close { T=T }, - }; + ti => { close(ti.iterator); }); } -skip :: (it: Iterator($T), count: u32, allocator := context.temp_allocator) -> Iterator(T) { - SkipIterator :: struct (T: type_expr) { - iterator: Iterator(T); - to_skip: i32; - skipped: bool = false; - allocator: Allocator; - } - skip_iterator := new(SkipIterator(T), allocator=allocator); - skip_iterator.iterator = it; - skip_iterator.to_skip = count; - skip_iterator.allocator = allocator; +// +// Discards the first `count` values and yields all remaining values, +skip :: (it: Iterator($T), count: u32) -> Iterator(T) { + return generator( + ^.{ iterator = it, to_skip = count, skipped = false }, - next :: (si: ^SkipIterator($T)) -> (T, bool) { - while !si.skipped && si.to_skip > 0 { - si.to_skip -= 1; - value, cont := si.iterator.next(si.iterator.data); + si => { + while !si.skipped && si.to_skip > 0 { + si.to_skip -= 1; + value, cont := next(si.iterator); - if !cont { - si.skipped = true; - return value, false; + if !cont { + si.skipped = true; + return value, false; + } } - } - - return si.iterator.next(si.iterator.data); - } - close :: (si: ^SkipIterator($T)) { - if si.iterator.close != null_proc do si.iterator.close(si.iterator.data); - raw_free(si.allocator, si); - } + return next(si.iterator); + }, - return .{ - data = skip_iterator, - next = #solidify next { T=T }, - close = #solidify close { T=T }, - }; + si => { close(si.iterator); }); } + +// +// Discards values while the predicate is true, then yields all values. skip_while :: #match #local {} #overload -skip_while :: (it: Iterator($T), predicate: (T) -> bool, allocator := context.temp_allocator) -> Iterator(T) { - SkipIterator :: struct (T: type_expr) { - iterator: Iterator(T); - allocator: Allocator; - predicate: (T) -> bool; - skipped := false; - } - - skip_iterator := new(SkipIterator(T), allocator=allocator); - skip_iterator.iterator = it; - skip_iterator.allocator = allocator; - skip_iterator.predicate = predicate; +skip_while :: (it: Iterator($T), predicate: (T) -> bool) -> Iterator(T) { + return generator( + ^.{ iterator = it, predicate = predicate, skipped = false }, - next :: (si: ^SkipIterator($T)) -> (T, bool) { - while !si.skipped { - value, cont := si.iterator.next(si.iterator.data); + si => { + while !si.skipped { + value, cont := next(si.iterator); - if !cont { - si.skipped = true; - return value, false; - } + if !cont { + si.skipped = true; + return value, false; + } - if !si.predicate(value) { - si.skipped = true; - return value, true; + if !si.predicate(value) { + si.skipped = true; + return value, true; + } } - } - - return si.iterator.next(si.iterator.data); - } - close :: (si: ^SkipIterator($T)) { - if si.iterator.close != null_proc do si.iterator.close(si.iterator.data); - raw_free(si.allocator, si); - } + return next(si.iterator); + }, - return .{ - data = skip_iterator, - next = #solidify next { T=T }, - close = #solidify close { T=T }, - }; + si => { close(si.iterator); }); } #overload -skip_while :: (it: Iterator($T), ctx: $Ctx, predicate: (T, Ctx) -> bool, allocator := context.temp_allocator) -> Iterator(T) { - SkipIterator :: struct (T: type_expr, Ctx: type_expr) { - iterator: Iterator(T); - allocator: Allocator; - predicate: (T, Ctx) -> bool; - ctx: Ctx; - skipped := false; - } +skip_while :: (it: Iterator($T), ctx: $Ctx, predicate: (T, Ctx) -> bool) -> Iterator(T) { + return generator( + ^.{ iterator = it, ctx = ctx, predicate = predicate }, - skip_iterator := new(SkipIterator(T, Ctx), allocator=allocator); - skip_iterator.iterator = it; - skip_iterator.allocator = allocator; - skip_iterator.predicate = predicate; - skip_iterator.ctx = ctx; + si => { + while !si.skipped { + value, cont := next(si.iterator); - next :: (si: ^SkipIterator($T, $Ctx)) -> (T, bool) { - while !si.skipped { - value, cont := si.iterator.next(si.iterator.data); + if !cont { + si.skipped = true; + return value, false; + } - if !cont { - si.skipped = true; - return value, false; + if !si.predicate(value, si.ctx) { + si.skipped = true; + return value, true; + } } - if !si.predicate(value, si.ctx) { - si.skipped = true; - return value, true; - } - } - - return si.iterator.next(si.iterator.data); - } - - close :: (si: ^SkipIterator($T, $Ctx)) { - if si.iterator.close != null_proc do si.iterator.close(si.iterator.data); - raw_free(si.allocator, si); - } + return next(si.iterator); + }, - return .{ - data = skip_iterator, - next = #solidify next { T=T, Ctx=Ctx }, - close = #solidify close { T=T, Ctx=Ctx }, - }; + si => { close(si.iterator); }); } -zip :: (left_iterator: Iterator($T), right_iterator: Iterator($R), allocator := context.temp_allocator) -> Iterator(Pair(T, R)) { - ZippedIterator :: struct (T: type_expr, R: type_expr) { - iterator1: Iterator(T); - iterator2: Iterator(R); - allocator: Allocator; - } - - zipped_iterator := new(ZippedIterator(T, R), allocator=allocator); - zipped_iterator.iterator1 = left_iterator; - zipped_iterator.iterator2 = right_iterator; - zipped_iterator.allocator = allocator; - next :: (zi: ^ZippedIterator($T, $R)) -> (Pair(T, R), bool) { - v1, cont1 := zi.iterator1.next(zi.iterator1.data); - v2, cont2 := zi.iterator2.next(zi.iterator2.data); +// +// Combines two iterators into one by yielding a Pair of +// the value from each of the iterators. +zip :: (left_iterator: Iterator($T), right_iterator: Iterator($R)) -> Iterator(Pair(T, R)) { + return generator( + ^.{ left_iter = left_iterator, right_iter = right_iterator }, - return .{ v1, v2 }, cont1 && cont2; - } + zi => { + v1, cont1 := next(zi.left_iter); + v2, cont2 := next(zi.right_iter); - close :: (zi: ^ZippedIterator($T, $R)) { - if zi.iterator1.close != null_proc do zi.iterator1.close(zi.iterator1.data); - if zi.iterator2.close != null_proc do zi.iterator2.close(zi.iterator2.data); - raw_free(zi.allocator, zi); - } + return Pair.make(v1, v2), cont1 && cont2; + }, - return .{ - data = zipped_iterator, - next = #solidify next { T=T, R=R }, - close = #solidify close { T=T, R=R }, - }; + zi => { close(zi.left_iter); close(zi.right_iter); }); } -concat :: (iters: ..Iterator($T)) -> Iterator(T) { - Context :: struct (T: type_expr) { - iters: [] Iterator(T); - idx: u32; - } - c := new(Context(T), allocator=context.temp_allocator); - c.iters = memory.copy_slice(iters, allocator=context.temp_allocator); - c.idx = 0; +// +// Combines iterators by first yielding all values from +// one, then yielding all values from the next, and so on. +concat :: (iters: ..Iterator($T)) -> Iterator(T) { + return generator( + ^.{ + iters = memory.copy_slice(iters, context.temp_allocator), + idx = 0 + }, - next :: (use c: ^Context($T)) -> (T, bool) { - while true { - if idx >= iters.count do return .{}, false; + c => { + while c.idx < c.iters.count { + curr_iter := c.iters[c.idx]; + value, valid := next(curr_iter); + if valid { + return value, true; + } - curr_iter := ^iters[idx]; - value, valid := curr_iter.next(curr_iter.data); - if valid { - return value, true; + c.idx += 1; } - idx += 1; - } - } - - close :: (use c: ^Context($T)) { - // I don't feel like this should always close ALL the iterators... - // But I don't know what the semantics should be for specifying which - // if any iterators to close. - for^ iters { - if it.close != null_proc do it.close(it.data); - } - } - - return .{ - c, - #solidify next {T=T}, - #solidify close {T=T} - }; + return .{}, false; + }, + + c => { + for^ c.iters { + close(*it); + } + }); } +// +// Yields the same value indefinitely. Useful with iter.zip. const :: (value: $T) -> Iterator(T) { - next :: (data: ^$T) -> (T, bool) { - return *(cast(^T) data), true; - } - - allocated := cast(^T) raw_alloc(context.temp_allocator, sizeof T); - *allocated = value; - - return .{ - data = allocated, - next = #solidify next { T=T }, - }; + return generator(^.{ v = value }, c => (c.v, true)); } -#local Enumeration_Value :: struct (T: type_expr) { - index: i32; - value: T; -} +// +// Yields a value that contains: 1) the value from the iterator, +// and 2) an incrementing integer. enumerate :: #match #local {} #overload @@ -529,36 +371,63 @@ enumerate :: macro (it: $T/Iterable, start_index: i32 = 0) => #overload enumerate :: (it: Iterator($T), start_index: i32 = 0) -> Iterator(Enumeration_Value(T)) { - Enumeration_Context :: struct (T: type_expr) { - iterator: Iterator(T); - current_index: i32; - } + return generator( + ^.{ iterator = it, current_index = start_index }, - ec := make(Enumeration_Context(T), allocator=context.temp_allocator); - ec.iterator = it; - ec.current_index = start_index; + ec => { + value, cont := next(ec.iterator); + if cont { + defer ec.current_index += 1; + return Enumeration_Value(typeof value).{ current_index, value }, true; + } - next :: (use data: ^Enumeration_Context($T)) -> (Enumeration_Value(T), bool) { - value, cont := iterator.next(iterator.data); + return .{}, false; + }, - if !cont do return .{ current_index, .{} }, false; + ec => { close(ec.iterator); }); +} - defer current_index += 1; - return .{ current_index, value }, true; - } +#local Enumeration_Value :: struct (T: type_expr) { + index: i32; + value: T; +} - close :: (use data: ^Enumeration_Context($T)) { - if iterator.close != null_proc do iterator.close(iterator.data); - } - return .{ - data = ec, - next = #solidify next { T = T }, - close = #solidify close { T = T }, - }; + +// +// Extract the next value out of an iterator. Closes it when +// the iterator is out of values, if no_close is false. +take_one :: (it: Iterator($T), no_close := false) -> (T, bool) { + ret, cont := next(it); + if !cont && !no_close { close(it); } + return ret, cont; } -use core.intrinsics.types {type_is_struct} +// Macro that allows you to extract elements from an iterator in a simple way: +// +// value: i32; +// iterator: Iterator(i32) = ...; +// +// if #(value) << iterator { +// ...iterater closed... +// } +#operator << macro (dest: Code, it: Iterator($T)) -> bool { + take_one :: take_one + + cont: bool; + (#unquote dest), cont = take_one(it); + return !cont; +} + + +// +// Iterator creations +// +// Sensible defaults for creating an iterator out of primitive types. +// + +#overload +as_iter :: from_array // // `from_array` has two almost identical implementations, @@ -600,11 +469,11 @@ from_array :: (arr: [] $T) => generator( ); -#overload -as_iter :: from_array - -#overload -as_iter :: (x: ^[..] $T) -> Iterator(T) { +// +// Iterators created from pointers to dynamic arrays are +// special, because they support the #remove directive. +#local +generic_dynamic_array_as_iter :: (x: ^[..] $T, $access: Code, $return_type: type_expr) => { Context :: struct (T: type_expr) { arr: ^[..] T; current: u32; @@ -614,10 +483,10 @@ as_iter :: (x: ^[..] $T) -> Iterator(T) { c.arr = x; c.current = 0; - next :: (use _: ^Context($T)) -> (T, bool) { + next :: (use _: ^Context($T), $access: Code) => { if current < arr.count { defer current += 1; - return arr.data[current], true; + return (#unquote access), true; } else { return .{}, false; @@ -632,48 +501,27 @@ as_iter :: (x: ^[..] $T) -> Iterator(T) { current -= 1; } - return .{ + return return_type.{ data = c, - next = #solidify next { T = T }, + next = #solidify next { T = T, access = access }, remove = #solidify remove { T = T }, }; } -#overload -as_iter :: (x: ^[..] $T, by_pointer: bool) -> Iterator(^T) { - Context :: struct (T: type_expr) { - arr: ^[..] T; - current: u32; - } - - c := make(Context(T), allocator=context.temp_allocator); - c.arr = x; - c.current = 0; - next :: (use _: ^Context($T)) -> (^T, bool) { - if current < arr.count { - defer current += 1; - return ^arr.data[current], true; +#overload +as_iter :: macro (x: ^[..] $T) => { + G :: generic_dynamic_array_as_iter + return G(x, #(arr.data[current]), Iterator(T)); +} - } else { - return null, false; - } - } +#overload +as_iter :: macro (x: ^[..] $T, by_pointer: bool) => { + G :: generic_dynamic_array_as_iter + return G(x, #(^arr.data[current]), Iterator(^T)); +} - remove :: (use _: ^Context($T)) { - // - // This is current - 1 because current will have already - // been incremented by the time this element calls #remove. - array.delete(arr, current - 1); - current -= 1; - } - return .{ - data = c, - next = #solidify next { T = T }, - remove = #solidify remove { T = T }, - }; -} #overload as_iter :: (r: range) => generator( @@ -697,9 +545,17 @@ as_iter :: (r: range) => generator( } }); + +// +// Iterator reducing +// + +// +// Incremently calls `combine` on the yielded value and the +// accumulated value, producing a new accumulated value. Returns +// the final accumulated value. fold :: #match #local {} -// @Cleanup // some way to shorten this would be nice #overload fold :: macro (it: $T/Iterable, init: $R, combine: $S) => #this_package.fold(#this_package.as_iter(it), init, combine); @@ -713,6 +569,9 @@ fold :: (it: Iterator($T), initial_value: $R, combine: (T, R) -> R) -> R { return initial_value; } + +// +// Returns how many times the `cond` was true. count :: #match #local {} #overload @@ -726,6 +585,10 @@ count :: (it: Iterator($T), cond: (T) -> bool) -> i32 { return c; } + + +// +// Returns if `cond` returned true for *any* yielded value. some :: #match #local {} #overload @@ -738,6 +601,9 @@ some :: (it: Iterator($T), cond: (T) -> bool) -> bool { return false; } + +// +// Returns if `cond` returned true for *all* yielded values. every :: #match #local {} #overload @@ -750,8 +616,38 @@ every :: (it: Iterator($T), cond: (T) -> bool) -> bool { return true; } -prod :: (x: $I1/Iterable, y: $I2/Iterable) => { - y_iter := as_iter(y); + +// +// Places all yielded values into a dynamically allocated array, +// using the allocator provided (context.allocator by default). +to_array :: (it: Iterator($T), allocator := context.allocator) -> [..] T { + arr := array.make(T, allocator=allocator); + for v: it do array.push(^arr, v); + + return arr; +} + + +// +// Produces an iterator that first yields all values from the +// first iterable, combined with the first yield value from the +// second iterable. Then, steps the second iterable, and repeats. +// +// For example, +// +// iter.prod(1 .. 4, 1 .. 3) +// +// Would yield: +// (1, 1), (2, 1), (3, 1), (1, 2), (2, 2), (3, 2) +prod :: #match #local {} + +#overload +prod :: macro (x: $I/Iterable, y: $I2/Iterable) => { + return #this_package.prod(x, #this_package.as_iter(y)); +} + +#overload +prod :: (x: $I1/Iterable, y_iter: Iterator(y)) => { y_val, _ := take_one(y_iter); return generator( @@ -782,13 +678,6 @@ prod :: (x: $I1/Iterable, y: $I2/Iterable) => { } -to_array :: (it: Iterator($T), allocator := context.allocator) -> [..] T { - arr := array.make(T, allocator=allocator); - for v: it do array.push(^arr, v); - - return arr; -} - // // Simple iterator comprehensions, in the same vein // as Pythons comprehension syntax. @@ -825,6 +714,10 @@ comp :: macro (i: $I/Iterable, value: Code) => #this_package.comp(#this_package.as_iter(i), value); +// +// Using the polymorph solving system, you can write type +// free versions of arbitrary iterators. This is used +// heavily by many of the functions defined above. // // Maybe at some point an alternate allocator would be good // for this? For now, I think the temporary allocator is sufficient. @@ -868,6 +761,9 @@ generator_no_copy :: (ctx: ^$Ctx, gen: (^Ctx) -> ($T, bool), close: (^Ctx) -> vo #if runtime.Multi_Threading_Enabled { #local sync :: core.sync + // A simple iterator transformer that protects + // the retrieving of the next value by using + // a mutex, making the iterator thread-safe. distributor :: #match #local {} #overload @@ -911,6 +807,14 @@ generator_no_copy :: (ctx: ^$Ctx, gen: (^Ctx) -> ($T, bool), close: (^Ctx) -> vo return .{c, #solidify next {T=it.Iter_Type}, #solidify close {T=it.Iter_Type}}; } + // + // Allows you to easily write a parallelized for-loop over an iterator. + // For example, + // + // iter.parallel_for(1 .. 100, 4, ^.{}) { + // printf("Thread {} has {}!\n", context.thread_id, it); + // } + // parallel_for :: #match #local {} #overload diff --git a/core/container/map.onyx b/core/container/map.onyx index d3dbf7d7..daeb19b6 100644 --- a/core/container/map.onyx +++ b/core/container/map.onyx @@ -3,18 +3,7 @@ package core.map use core {array, hash, memory, math, conv, Optional} use core.intrinsics.onyx { __initialize } -#local ValidKey :: interface (t: $T) { - // In order to use a certain type as a key in a Map, you must - // provide an implementation of core.hash.to_u32() for that type, - // and you must provide an operator overload for ==. - - { hash.to_u32(t) } -> u32; - { t == t } -> bool; -} - -#tag conv.Custom_Format.{ - #solidify format_map {K=Key_Type, V=Value_Type} -} +@conv.Custom_Format.{ #solidify format_map {K=Key_Type, V=Value_Type} } Map :: struct (Key_Type: type_expr, Value_Type: type_expr) where ValidKey(Key_Type) { allocator : Allocator; @@ -32,6 +21,15 @@ Map :: struct (Key_Type: type_expr, Value_Type: type_expr) where ValidKey(Key_Ty } } +#local ValidKey :: interface (t: $T) { + // In order to use a certain type as a key in a Map, you must + // provide an implementation of core.hash.to_u32() for that type, + // and you must provide an operator overload for ==. + + { hash.to_u32(t) } -> u32; + { t == t } -> bool; +} + #inject Map { init :: init has :: has @@ -47,14 +45,15 @@ Map :: struct (Key_Type: type_expr, Value_Type: type_expr) where ValidKey(Key_Ty as_iter :: as_iter } + +#match __make_overload macro (x: ^Map($K, $V), allocator := context.allocator) => #this_package.make(K, V); + make :: ($Key: type_expr, $Value: type_expr, default := Value.{}) -> Map(Key, Value) { map : Map(Key, Value); init(^map, default = default); return map; } -#match __make_overload macro (x: ^Map($K, $V), allocator := context.allocator) => core.map.make(K, V); - init :: (use map: ^Map($K, $V), default := V.{}) { __initialize(map); @@ -67,13 +66,13 @@ init :: (use map: ^Map($K, $V), default := V.{}) { array.init(^entries, allocator=allocator); } +#match builtin.delete core.map.free + free :: (use map: ^Map) { if hashes.data != null do memory.free_slice(^hashes, allocator=allocator); if entries.data != null do array.free(^entries); } -#match builtin.delete core.map.free - put :: (use map: ^Map, key: map.Key_Type, value: map.Value_Type) { lr := lookup(map, key); @@ -100,10 +99,6 @@ get :: (use map: ^Map, key: map.Key_Type) -> map.Value_Type { return default_value; } -#operator [] macro (map: Map($K, $V), key: K) -> V do return core.map.get(^map, key); -#operator []= macro (map: Map($K, $V), key: K, value: V) do core.map.put(^map, key, value); -#operator ^[] macro (map: Map($K, $V), key: K) -> ^V do return core.map.get_ptr(^map, key); - get_ptr :: (use map: ^Map, key: map.Key_Type) -> ^map.Value_Type { lr := lookup(map, key); if lr.entry_index >= 0 do return ^entries[lr.entry_index].value; @@ -132,15 +127,15 @@ delete :: (use map: ^Map, key: map.Key_Type) { array.fast_delete(^entries, lr.entry_index); last := lookup(map, entries[lr.entry_index].key); + if last.entry_prev >= 0 do entries[last.entry_prev].next = lr.entry_index; else do hashes[last.hash_index] = lr.entry_index; } update :: macro (map: ^Map, key: map.Key_Type, body: Code) { - // @Hack // Weird hack because 'lookup' exists at file scope. lookup_ :: lookup - lr := lookup_(map, key); + if lr.entry_index >= 0 { it := ^map.entries[lr.entry_index].value; #unquote body; @@ -189,13 +184,11 @@ literal :: ($Key: type_expr, $Value: type_expr, values: [] MapLiteralValue(Key, return m; } - -#overload core.iter.as_iter as_iter as_iter :: (m: ^Map) => core.iter.generator( ^.{ m = m, i = 0 }, - (ctx) => { + ctx => { if ctx.i >= ctx.m.entries.count { return (typeof ctx.m.entries.data).{}, false; } @@ -204,6 +197,11 @@ as_iter :: (m: ^Map) => return ^ctx.m.entries.data[ctx.i], true; }); + +#operator [] macro (map: Map($K, $V), key: K) -> V { return #this_package.get(^map, key); } +#operator ^[] macro (map: Map($K, $V), key: K) -> ^V { return #this_package.get_ptr(^map, key); } +#operator []= macro (map: Map($K, $V), key: K, value: V) { #this_package.put(^map, key, value); } + // // Private symbols // @@ -220,7 +218,7 @@ as_iter :: (m: ^Map) => if hashes.data == null do init(map); lr := MapLookupResult.{}; - hash_value: u32 = hash.to_u32(key); // You cannot use this type for the key, unless you add an overload. + hash_value: u32 = hash.to_u32(key); lr.hash = hash_value; lr.hash_index = hash_value % hashes.count;