From 74db8a5790f37fbde4328af354a5ac9f3891a741 Mon Sep 17 00:00:00 2001 From: dimgigov Date: Tue, 9 Jun 2026 21:53:12 +0300 Subject: [PATCH] feat: for-in range loops in selfhost + bootstrap - Parser: add parseRange for selfhost (.. / ..=) - Sema: determine loop variable type from range bounds - HIR Lower: desugar range for-in to while loop with counter - Bootstrap sema: fix range-based for variable type (was unknown) - Selfhost loop remains deterministic --- bootstrap/sema.nim | 8 ++- src/hir_lower.bux | 141 +++++++++++++++++++++++++++++++++++++++++++++ src/parser.bux | 21 ++++++- src/sema.bux | 28 ++++++++- 4 files changed, 194 insertions(+), 4 deletions(-) diff --git a/bootstrap/sema.nim b/bootstrap/sema.nim index d041a40..2b444da 100644 --- a/bootstrap/sema.nim +++ b/bootstrap/sema.nim @@ -1440,9 +1440,13 @@ proc checkStmt(sema: var Sema, stmt: Stmt, scope: Scope): Type = discard sema.checkStmt(Stmt(kind: skExpr, loc: stmt.stmtLoopBody.loc, stmtExpr: Expr(kind: ekBlock, loc: stmt.stmtLoopBody.loc, exprBlock: stmt.stmtLoopBody)), scope) return makeVoid() of skFor: - discard sema.checkExpr(stmt.stmtForIter, scope) + let iterExpr = stmt.stmtForIter + discard sema.checkExpr(iterExpr, scope) var forScope = newScope(scope) - let iterSym = Symbol(kind: skVar, name: stmt.stmtForVar, typ: makeUnknown(), isMutable: true) + var iterTyp = makeUnknown() + if iterExpr.kind == ekRange: + iterTyp = sema.checkExpr(iterExpr.exprRangeLo, scope) + let iterSym = Symbol(kind: skVar, name: stmt.stmtForVar, typ: iterTyp, isMutable: true) discard forScope.define(iterSym) discard sema.checkStmt(Stmt(kind: skExpr, loc: stmt.stmtForBody.loc, stmtExpr: Expr(kind: ekBlock, loc: stmt.stmtForBody.loc, exprBlock: stmt.stmtForBody)), forScope) return makeVoid() diff --git a/src/hir_lower.bux b/src/hir_lower.bux index 71b9ab1..24c4cd3 100644 --- a/src/hir_lower.bux +++ b/src/hir_lower.bux @@ -674,6 +674,147 @@ func Lcx_LowerStmt(ctx: *LowerCtx, stmt: *Stmt) -> *HirNode { return n; } + // For + if kind == skFor { + let iterExpr: *Expr = stmt.child1; + let varName: String = stmt.strValue; + let body: *Block = stmt.refStmtBlock; + + // Range-based for: for i in lo..hi { body } + // (selfhost parses .. as ekBinary; bootstrap parses as ekRange) + let isRangeExpr: bool = iterExpr != null as *Expr && (iterExpr.kind == ekRange || (iterExpr.kind == ekBinary && (iterExpr.intValue == tkDotDot || iterExpr.intValue == tkDotDotEqual))); + if isRangeExpr { + let lo: *HirNode = Lcx_LowerExpr(ctx, iterExpr.child1); + let hi: *HirNode = Lcx_LowerExpr(ctx, iterExpr.child2); + var inclusive: bool = iterExpr.boolValue; + if iterExpr.kind == ekBinary && iterExpr.intValue == tkDotDotEqual { + inclusive = true; + } + + let varTypeKind: int = tyInt; + let varTypeName: String = "int"; + + // alloca for loop variable + let alloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; + alloca.kind = hAlloca; + alloca.line = line; + alloca.column = col; + alloca.strValue = varName; + alloca.typeName = varTypeName; + alloca.typeKind = varTypeKind; + + // store init value + let store: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; + store.kind = hStore; + store.child1 = alloca; + store.child2 = lo; + + // var node for reading in condition + let varRead: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; + varRead.kind = hVar; + varRead.strValue = varName; + varRead.typeName = varTypeName; + varRead.typeKind = varTypeKind; + + // condition: var < hi (or <=) + let cond: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; + cond.kind = hBinary; + if inclusive { + cond.intValue = tkLe; + } else { + cond.intValue = tkLt; + } + cond.child1 = varRead; + cond.child2 = hi; + + // Build while body: original body + increment + let bodyBlock: *HirNode = Lcx_LowerBlock(ctx, body, -1); + + // increment: var = var + 1 + let varRead2: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; + varRead2.kind = hVar; + varRead2.strValue = varName; + varRead2.typeName = varTypeName; + varRead2.typeKind = varTypeKind; + + let one: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; + one.kind = hLit; + one.intValue = tkIntLiteral; + one.strValue = "1"; + one.typeKind = varTypeKind; + + let inc: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; + inc.kind = hBinary; + inc.intValue = tkPlus; + inc.child1 = varRead2; + inc.child2 = one; + + let storeInc: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; + storeInc.kind = hStore; + // Use hVar (not alloca) for assignment to existing variable + let varForInc: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; + varForInc.kind = hVar; + varForInc.strValue = varName; + varForInc.typeName = varTypeName; + varForInc.typeKind = varTypeKind; + storeInc.child1 = varForInc; + storeInc.child2 = inc; + + // Append storeInc to body block chain + if bodyBlock != null as *HirNode && bodyBlock.kind == hBlock { + if bodyBlock.child1 != null as *HirNode { + var last: *HirNode = bodyBlock.child1; + while last.child3 != null as *HirNode { + last = last.child3; + } + last.child3 = storeInc; + } else { + bodyBlock.child1 = storeInc; + } + } else if bodyBlock != null as *HirNode { + // Wrap single node into a block + let wrapBlock: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; + wrapBlock.kind = hBlock; + wrapBlock.child1 = bodyBlock; + var last: *HirNode = bodyBlock; + while last.child3 != null as *HirNode { + last = last.child3; + } + last.child3 = storeInc; + bodyBlock = wrapBlock; + } else { + let wrapBlock: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; + wrapBlock.kind = hBlock; + wrapBlock.child1 = storeInc; + bodyBlock = wrapBlock; + } + + // while node + let whileNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; + whileNode.kind = hWhile; + whileNode.child1 = cond; + whileNode.child2 = bodyBlock; + + // Chain: store (contains alloca as child1) -> while + // C backend emits hStore with hAlloca child1 as "Type x = value;" + store.child3 = whileNode; + + let blockNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; + blockNode.kind = hBlock; + blockNode.line = line; + blockNode.column = col; + blockNode.child1 = store; + return blockNode; + } + + // Collection-based for (placeholder - infinite loop for now) + n.kind = hLoop; + if stmt.refStmtBlock != null as *Block { + n.child1 = Lcx_LowerBlock(ctx, stmt.refStmtBlock, -1); + } + return n; + } + // Break if kind == skBreak { n.kind = hBreak; diff --git a/src/parser.bux b/src/parser.bux index 439e9a4..f86b7bb 100644 --- a/src/parser.bux +++ b/src/parser.bux @@ -759,12 +759,31 @@ func parserParseBinary(p: *Parser) -> *Expr { return parserParseBinaryPrec(p, 1); } +// --------------------------------------------------------------------------- +// Range: lo .. hi or lo ..= hi +// --------------------------------------------------------------------------- + +func parserParseRange(p: *Parser) -> *Expr { + var left: *Expr = parserParseBinary(p); + if parserCheck(p, tkDotDot) || parserCheck(p, tkDotDotEqual) { + let inclusive: bool = parserCheck(p, tkDotDotEqual); + let opTok: LexToken = parserAdvance(p); + let right: *Expr = parserParseBinary(p); + let e: *Expr = parserMakeExpr(ekRange, opTok.line, opTok.column); + e.child1 = left; + e.child2 = right; + e.boolValue = inclusive; + return e; + } + return left; +} + // --------------------------------------------------------------------------- // Ternary: cond ? then : else // --------------------------------------------------------------------------- func parserParseTernary(p: *Parser) -> *Expr { - var left: *Expr = parserParseBinary(p); + var left: *Expr = parserParseRange(p); if parserMatch(p, tkQuestion) { let thenExpr: *Expr = parserParseExpr(p); discard parserExpect(p, tkColon, "expected ':' in ternary"); diff --git a/src/sema.bux b/src/sema.bux index 7d3e524..996b91a 100644 --- a/src/sema.bux +++ b/src/sema.bux @@ -828,7 +828,7 @@ func Sema_CheckStmt(sema: *Sema, stmt: *Stmt) { // For if kind == skFor { - discard Sema_CheckExpr(sema, stmt.child1); + let iterType: int = Sema_CheckExpr(sema, stmt.child1); var forScope: Scope = Scope_NewChild(sema.scope); var loopSym: Symbol; loopSym.kind = skVar; @@ -839,6 +839,32 @@ func Sema_CheckStmt(sema: *Sema, stmt: *Stmt) { loopSym.isMutable = true; loopSym.isPublic = false; loopSym.decl = null as *Decl; + + // Determine loop variable type from iterator expression + if stmt.child1 != null as *Expr { + // Range-based: type from lower bound (selfhost parses .. as ekBinary) + if stmt.child1.kind == ekRange || (stmt.child1.kind == ekBinary && (stmt.child1.intValue == tkDotDot || stmt.child1.intValue == tkDotDotEqual)) { + let boundType: int = Sema_CheckExpr(sema, stmt.child1.child1); + loopSym.typeKind = boundType; + } + // Array-based: extract element type from Array annotation + if stmt.child1.kind == ekIdent { + let sym: Symbol = Scope_Lookup(sema.scope, stmt.child1.strValue); + if sym.refType != null as *TypeExpr { + if String_Eq(sym.refType.typeName, "Array") && sym.refType.typeArgCount > 0 { + let elemTe: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr; + elemTe.kind = tekNamed; + elemTe.typeName = sym.refType.typeArgName0; + elemTe.line = stmt.line; + elemTe.column = stmt.column; + loopSym.typeKind = Sema_ResolveType(sema, elemTe); + loopSym.typeName = sym.refType.typeArgName0; + loopSym.refType = elemTe; + } + } + } + } + discard Scope_Define(&forScope, loopSym); let prevScope: *Scope = sema.scope; sema.scope = &forScope;