diff --git a/Makefile b/Makefile index 6f287e8..9d2137a 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/docs/QUALITY_PLAN.md b/docs/QUALITY_PLAN.md index 7f7465c..599a105 100644 --- a/docs/QUALITY_PLAN.md +++ b/docs/QUALITY_PLAN.md @@ -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) diff --git a/docs/Stdlib.md b/docs/Stdlib.md index 323f9d5..0544c4b 100644 --- a/docs/Stdlib.md +++ b/docs/Stdlib.md @@ -143,6 +143,20 @@ struct Iter { | `Iter_AllEq` | `func Iter_AllEq(it: *Iter, value: T) -> bool` | True if all remaining equal value | | `Iter_Collect` | `func Iter_Collect(it: *Iter) -> Array` | 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, f: func(int) -> int) -> Array` | Map each element | +| `Iter_FilterInt` | `func Iter_FilterInt(it: *Iter, pred: func(int) -> bool) -> Array` | Keep matching elements | +| `Iter_FoldInt` | `func Iter_FoldInt(it: *Iter, init: int, f: func(int, int) -> int) -> int` | Left fold | +| `Iter_ForEachInt` | `func Iter_ForEachInt(it: *Iter, f: func(int) -> int)` | Side-effect per element | +| `Iter_AnyInt` | `func Iter_AnyInt(it: *Iter, pred: func(int) -> bool) -> bool` | Any matches pred | +| `Iter_AllInt` | `func Iter_AllInt(it: *Iter, pred: func(int) -> bool) -> bool` | All match pred | +| `Iter_SumInt` | `func Iter_SumInt(it: *Iter) -> int` | Sum remaining ints | + ### Example ```bux import Std::Array::*; diff --git a/examples/iter_hof.bux b/examples/iter_hof.bux new file mode 100644 index 0000000..bb1bf37 --- /dev/null +++ b/examples/iter_hof.bux @@ -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 = Array_New(8); + Array_Push(&nums, 1); + Array_Push(&nums, 2); + Array_Push(&nums, 3); + Array_Push(&nums, 4); + Array_Push(&nums, 5); + + // Map with named function + let it1: Iter = Array_Iter(&nums); + var doubled: Array = Iter_MapInt(&it1, Double); + Test_AssertEqInt(Array_Len(&doubled) as int, 5); + Test_AssertEqInt(Array_Get(&doubled, 0), 2); + Test_AssertEqInt(Array_Get(&doubled, 4), 10); + + // Filter with named predicate + let it2: Iter = Array_Iter(&nums); + var evens: Array = Iter_FilterInt(&it2, IsEven); + Test_AssertEqInt(Array_Len(&evens) as int, 2); + Test_AssertEqInt(Array_Get(&evens, 0), 2); + Test_AssertEqInt(Array_Get(&evens, 1), 4); + + // Fold with named combiner + let it3: Iter = Array_Iter(&nums); + let sum: int = Iter_FoldInt(&it3, 0, Add); + Test_AssertEqInt(sum, 15); + let itSum: Iter = Array_Iter(&nums); + Test_AssertEqInt(Iter_SumInt(&itSum), 15); + + // Map with capturing closure + let scale: int = 10; + let it4: Iter = Array_Iter(&nums); + var scaled: Array = Iter_MapInt(&it4, |x: int| -> int { + return x * scale; + }); + Test_AssertEqInt(Array_Get(&scaled, 0), 10); + Test_AssertEqInt(Array_Get(&scaled, 2), 30); + + // Filter with closure + let minVal: int = 3; + let it5: Iter = Array_Iter(&nums); + var big: Array = Iter_FilterInt(&it5, |x: int| -> bool { + return x >= minVal; + }); + Test_AssertEqInt(Array_Len(&big) as int, 3); + Test_AssertEqInt(Array_Get(&big, 0), 3); + + // Fold with closure + let it6: Iter = Array_Iter(&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 = Array_Iter(&nums); + Test_AssertTrue(Iter_AnyInt(&itAny, IsEven)); + let itAll: Iter = Array_Iter(&nums); + Test_AssertFalse(Iter_AllInt(&itAll, IsEven)); + let itEv: Iter = Array_Iter(&evens); + Test_AssertTrue(Iter_AllInt(&itEv, IsEven)); + + // ForEach (print via side-effect free assert path — just call Double) + let it7: Iter = Array_Iter(&nums); + Iter_ForEachInt(&it7, Double); + + PrintInt(sum); + PrintLine(""); + PrintInt(prod); + PrintLine(""); + Test_Pass("iter_hof"); + + Array_Free(&nums); + Array_Free(&doubled); + Array_Free(&evens); + Array_Free(&scaled); + Array_Free(&big); + return 0; +} diff --git a/lib/Iter.bux b/lib/Iter.bux index 8247c0f..0ac6f46 100644 --- a/lib/Iter.bux +++ b/lib/Iter.bux @@ -107,4 +107,99 @@ func Iter_Collect(it: *Iter) -> Array { 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, f: func(int) -> int) -> Array { + let remaining: uint = it.len - it.pos; + var cap: uint = remaining; + if cap == 0 { + cap = 1; + } + var out: Array = Array_New(cap); + var i: uint = it.pos; + while i < it.len { + let mapped: int = f(it.data[i]); + Array_Push(&out, mapped); + i = i + 1; + } + return out; +} + +/* Keep remaining ints for which pred returns true */ +func Iter_FilterInt(it: *Iter, pred: func(int) -> bool) -> Array { + let remaining: uint = it.len - it.pos; + var cap: uint = remaining; + if cap == 0 { + cap = 1; + } + var out: Array = Array_New(cap); + var i: uint = it.pos; + while i < it.len { + let v: int = it.data[i]; + if pred(v) { + Array_Push(&out, v); + } + i = i + 1; + } + return out; +} + +/* Left-fold remaining ints: f(f(...f(init, x0), x1), ...) */ +func Iter_FoldInt(it: *Iter, 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, 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, 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, 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 { + var total: int = 0; + var i: uint = it.pos; + while i < it.len { + total = total + it.data[i]; + i = i + 1; + } + return total; +} + } diff --git a/src/c_backend.bux b/src/c_backend.bux index ba151f5..466f137 100644 --- a/src/c_backend.bux +++ b/src/c_backend.bux @@ -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 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);