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
This commit is contained in:
+6
-2
@@ -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)
|
discard sema.checkStmt(Stmt(kind: skExpr, loc: stmt.stmtLoopBody.loc, stmtExpr: Expr(kind: ekBlock, loc: stmt.stmtLoopBody.loc, exprBlock: stmt.stmtLoopBody)), scope)
|
||||||
return makeVoid()
|
return makeVoid()
|
||||||
of skFor:
|
of skFor:
|
||||||
discard sema.checkExpr(stmt.stmtForIter, scope)
|
let iterExpr = stmt.stmtForIter
|
||||||
|
discard sema.checkExpr(iterExpr, scope)
|
||||||
var forScope = newScope(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 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)
|
discard sema.checkStmt(Stmt(kind: skExpr, loc: stmt.stmtForBody.loc, stmtExpr: Expr(kind: ekBlock, loc: stmt.stmtForBody.loc, exprBlock: stmt.stmtForBody)), forScope)
|
||||||
return makeVoid()
|
return makeVoid()
|
||||||
|
|||||||
@@ -674,6 +674,147 @@ func Lcx_LowerStmt(ctx: *LowerCtx, stmt: *Stmt) -> *HirNode {
|
|||||||
return n;
|
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
|
// Break
|
||||||
if kind == skBreak {
|
if kind == skBreak {
|
||||||
n.kind = hBreak;
|
n.kind = hBreak;
|
||||||
|
|||||||
+20
-1
@@ -759,12 +759,31 @@ func parserParseBinary(p: *Parser) -> *Expr {
|
|||||||
return parserParseBinaryPrec(p, 1);
|
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
|
// Ternary: cond ? then : else
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
func parserParseTernary(p: *Parser) -> *Expr {
|
func parserParseTernary(p: *Parser) -> *Expr {
|
||||||
var left: *Expr = parserParseBinary(p);
|
var left: *Expr = parserParseRange(p);
|
||||||
if parserMatch(p, tkQuestion) {
|
if parserMatch(p, tkQuestion) {
|
||||||
let thenExpr: *Expr = parserParseExpr(p);
|
let thenExpr: *Expr = parserParseExpr(p);
|
||||||
discard parserExpect(p, tkColon, "expected ':' in ternary");
|
discard parserExpect(p, tkColon, "expected ':' in ternary");
|
||||||
|
|||||||
+27
-1
@@ -828,7 +828,7 @@ func Sema_CheckStmt(sema: *Sema, stmt: *Stmt) {
|
|||||||
|
|
||||||
// For
|
// For
|
||||||
if kind == skFor {
|
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 forScope: Scope = Scope_NewChild(sema.scope);
|
||||||
var loopSym: Symbol;
|
var loopSym: Symbol;
|
||||||
loopSym.kind = skVar;
|
loopSym.kind = skVar;
|
||||||
@@ -839,6 +839,32 @@ func Sema_CheckStmt(sema: *Sema, stmt: *Stmt) {
|
|||||||
loopSym.isMutable = true;
|
loopSym.isMutable = true;
|
||||||
loopSym.isPublic = false;
|
loopSym.isPublic = false;
|
||||||
loopSym.decl = null as *Decl;
|
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<T> 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);
|
discard Scope_Define(&forScope, loopSym);
|
||||||
let prevScope: *Scope = sema.scope;
|
let prevScope: *Scope = sema.scope;
|
||||||
sema.scope = &forScope;
|
sema.scope = &forScope;
|
||||||
|
|||||||
Reference in New Issue
Block a user