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:
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+42
-7
@@ -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);
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user