feat(stdlib): Iter map/filter/fold with closures

Add int-specialized higher-order Iter helpers that accept fat function
pointers, plus an example using named funcs and capturing closures.
Fix selfhost C backend pointer field access and Array Push indexing.
This commit is contained in:
2026-07-15 16:07:47 +03:00
parent 9efba57b4c
commit f1896183f5
6 changed files with 256 additions and 10 deletions
+1 -1
View File
@@ -3,7 +3,7 @@ SRC := bootstrap/main.nim
OUT := buxc
BUILD_DIR := build
EXAMPLES := hello fibonacci factorial structs enums methods algebraic_enums generics generics_struct generic_infer generic_infer2 extend_generic pattern_matching strings strings2 map result_option try_operator ownership ctfe async concurrency os_time process json iter trait_bounds channel sync jwt stdlib_ergonomics tuples func_ptr map_remove array_iter_extra string_extra multi_closure
EXAMPLES := hello fibonacci factorial structs enums methods algebraic_enums generics generics_struct generic_infer generic_infer2 extend_generic pattern_matching strings strings2 map result_option try_operator ownership ctfe async concurrency os_time process json iter trait_bounds channel sync jwt stdlib_ergonomics tuples func_ptr map_remove array_iter_extra string_extra multi_closure iter_hof
.PHONY: all build dev debug test clean clean-all test-examples selfhost test-golden test-errors selfhost-loop lsp
+1 -1
View File
@@ -48,7 +48,7 @@
| A.2 | String: IsEmpty, ReplaceAll | Чести операции; само first-replace досега | ✅ (тази сесия) |
| A.3 | Os_Exit + Test_AssertEqString / richer asserts | Тестове и CLI без raw `bux_exit` | ✅ (тази сесия) |
| A.4 | Map_Remove / Set polish | Completeness на колекциите | ✅ (тази сесия) |
| A.5 | Iter: map/filter/fold върху closures | Higher-order без boilerplate | |
| A.5 | Iter: map/filter/fold върху closures | Higher-order без boilerplate | ✅ Iter_Map/Filter/FoldInt |
| A.6 | Result helpers: Expect, UnwrapErr, Or | По-малко match boilerplate | ✅ (тази сесия) |
### B — Compiler Correctness (P0)
+14
View File
@@ -143,6 +143,20 @@ struct Iter<T> {
| `Iter_AllEq<T>` | `func Iter_AllEq<T>(it: *Iter<T>, value: T) -> bool` | True if all remaining equal value |
| `Iter_Collect<T>` | `func Iter_Collect<T>(it: *Iter<T>) -> Array<T>` | Collect remaining into a new Array |
### Higher-order (int-specialized)
Take fat function pointers / closures (`func(int) -> int`, `func(int) -> bool`, …).
| Function | Signature | Description |
|----------|-----------|-------------|
| `Iter_MapInt` | `func Iter_MapInt(it: *Iter<int>, f: func(int) -> int) -> Array<int>` | Map each element |
| `Iter_FilterInt` | `func Iter_FilterInt(it: *Iter<int>, pred: func(int) -> bool) -> Array<int>` | Keep matching elements |
| `Iter_FoldInt` | `func Iter_FoldInt(it: *Iter<int>, init: int, f: func(int, int) -> int) -> int` | Left fold |
| `Iter_ForEachInt` | `func Iter_ForEachInt(it: *Iter<int>, f: func(int) -> int)` | Side-effect per element |
| `Iter_AnyInt` | `func Iter_AnyInt(it: *Iter<int>, pred: func(int) -> bool) -> bool` | Any matches pred |
| `Iter_AllInt` | `func Iter_AllInt(it: *Iter<int>, pred: func(int) -> bool) -> bool` | All match pred |
| `Iter_SumInt` | `func Iter_SumInt(it: *Iter<int>) -> int` | Sum remaining ints |
### Example
```bux
import Std::Array::*;
+102
View File
@@ -0,0 +1,102 @@
// Higher-order Iter helpers: Map / Filter / Fold with closures
import Std::Io::{PrintLine, PrintInt};
import Std::Array::{
Array, Array_New, Array_Push, Array_Get, Array_Len, Array_Free
};
import Std::Iter::{
Array_Iter, Iter, Iter_MapInt, Iter_FilterInt, Iter_FoldInt,
Iter_AnyInt, Iter_AllInt, Iter_SumInt, Iter_ForEachInt
};
import Std::Test::{Test_AssertEqInt, Test_AssertTrue, Test_AssertFalse, Test_Pass};
func Double(x: int) -> int {
return x * 2;
}
func IsEven(x: int) -> bool {
return (x % 2) == 0;
}
func Add(acc: int, x: int) -> int {
return acc + x;
}
func Main() -> int {
var nums: Array<int> = Array_New<int>(8);
Array_Push<int>(&nums, 1);
Array_Push<int>(&nums, 2);
Array_Push<int>(&nums, 3);
Array_Push<int>(&nums, 4);
Array_Push<int>(&nums, 5);
// Map with named function
let it1: Iter<int> = Array_Iter<int>(&nums);
var doubled: Array<int> = Iter_MapInt(&it1, Double);
Test_AssertEqInt(Array_Len<int>(&doubled) as int, 5);
Test_AssertEqInt(Array_Get<int>(&doubled, 0), 2);
Test_AssertEqInt(Array_Get<int>(&doubled, 4), 10);
// Filter with named predicate
let it2: Iter<int> = Array_Iter<int>(&nums);
var evens: Array<int> = Iter_FilterInt(&it2, IsEven);
Test_AssertEqInt(Array_Len<int>(&evens) as int, 2);
Test_AssertEqInt(Array_Get<int>(&evens, 0), 2);
Test_AssertEqInt(Array_Get<int>(&evens, 1), 4);
// Fold with named combiner
let it3: Iter<int> = Array_Iter<int>(&nums);
let sum: int = Iter_FoldInt(&it3, 0, Add);
Test_AssertEqInt(sum, 15);
let itSum: Iter<int> = Array_Iter<int>(&nums);
Test_AssertEqInt(Iter_SumInt(&itSum), 15);
// Map with capturing closure
let scale: int = 10;
let it4: Iter<int> = Array_Iter<int>(&nums);
var scaled: Array<int> = Iter_MapInt(&it4, |x: int| -> int {
return x * scale;
});
Test_AssertEqInt(Array_Get<int>(&scaled, 0), 10);
Test_AssertEqInt(Array_Get<int>(&scaled, 2), 30);
// Filter with closure
let minVal: int = 3;
let it5: Iter<int> = Array_Iter<int>(&nums);
var big: Array<int> = Iter_FilterInt(&it5, |x: int| -> bool {
return x >= minVal;
});
Test_AssertEqInt(Array_Len<int>(&big) as int, 3);
Test_AssertEqInt(Array_Get<int>(&big, 0), 3);
// Fold with closure
let it6: Iter<int> = Array_Iter<int>(&nums);
let prod: int = Iter_FoldInt(&it6, 1, |a: int, b: int| -> int {
return a * b;
});
Test_AssertEqInt(prod, 120); // 1*2*3*4*5
// Any / All
let itAny: Iter<int> = Array_Iter<int>(&nums);
Test_AssertTrue(Iter_AnyInt(&itAny, IsEven));
let itAll: Iter<int> = Array_Iter<int>(&nums);
Test_AssertFalse(Iter_AllInt(&itAll, IsEven));
let itEv: Iter<int> = Array_Iter<int>(&evens);
Test_AssertTrue(Iter_AllInt(&itEv, IsEven));
// ForEach (print via side-effect free assert path — just call Double)
let it7: Iter<int> = Array_Iter<int>(&nums);
Iter_ForEachInt(&it7, Double);
PrintInt(sum);
PrintLine("");
PrintInt(prod);
PrintLine("");
Test_Pass("iter_hof");
Array_Free<int>(&nums);
Array_Free<int>(&doubled);
Array_Free<int>(&evens);
Array_Free<int>(&scaled);
Array_Free<int>(&big);
return 0;
}
+95
View File
@@ -107,4 +107,99 @@ func Iter_Collect<T>(it: *Iter<T>) -> Array<T> {
return arr;
}
// ---------------------------------------------------------------------------
// Higher-order helpers (int-specialized; take fat func pointers / closures)
// ---------------------------------------------------------------------------
/* Map each remaining int through f, collect into a new Array */
func Iter_MapInt(it: *Iter<int>, f: func(int) -> int) -> Array<int> {
let remaining: uint = it.len - it.pos;
var cap: uint = remaining;
if cap == 0 {
cap = 1;
}
var out: Array<int> = Array_New<int>(cap);
var i: uint = it.pos;
while i < it.len {
let mapped: int = f(it.data[i]);
Array_Push<int>(&out, mapped);
i = i + 1;
}
return out;
}
/* Keep remaining ints for which pred returns true */
func Iter_FilterInt(it: *Iter<int>, pred: func(int) -> bool) -> Array<int> {
let remaining: uint = it.len - it.pos;
var cap: uint = remaining;
if cap == 0 {
cap = 1;
}
var out: Array<int> = Array_New<int>(cap);
var i: uint = it.pos;
while i < it.len {
let v: int = it.data[i];
if pred(v) {
Array_Push<int>(&out, v);
}
i = i + 1;
}
return out;
}
/* Left-fold remaining ints: f(f(...f(init, x0), x1), ...) */
func Iter_FoldInt(it: *Iter<int>, init: int, f: func(int, int) -> int) -> int {
var acc: int = init;
var i: uint = it.pos;
while i < it.len {
acc = f(acc, it.data[i]);
i = i + 1;
}
return acc;
}
/* Call f for each remaining int (side effects; f's return is ignored) */
func Iter_ForEachInt(it: *Iter<int>, f: func(int) -> int) {
var i: uint = it.pos;
while i < it.len {
let _ignored: int = f(it.data[i]);
i = i + 1;
}
}
/* True if any remaining element satisfies pred */
func Iter_AnyInt(it: *Iter<int>, pred: func(int) -> bool) -> bool {
var i: uint = it.pos;
while i < it.len {
if pred(it.data[i]) {
return true;
}
i = i + 1;
}
return false;
}
/* True if all remaining elements satisfy pred (true if empty) */
func Iter_AllInt(it: *Iter<int>, pred: func(int) -> bool) -> bool {
var i: uint = it.pos;
while i < it.len {
if !pred(it.data[i]) {
return false;
}
i = i + 1;
}
return true;
}
/* Sum remaining ints (specialized fold) */
func Iter_SumInt(it: *Iter<int>) -> int {
var total: int = 0;
var i: uint = it.pos;
while i < it.len {
total = total + it.data[i];
i = i + 1;
}
return total;
}
}
+43 -8
View File
@@ -525,10 +525,24 @@ func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
return;
}
// Field access on value (direct dot): obj.field
// Field access: obj.field — use -> if base is a pointer
if kind == hFieldAccess {
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, ".");
var isPtr: bool = false;
if node.child1 != null as *HirNode {
let childType: String = CBE_GetExprTypeName(cbe.mod, node.child1);
if String_EndsWith(childType, "*") {
isPtr = true;
}
if node.child1.kind == hVar && node.child1.typeKind == tyPointer {
isPtr = true;
}
}
if isPtr {
StringBuilder_Append(&cbe.sb, "->");
} else {
StringBuilder_Append(&cbe.sb, ".");
}
StringBuilder_Append(&cbe.sb, node.strValue);
return;
}
@@ -565,7 +579,7 @@ func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
}
// Index: arr[idx] — emit as arr[idx]
// For Array<T> desugar pattern (fieldPtr "data"), emit bounds-checked access
// For Array/Iter desugar pattern (fieldPtr "data"), emit bounds-checked access
if kind == hIndexPtr {
var isArrayAccess: bool = false;
if node.child1 != null as *HirNode {
@@ -574,12 +588,27 @@ func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
}
}
if isArrayAccess {
CBE_EmitExpr(cbe, node.child1.child1);
StringBuilder_Append(&cbe.sb, ".data[bux_index_check(");
// self.data[i] is a raw pointer index; Bux source inserts
// bux_bounds_check explicitly where needed (Array_Get). Do NOT
// re-check against .len here — Push writes at index == len.
let base: *HirNode = node.child1.child1;
var isPtr: bool = false;
if base != null as *HirNode {
let childType: String = CBE_GetExprTypeName(cbe.mod, base);
if String_EndsWith(childType, "*") {
isPtr = true;
}
if base.kind == hVar && base.typeKind == tyPointer {
isPtr = true;
}
}
let sep: String = ".";
if isPtr { sep = "->"; }
CBE_EmitExpr(cbe, base);
StringBuilder_Append(&cbe.sb, sep);
StringBuilder_Append(&cbe.sb, "data[");
CBE_EmitExpr(cbe, node.child2);
StringBuilder_Append(&cbe.sb, ", ");
CBE_EmitExpr(cbe, node.child1.child1);
StringBuilder_Append(&cbe.sb, ".len)]");
StringBuilder_Append(&cbe.sb, "]");
} else {
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, "[");
@@ -755,6 +784,11 @@ func CBE_EmitFatFuncTypedefs(cbe: *CEmitter, mod: *HirModule) {
StringBuilder_Append(&cbe.sb, " int (*code)(void* env, int a0, int a1);\n");
StringBuilder_Append(&cbe.sb, " void* env;\n");
StringBuilder_Append(&cbe.sb, "} BuxFn_int_int_int;\n");
// (int)->bool
StringBuilder_Append(&cbe.sb, "typedef struct BuxFn_bool_int {\n");
StringBuilder_Append(&cbe.sb, " bool (*code)(void* env, int a0);\n");
StringBuilder_Append(&cbe.sb, " void* env;\n");
StringBuilder_Append(&cbe.sb, "} BuxFn_bool_int;\n");
// ()->void
StringBuilder_Append(&cbe.sb, "typedef struct BuxFn_void_void {\n");
StringBuilder_Append(&cbe.sb, " void (*code)(void* env);\n");
@@ -795,6 +829,7 @@ func CBE_MaybeEmitExtraFat(cbe: *CEmitter, name: String) {
// Skip ones we already emit as built-ins
if String_Eq(name, "BuxFn_int_int") { return; }
if String_Eq(name, "BuxFn_int_int_int") { return; }
if String_Eq(name, "BuxFn_bool_int") { return; }
if String_Eq(name, "BuxFn_void_void") { return; }
if String_Eq(name, "BuxFn_int_void") { return; }
CBE_EmitOneFatTypedef(cbe, name);