add while stmt
All checks were successful
Check / Explore-Gitea-Actions (push) Successful in 17s

This commit is contained in:
sfja 2026-03-11 16:21:05 +01:00
parent 54ee879b45
commit d2a49f3be0
6 changed files with 139 additions and 8 deletions

View File

@ -84,6 +84,10 @@ export class Node {
return visit(k.param, k.expr);
case "IfStmt":
return visit(k.cond, k.truthy, k.falsy);
case "WhileStmt":
return visit(k.cond, k.body);
case "BreakStmt":
return visit();
case "Param":
return visit(k.ty);
case "IdentExpr":
@ -132,6 +136,8 @@ export type NodeKind =
| { tag: "ReturnStmt"; expr: Node | null }
| { tag: "LetStmt"; param: Node; expr: Node }
| { tag: "IfStmt"; cond: Node; truthy: Node; falsy: Node | null }
| { tag: "WhileStmt"; cond: Node; body: Node }
| { tag: "BreakStmt" }
| { tag: "Param"; ident: string; ty: Node | null }
| { tag: "IdentExpr"; ident: string }
| { tag: "IntExpr"; value: number }

View File

@ -51,6 +51,10 @@ export class Parser {
return this.parseLetStmt();
} else if (this.test("if")) {
return this.parseIfStmt();
} else if (this.test("while")) {
return this.parseWhileStmt();
} else if (this.test("break")) {
return this.parseBreakStmt();
} else {
const place = this.parseExpr();
if (this.eat("=")) {
@ -120,6 +124,21 @@ export class Parser {
return ast.Node.create(loc, "IfStmt", { cond, truthy, falsy });
}
parseWhileStmt(): ast.Node {
const loc = this.loc();
this.step();
const cond = this.parseExpr();
const body = this.parseBlock();
return ast.Node.create(loc, "WhileStmt", { cond, body });
}
parseBreakStmt(): ast.Node {
const loc = this.loc();
this.step();
this.mustEat(";");
return ast.Node.create(loc, "BreakStmt", {});
}
parseParam(): ast.Node {
const loc = this.loc();
const ident = this.mustEat("ident").value;
@ -322,22 +341,24 @@ export class Parser {
private mustEat(type: string, loc = this.loc()): Tok {
const tok = this.current;
if (tok.type !== type) {
this.error(
this.reporter.error(
loc,
`expected '${type}', got '${
this.done ? "eof" : this.current.type
}'`,
loc,
);
if (type === ";" && this.idx > 0) {
this.reporter.info(
this.toks[this.idx - 1].loc,
`try adding '${type}' here`,
);
}
this.reporter.abort();
}
this.step();
return tok;
}
private error(message: string, loc: Loc): never {
this.reporter.error(loc, message);
this.reporter.abort();
}
private eat(type: string): boolean {
if (this.test(type)) {
this.step();

View File

@ -30,7 +30,8 @@ export type Sym =
tag: "Let";
stmt: ast.NodeWithKind<"LetStmt">;
param: ast.NodeWithKind<"Param">;
};
}
| { tag: "Loop"; stmt: ast.Node };
export function resolve(
file: ast.Node,
@ -39,6 +40,8 @@ export function resolve(
let syms = ResolverSyms.root();
const resols = new Map<number, Sym>();
const loopStack: ast.Node[] = [];
file.visit({
visit(node) {
const k = node.kind;
@ -85,6 +88,22 @@ export function resolve(
}
resols.set(node.id, sym);
}
if (k.tag === "WhileStmt") {
loopStack.push(node);
node.visitBelow(this);
loopStack.pop();
return "break";
}
if (k.tag === "BreakStmt") {
const loopNode = loopStack.at(-1);
if (!loopNode) {
reporter.error(node.loc, `break outside loop`);
reporter.abort();
}
resols.set(node.id, { tag: "Loop", stmt: loopNode });
}
},
});

View File

@ -25,6 +25,7 @@ class FnLowerer {
private allocs: Inst[] = [];
private bbs: BasicBlock[] = [new BasicBlock()];
private localMap = new Map<number, Inst>();
private loopEndMap = new Map<number, BasicBlock>();
constructor(
private lowerer: MiddleLowerer,
@ -68,6 +69,7 @@ class FnLowerer {
? this.lowerExpr(stmt.kind.expr)
: this.makeVoid();
this.pushInst(Ty.Void, "Return", { source });
this.bbs.push(new BasicBlock());
return;
}
if (stmt.is("IfStmt")) {
@ -108,6 +110,54 @@ class FnLowerer {
);
return;
}
if (stmt.is("WhileStmt")) {
const before = this.bbs.at(-1)!;
this.bbs.push(new BasicBlock());
const body = this.bbs.at(-1)!;
const after = new BasicBlock();
this.loopEndMap.set(stmt.id, after);
this.lowerBlock(stmt.kind.body.as("Block"));
const bodyEnd = this.bbs.at(-1)!;
this.bbs.push(new BasicBlock());
const condBlock = this.bbs.at(-1)!;
const cond = this.lowerExpr(stmt.kind.cond);
const condBlockEnd = this.bbs.at(-1)!;
this.bbs.push(after);
before.insts.push(
new Inst(Ty.Void, { tag: "Jump", target: condBlock }),
);
condBlockEnd.insts.push(
new Inst(Ty.Void, {
tag: "Branch",
cond: cond,
truthy: body,
falsy: after,
}),
);
bodyEnd.insts.push(
new Inst(Ty.Void, { tag: "Jump", target: condBlock }),
);
return;
}
if (stmt.is("BreakStmt")) {
const sym = this.syms.get(stmt);
if (sym.tag !== "Loop") {
throw new Error();
}
const loopEnd = this.loopEndMap.get(sym.stmt.id);
if (!loopEnd) {
throw new Error();
}
this.pushInst(Ty.Void, "Jump", { target: loopEnd });
this.bbs.push(new BasicBlock());
return;
}
if (stmt.is("AssignStmt")) {
const source = this.lowerExpr(stmt.kind.expr);
const target = this.lowerPlace(stmt.kind.place);

View File

@ -1,4 +1,5 @@
// expect: 2
// expect: 5
fn main()
{
@ -8,5 +9,17 @@ fn main()
a = 2;
}
if false {
a = 3;
}
print_int(a);
if false {
a = 4;
} else {
a = 5;
}
print_int(a);
}

22
tests/loop.ethlang Normal file
View File

@ -0,0 +1,22 @@
// expect: 3
// expect: 8
fn main()
{
let a = 0;
while a < 3 {
a = a + 1;
}
print_int(a);
while a < 10 {
if a >= 8 {
break;
}
a = a + 1;
}
print_int(a);
}