From b177e8fbce34516d77300af31722363be894f604 Mon Sep 17 00:00:00 2001 From: dimgigov Date: Sun, 31 May 2026 02:13:30 +0300 Subject: [PATCH] feat: add range-based for loops - Parse range expressions in expressions (0..5, 0..=5) - Add ekRange AST node, hRange HIR node - Lower for-range to while loop with alloca counter - Add hBlock.isScope flag for C scope emission - Update C backend to emit scope blocks only when isScope is set --- src/c_backend.nim | 14 +++++++---- src/hir.nim | 10 ++++++-- src/hir_lower.nim | 61 +++++++++++++++++++++++++++++++++++++++++++---- src/parser.nim | 15 ++++++++++-- 4 files changed, 87 insertions(+), 13 deletions(-) diff --git a/src/c_backend.nim b/src/c_backend.nim index 0e7d92f..eb68364 100644 --- a/src/c_backend.nim +++ b/src/c_backend.nim @@ -307,11 +307,17 @@ proc emitStmt(be: var CBackend, node: HirNode) = be.emitLine("continue;") of hBlock: + if node.isScope: + be.emitLine("{") + inc be.indent for stmt in node.blockStmts: be.emitStmt(stmt) if node.blockExpr != nil: let val = be.emitExpr(node.blockExpr) be.emitLine(&"{val};") + if node.isScope: + dec be.indent + be.emitLine("}") of hAlloca: let typ = typeToC(node.allocaType) @@ -352,12 +358,12 @@ proc emitFunc*(be: var CBackend, hfunc: HirFunc) = be.emitLine(&"{retType} {hfunc.name}({paramsStr}) {{") inc be.indent if hfunc.body != nil: - if hfunc.body.kind == hBlock and hfunc.body.blockExpr != nil and hfunc.retType.kind != tkVoid: - # Function returns a value via block expression — emit statements and add return + if hfunc.body.kind == hBlock: for stmt in hfunc.body.blockStmts: be.emitStmt(stmt) - let val = be.emitExpr(hfunc.body.blockExpr) - be.emitLine(&"return {val};") + if hfunc.body.blockExpr != nil and hfunc.retType.kind != tkVoid: + let val = be.emitExpr(hfunc.body.blockExpr) + be.emitLine(&"return {val};") else: be.emitStmt(hfunc.body) dec be.indent diff --git a/src/hir.nim b/src/hir.nim index d757ccb..12cbff3 100644 --- a/src/hir.nim +++ b/src/hir.nim @@ -34,6 +34,7 @@ type hBlock hStructInit hSliceInit + hRange hTupleInit # Match (desugared to switch/branch later) hMatch @@ -106,11 +107,16 @@ type of hBlock: blockStmts*: seq[HirNode] blockExpr*: HirNode + isScope*: bool of hStructInit: structInitName*: string structInitFields*: seq[tuple[name: string, value: HirNode]] of hSliceInit: sliceInitElements*: seq[HirNode] + of hRange: + rangeLo*: HirNode + rangeHi*: HirNode + rangeInclusive*: bool of hTupleInit: tupleInitElements*: seq[HirNode] of hMatch: @@ -162,8 +168,8 @@ proc hirCall*(callee: string, args: seq[HirNode], typ: Type, loc: SourceLocation proc hirReturn*(value: HirNode, loc: SourceLocation): HirNode = HirNode(kind: hReturn, returnValue: value, typ: makeVoid(), loc: loc) -proc hirBlock*(stmts: seq[HirNode], expr: HirNode, typ: Type, loc: SourceLocation): HirNode = - HirNode(kind: hBlock, blockStmts: stmts, blockExpr: expr, typ: typ, loc: loc) +proc hirBlock*(stmts: seq[HirNode], expr: HirNode, typ: Type, loc: SourceLocation, isScope: bool = false): HirNode = + HirNode(kind: hBlock, blockStmts: stmts, blockExpr: expr, typ: typ, loc: loc, isScope: isScope) proc hirAlloca*(name: string, typ: Type, loc: SourceLocation): HirNode = HirNode(kind: hAlloca, allocaType: typ, allocaName: name, typ: makePointer(typ), loc: loc) diff --git a/src/hir_lower.nim b/src/hir_lower.nim index 2807f27..7beebdd 100644 --- a/src/hir_lower.nim +++ b/src/hir_lower.nim @@ -229,6 +229,9 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type = if expr.exprSliceElements.len > 0: return makeSlice(ctx.resolveExprType(expr.exprSliceElements[0])) return makeSlice(makeUnknown()) + of ekRange: + let loType = ctx.resolveExprType(expr.exprRangeLo) + return makeRange(loType) of ekTuple: var elems: seq[Type] = @[] for e in expr.exprTupleElements: @@ -380,6 +383,12 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode = elems.add(ctx.lowerExpr(e)) return HirNode(kind: hSliceInit, sliceInitElements: elems, typ: typ, loc: loc) + of ekRange: + let lo = ctx.lowerExpr(expr.exprRangeLo) + let hi = ctx.lowerExpr(expr.exprRangeHi) + return HirNode(kind: hRange, rangeLo: lo, rangeHi: hi, + rangeInclusive: expr.exprRangeInclusive, typ: typ, loc: loc) + of ekTuple: var elems: seq[HirNode] = @[] for e in expr.exprTupleElements: @@ -509,11 +518,53 @@ proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode = typ: makeVoid(), loc: loc) of skFor: - # Desugar: for i in iter { body } → { let __iter = iter; while __hasNext(__iter) { let i = __next(__iter); body } } - let iterExpr = ctx.lowerExpr(stmt.stmtForIter) - let body = ctx.lowerBlock(stmt.stmtForBody) - # Simplified: just lower the body for now - return HirNode(kind: hLoop, loopBody: body, typ: makeVoid(), loc: loc) + let iterExpr = stmt.stmtForIter + let body = stmt.stmtForBody + let varName = stmt.stmtForVar + let loc = stmt.loc + + # Range-based for: for i in lo..hi { body } + if iterExpr.kind == ekRange: + let lo = ctx.lowerExpr(iterExpr.exprRangeLo) + let hi = ctx.lowerExpr(iterExpr.exprRangeHi) + let inclusive = iterExpr.exprRangeInclusive + + # Determine loop variable type from range bounds + let varType = ctx.resolveExprType(iterExpr.exprRangeLo) + + # Create: var i = lo; while i < hi { body; i = i + 1; } + let initStmt = hirAlloca(varName, varType, loc) + let varNode = hirVar(varName, makePointer(varType), loc) + let initStore = hirStore(varNode, lo, loc) + + let readI = hirVar(varName, varType, loc) + let condOp = if inclusive: tkLe else: tkLt + let cond = HirNode(kind: hBinary, binaryOp: condOp, + binaryLeft: readI, binaryRight: hi, + typ: makeBool(), loc: loc) + + var bodyStmts: seq[HirNode] = @[] + bodyStmts.add(ctx.lowerBlock(body)) + + let readI2 = hirVar(varName, varType, loc) + let one = hirLit(Token(kind: tkIntLiteral, text: "1", loc: loc), varType, loc) + let inc = HirNode(kind: hBinary, binaryOp: tkPlus, + binaryLeft: readI2, binaryRight: one, + typ: varType, loc: loc) + bodyStmts.add(hirStore(varNode, inc, loc)) + + let whileBody = hirBlock(bodyStmts, nil, makeVoid(), loc) + let whileNode = HirNode(kind: hWhile, whileCond: cond, whileBody: whileBody, + typ: makeVoid(), loc: loc) + + # Wrap in a block so loop variable doesn't leak into outer scope + let forBlock = hirBlock(@[initStmt, initStore, whileNode], nil, makeVoid(), loc, isScope = true) + return forBlock + + # Generic iterator for loop (simplified - just infinite loop for now) + let loweredIter = ctx.lowerExpr(iterExpr) + let loweredBody = ctx.lowerBlock(body) + return HirNode(kind: hLoop, loopBody: loweredBody, typ: makeVoid(), loc: loc) of skDoWhile: let body = ctx.lowerBlock(stmt.stmtDoWhileBody) diff --git a/src/parser.nim b/src/parser.nim index 730e575..711c93f 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -326,6 +326,7 @@ proc parseAdd(p: var Parser): Expr proc parseShift(p: var Parser): Expr proc parseCast(p: var Parser): Expr proc parseComparison(p: var Parser): Expr +proc parseRange(p: var Parser): Expr proc parseEquality(p: var Parser): Expr proc parseBitAnd(p: var Parser): Expr proc parseBitXor(p: var Parser): Expr @@ -583,12 +584,22 @@ proc parseComparison(p: var Parser): Expr = left = newBinaryExpr(op, left, right, loc) return left -proc parseEquality(p: var Parser): Expr = +proc parseRange(p: var Parser): Expr = let loc = p.currentLoc var left = p.parseComparison() + if p.check(tkDotDot) or p.check(tkDotDotEqual): + let inclusive = p.check(tkDotDotEqual) + discard p.advance() + let right = p.parseComparison() + return Expr(kind: ekRange, loc: loc, exprRangeLo: left, exprRangeHi: right, exprRangeInclusive: inclusive) + return left + +proc parseEquality(p: var Parser): Expr = + let loc = p.currentLoc + var left = p.parseRange() while p.checkAny([tkEq, tkNe]): let op = p.advance().kind - let right = p.parseComparison() + let right = p.parseRange() left = newBinaryExpr(op, left, right, loc) return left