diff --git a/Makefile b/Makefile index 955bb70..4bf4d62 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ SRC := compiler/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 +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 .PHONY: all build dev test clean test-examples diff --git a/_test_array/bux.toml b/_test_array/bux.toml new file mode 100644 index 0000000..ca1e559 --- /dev/null +++ b/_test_array/bux.toml @@ -0,0 +1,2 @@ +name = "test_array" +version = "0.1.0" diff --git a/_test_array/src/Main.bux b/_test_array/src/Main.bux new file mode 100644 index 0000000..ab581e0 --- /dev/null +++ b/_test_array/src/Main.bux @@ -0,0 +1,33 @@ +module Main { +import Std::Array::Array; +import Std::Iter::Iter; +import Std::Test::Test; +import Std::Io::Io; + +func Main() -> int { + var arr: Array = Array_New(4); + Array_Push(&arr, 10); + Array_Push(&arr, 20); + Array_Push(&arr, 30); + + Test_AssertEqInt(Array_Len(&arr) as int, 3); + Test_AssertEqInt(Array_Get(&arr, 0), 10); + Test_AssertEqInt(Array_Get(&arr, 1), 20); + Test_AssertEqInt(Array_Get(&arr, 2), 30); + + var it: Iter = Array_Iter(&arr); + Test_AssertTrue(Iter_HasNext(&it)); + Test_AssertEqInt(Iter_Next(&it), 10); + Test_AssertEqInt(Iter_Next(&it), 20); + Test_AssertEqInt(Iter_Next(&it), 30); + Test_AssertFalse(Iter_HasNext(&it)); + + var it2: Iter = Array_Iter(&arr); + let count: uint = Iter_Count(&it2); + Test_AssertEqInt(count as int, 3); + + Print("All array+iter tests passed!"); + Array_Free(&arr); + return 0; +} +} diff --git a/compiler/bootstrap/hir_lower.nim b/compiler/bootstrap/hir_lower.nim index 7efd4a9..7f0df9a 100644 --- a/compiler/bootstrap/hir_lower.nim +++ b/compiler/bootstrap/hir_lower.nim @@ -152,16 +152,26 @@ proc substituteType(ctx: var LowerCtx, te: TypeExpr, subst: Table[string, Type]) let mangledName = te.typeName & "_" & suffix if not ctx.generatedStructInsts.hasKey(mangledName): let genericDecl = ctx.genericStructs[te.typeName] - var fields: seq[tuple[name: string, typ: Type]] = @[] - var concreteArgs: seq[Type] = @[] - for f in genericDecl.declStructFields: - let resolvedType = substituteType(ctx, f.ftype, subst) - fields.add((f.name, resolvedType)) + # Skip if any type arg is still an unresolved type parameter + var hasUnresolved = false for arg in te.typeArgs: - concreteArgs.add(substituteType(ctx, arg, subst)) - ctx.extraStructs.add((mangledName, fields)) - ctx.generatedStructInsts[mangledName] = true - ctx.structInstMap[mangledName] = (te.typeName, concreteArgs) + let argType = substituteType(ctx, arg, subst) + for tp in genericDecl.declStructTypeParams: + if argType.kind == tkNamed and argType.name == tp.name: + hasUnresolved = true + break + if hasUnresolved: break + if not hasUnresolved: + var fields: seq[tuple[name: string, typ: Type]] = @[] + var concreteArgs: seq[Type] = @[] + for f in genericDecl.declStructFields: + let resolvedType = substituteType(ctx, f.ftype, subst) + fields.add((f.name, resolvedType)) + for arg in te.typeArgs: + concreteArgs.add(substituteType(ctx, arg, subst)) + ctx.extraStructs.add((mangledName, fields)) + ctx.generatedStructInsts[mangledName] = true + ctx.structInstMap[mangledName] = (te.typeName, concreteArgs) return makeNamed(mangledName) return ctx.resolveTypeExpr(te) of tekOwn: @@ -197,20 +207,30 @@ proc resolveTypeExpr(ctx: var LowerCtx, te: TypeExpr): Type = let mangledName = te.typeName & "_" & suffix if not ctx.generatedStructInsts.hasKey(mangledName): let genericDecl = ctx.genericStructs[te.typeName] - var fields: seq[tuple[name: string, typ: Type]] = @[] - var subst = initTable[string, Type]() - var concreteArgs: seq[Type] = @[] - for j, tp in genericDecl.declStructTypeParams: - if j < te.typeArgs.len: - subst[tp.name] = ctx.resolveTypeExpr(te.typeArgs[j]) + # Skip if any type arg is still an unresolved type parameter + var hasUnresolved = false for arg in te.typeArgs: - concreteArgs.add(ctx.resolveTypeExpr(arg)) - for f in genericDecl.declStructFields: - let resolvedType = substituteType(ctx, f.ftype, subst) - fields.add((f.name, resolvedType)) - ctx.extraStructs.add((mangledName, fields)) - ctx.generatedStructInsts[mangledName] = true - ctx.structInstMap[mangledName] = (te.typeName, concreteArgs) + let argType = ctx.resolveTypeExpr(arg) + for tp in genericDecl.declStructTypeParams: + if argType.kind == tkNamed and argType.name == tp.name: + hasUnresolved = true + break + if hasUnresolved: break + if not hasUnresolved: + var fields: seq[tuple[name: string, typ: Type]] = @[] + var subst = initTable[string, Type]() + var concreteArgs: seq[Type] = @[] + for j, tp in genericDecl.declStructTypeParams: + if j < te.typeArgs.len: + subst[tp.name] = ctx.resolveTypeExpr(te.typeArgs[j]) + for arg in te.typeArgs: + concreteArgs.add(ctx.resolveTypeExpr(arg)) + for f in genericDecl.declStructFields: + let resolvedType = substituteType(ctx, f.ftype, subst) + fields.add((f.name, resolvedType)) + ctx.extraStructs.add((mangledName, fields)) + ctx.generatedStructInsts[mangledName] = true + ctx.structInstMap[mangledName] = (te.typeName, concreteArgs) return makeNamed(mangledName) case te.typeName of "void": return makeVoid() @@ -539,15 +559,7 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode = # Generic function call: Max(10, 20) → Max_int(10, 20) if expr.exprCallCallee.kind == ekGenericCall: let baseName = expr.exprCallCallee.exprGenericCallee - var typeSuffix = "" - for i, targ in expr.exprCallCallee.exprGenericTypeArgs: - if i > 0: - typeSuffix.add("_") - if targ.kind == tekNamed: - typeSuffix.add(targ.typeName) - else: - typeSuffix.add("unknown") - let mangledName = baseName & "_" & typeSuffix + let mangledName = ctx.generateMethodInstance(baseName, expr.exprCallCallee.exprGenericTypeArgs) let args = ctx.lowerCallArgs(expr.exprCallCallee, expr.exprCallArgs) return hirCall(mangledName, args, typ, loc) @@ -563,32 +575,7 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode = calleeName = expr.exprCallCallee.exprPath.join("_") else: discard if calleeName != "": - var typeSuffix = "" - var typeArgIdx = 0 - if ctx.genericFuncs.hasKey(calleeName): - let genericDecl = ctx.genericFuncs[calleeName] - for j, tp in genericDecl.declFuncTypeParams: - if tp.isLifetime: continue - if typeArgIdx > 0: - typeSuffix.add("_") - if j < expr.exprCallInferredTypeArgs.len: - let targ = expr.exprCallInferredTypeArgs[j] - if targ.kind == tekNamed: - typeSuffix.add(targ.typeName) - else: - typeSuffix.add("unknown") - else: - typeSuffix.add("unknown") - inc(typeArgIdx) - else: - for i, targ in expr.exprCallInferredTypeArgs: - if i > 0: - typeSuffix.add("_") - if targ.kind == tekNamed: - typeSuffix.add(targ.typeName) - else: - typeSuffix.add("unknown") - let mangledName = calleeName & "_" & typeSuffix + let mangledName = ctx.generateMethodInstance(calleeName, expr.exprCallInferredTypeArgs) let args = ctx.lowerCallArgs(expr.exprCallCallee, expr.exprCallArgs) return hirCall(mangledName, args, typ, loc) @@ -1184,131 +1171,7 @@ proc lowerModule*(module: Module, sema: Sema): HirModule = let mangledName = typeName & "_" & methodDecl.declFuncName ctx.genericFuncs[mangledName] = methodDecl - # Second pass: find all generic calls and monomorphize - proc findGenericCalls(expr: Expr): seq[tuple[name: string, typeArgs: seq[TypeExpr]]] = - if expr == nil: return @[] - result = @[] - case expr.kind - of ekCall: - if expr.exprCallCallee.kind == ekGenericCall: - result.add((expr.exprCallCallee.exprGenericCallee, expr.exprCallCallee.exprGenericTypeArgs)) - elif expr.exprCallInferredTypeArgs.len > 0: - var calleeName = "" - case expr.exprCallCallee.kind - of ekIdent: calleeName = expr.exprCallCallee.exprIdent - of ekPath: calleeName = expr.exprCallCallee.exprPath.join("::") - else: discard - if calleeName != "": - result.add((calleeName, expr.exprCallInferredTypeArgs)) - result.add(findGenericCalls(expr.exprCallCallee)) - for arg in expr.exprCallArgs: - result.add(findGenericCalls(arg)) - of ekGenericCall: - result.add((expr.exprGenericCallee, expr.exprGenericTypeArgs)) - of ekBinary: - result.add(findGenericCalls(expr.exprBinaryLeft)) - result.add(findGenericCalls(expr.exprBinaryRight)) - of ekUnary: - result.add(findGenericCalls(expr.exprUnaryOperand)) - of ekTry: - result.add(findGenericCalls(expr.exprTryOperand)) - of ekUnwrap: - result.add(findGenericCalls(expr.exprUnwrapOperand)) - of ekAssign: - result.add(findGenericCalls(expr.exprAssignTarget)) - result.add(findGenericCalls(expr.exprAssignValue)) - of ekBlock: - if expr.exprBlock != nil: - for stmt in expr.exprBlock.stmts: - case stmt.kind - of skLet: result.add(findGenericCalls(stmt.stmtLetInit)) - of skReturn: result.add(findGenericCalls(stmt.stmtReturnValue)) - of skExpr: result.add(findGenericCalls(stmt.stmtExpr)) - of skIf: - result.add(findGenericCalls(stmt.stmtIfCond)) - of skWhile: - result.add(findGenericCalls(stmt.stmtWhileCond)) - else: discard - else: discard - - # Collect all generic instantiations - var instantiations: seq[tuple[name: string, typeArgs: seq[TypeExpr]]] = @[] - for decl in module.items: - if decl.kind == dkFunc and decl.declFuncBody != nil: - for stmt in decl.declFuncBody.stmts: - case stmt.kind - of skLet: - instantiations.add(findGenericCalls(stmt.stmtLetInit)) - of skReturn: - instantiations.add(findGenericCalls(stmt.stmtReturnValue)) - of skExpr: - instantiations.add(findGenericCalls(stmt.stmtExpr)) - of skIf: - instantiations.add(findGenericCalls(stmt.stmtIfCond)) - of skWhile: - instantiations.add(findGenericCalls(stmt.stmtWhileCond)) - else: discard - - # Generate monomorphized functions - var generated = initTable[string, bool]() - for inst in instantiations: - let baseName = inst.name - if ctx.genericFuncs.hasKey(baseName): - let genericDecl = ctx.genericFuncs[baseName] - var typeSuffix = "" - var nonLifetimeIdx = 0 - for j, tp in genericDecl.declFuncTypeParams: - if tp.isLifetime: continue - if nonLifetimeIdx > 0: typeSuffix.add("_") - if j < inst.typeArgs.len: - let targ = inst.typeArgs[j] - if targ.kind == tekNamed: - typeSuffix.add(targ.typeName) - else: - typeSuffix.add("unknown") - else: - typeSuffix.add("unknown") - inc(nonLifetimeIdx) - let mangledName = baseName & "_" & typeSuffix - if not generated.hasKey(mangledName): - # Generate specialized version - - # Build type substitution table - var subst = initTable[string, Type]() - for j, tp in genericDecl.declFuncTypeParams: - if tp.isLifetime: continue - if j < inst.typeArgs.len: - let targ = inst.typeArgs[j] - if targ.kind == tekNamed: - case targ.typeName - of "int", "int32": subst[tp.name] = makeInt() - of "int64": subst[tp.name] = makeInt64() - of "float64": subst[tp.name] = makeFloat64() - of "float32": subst[tp.name] = makeFloat32() - of "bool": subst[tp.name] = makeBool() - else: subst[tp.name] = makeNamed(targ.typeName) - - # Create specialized declaration - var specDecl = Decl( - kind: dkFunc, - loc: genericDecl.loc, - isPublic: genericDecl.isPublic, - declFuncAsm: genericDecl.declFuncAsm, - declFuncCallConv: genericDecl.declFuncCallConv, - declFuncName: mangledName, - declFuncTypeParams: @[], - declFuncParams: genericDecl.declFuncParams, - declFuncReturnType: genericDecl.declFuncReturnType, - declFuncBody: genericDecl.declFuncBody - ) - - # Set substitution and lower - ctx.typeSubst = subst - funcs.add(ctx.lowerFunc(specDecl)) - ctx.typeSubst = initTable[string, Type]() # Clear substitution - generated[mangledName] = true - - # Third pass: lower all non-generic functions + # Second pass: lower all non-generic functions for decl in module.items: case decl.kind of dkFunc: diff --git a/docs/Stdlib.md b/docs/Stdlib.md index 9407f69..bc1e6d8 100644 --- a/docs/Stdlib.md +++ b/docs/Stdlib.md @@ -101,6 +101,59 @@ func Main() -> int { --- +## Std::Iter + +Lightweight iterator over `Array` (index-based, no allocation). + +### Types +```bux +struct Iter { + data: *T, + len: uint, + pos: uint, +} +``` + +### Functions + +| Function | Signature | Description | +|----------|-----------|-------------| +| `Array_Iter` | `func Array_Iter(arr: *Array) -> Iter` | Create iterator from array | +| `Iter_HasNext` | `func Iter_HasNext(it: *Iter) -> bool` | Check if more elements remain | +| `Iter_Next` | `func Iter_Next(it: *Iter) -> T` | Get next element and advance | +| `Iter_Peek` | `func Iter_Peek(it: *Iter) -> T` | Peek current element without advancing | +| `Iter_Reset` | `func Iter_Reset(it: *Iter)` | Reset to start | +| `Iter_Pos` | `func Iter_Pos(it: *Iter) -> uint` | Current position | +| `Iter_Len` | `func Iter_Len(it: *Iter) -> uint` | Total length | +| `Iter_Count` | `func Iter_Count(it: *Iter) -> uint` | Count remaining elements | +| `Iter_Skip` | `func Iter_Skip(it: *Iter, n: uint)` | Skip N elements | +| `Iter_Take` | `func Iter_Take(it: *Iter, n: uint) -> Iter` | Take first N elements as new iterator | + +### Example +```bux +import Std::Array::*; +import Std::Iter::*; +import Std::Io::{PrintInt, PrintLine}; + +func Main() -> int { + let arr: Array = Array_New(4); + Array_Push(&arr, 10); + Array_Push(&arr, 20); + Array_Push(&arr, 30); + + let it: Iter = Array_Iter(&arr); + while Iter_HasNext(&it) { + PrintInt(Iter_Next(&it)); + PrintLine(""); + } + + Array_Free(&arr); + return 0; +} +``` + +--- + ## Std::String String manipulation utilities. @@ -848,7 +901,7 @@ func Main() -> int { - `Std::Time` — `NowMs`, `NowUs`, `SleepMs` ✅ - `Std::Process` — `Run`, `Output` ✅ - `Std::Fmt` — String formatting with interpolation ✅ -- `Std::Iter` — Iterator trait and combinators ⏳ +- `Std::Iter` — Iterator over `Array` ✅ - `Std::Task` / `Std::Channel` — Lightweight concurrency (pthread-based threads) ✅ - `Std::Net` — TCP sockets ✅ - `Std::Json` — JSON parser/serializer ✅ diff --git a/examples/iter.bux b/examples/iter.bux new file mode 100644 index 0000000..0ef4326 --- /dev/null +++ b/examples/iter.bux @@ -0,0 +1,19 @@ +import Std::Io::{PrintLine, PrintInt}; +import Std::Array::*; +import Std::Iter::*; + +func Main() -> int { + let arr: Array = Array_New(8); + Array_Push(&arr, 10); + Array_Push(&arr, 20); + Array_Push(&arr, 30); + + let it: Iter = Array_Iter(&arr); + while Iter_HasNext(&it) { + PrintInt(Iter_Next(&it)); + PrintLine(""); + } + + Array_Free(&arr); + return 0; +} diff --git a/library/std/Iter.bux b/library/std/Iter.bux new file mode 100644 index 0000000..8973cd9 --- /dev/null +++ b/library/std/Iter.bux @@ -0,0 +1,70 @@ +module Std::Iter { + +import Std::Array::*; + +struct Iter { + data: *T, + len: uint, + pos: uint, +} + +/* Create an iterator from an Array */ +func Array_Iter(arr: *Array) -> Iter { + return Iter { data: arr.data, len: arr.len, pos: 0 }; +} + +/* Check if there are more elements */ +func Iter_HasNext(it: *Iter) -> bool { + return it.pos < it.len; +} + +/* Get the next element and advance (undefined if HasNext is false) */ +func Iter_Next(it: *Iter) -> T { + let val: T = it.data[it.pos]; + it.pos = it.pos + 1; + return val; +} + +/* Peek current element without advancing (undefined if HasNext is false) */ +func Iter_Peek(it: *Iter) -> T { + return it.data[it.pos]; +} + +/* Reset iterator to the beginning */ +func Iter_Reset(it: *Iter) { + it.pos = 0; +} + +/* Current position */ +func Iter_Pos(it: *Iter) -> uint { + return it.pos; +} + +/* Remaining length */ +func Iter_Len(it: *Iter) -> uint { + return it.len; +} + +/* Count remaining elements */ +func Iter_Count(it: *Iter) -> uint { + return it.len - it.pos; +} + +/* Skip N elements */ +func Iter_Skip(it: *Iter, n: uint) { + it.pos = it.pos + n; + if it.pos > it.len { + it.pos = it.len; + } +} + +/* Take first N elements (by limiting len) */ +func Iter_Take(it: *Iter, n: uint) -> Iter { + var endPos: uint = it.pos + n; + if endPos > it.len { + endPos = it.len; + } + return Iter { data: it.data, len: endPos, pos: it.pos }; +} + +}