compiler: add loops

This commit is contained in:
SimonFJ20 2025-02-12 14:16:34 +01:00
parent 3d161efb99
commit a97a128336
7 changed files with 423 additions and 22 deletions

View File

@ -37,7 +37,7 @@ export class Checker {
Ty({ tag: "null" });
}
private checkLetStmtTy(stmt: ast.Stmt, kind: ast.LetStmt) {
private checkLetStmt(stmt: ast.Stmt, kind: ast.LetStmt) {
if (this.stmtChecked.has(stmt.id)) {
return;
}
@ -84,6 +84,42 @@ export class Checker {
exhausted(k);
}
public checkBreakStmt(stmt: ast.Stmt, kind: ast.BreakStmt) {
if (this.stmtChecked.has(stmt.id)) {
return;
}
this.stmtChecked.add(stmt.id);
const re = this.re.loopRes(stmt.id);
if (re.tag === "error") {
return;
}
if (re.tag !== "loop") {
if (kind.expr) {
this.report(
`'${re.tag}'-style loop cannot break with value`,
stmt.span,
);
return;
}
return;
}
const exTy = this.exprTys.get(re.expr.id)!;
if (!kind.expr) {
const ty = Ty({ tag: "null" });
const tyRes = this.resolveTys(ty, exTy);
if (!tyRes.ok) {
this.report(tyRes.val, stmt.span);
return;
}
this.exprTys.set(re.expr.id, tyRes.val)!;
return;
}
const ty = this.exprTy(kind.expr, exTy);
if (ty.kind.tag !== "error") {
this.exprTys.set(re.expr.id, ty);
}
}
public checkAssignStmt(stmt: ast.Stmt, kind: ast.AssignStmt) {
if (this.stmtChecked.has(stmt.id)) {
return;
@ -207,9 +243,9 @@ export class Checker {
return Ty({ tag: "fn", item, kind, params, returnTy });
}
public exprTy(expr: ast.Expr): Ty {
public exprTy(expr: ast.Expr, expected = Ty({ tag: "unknown" })): Ty {
return this.exprTys.get(expr.id) ||
this.checkExpr(expr, Ty({ tag: "unknown" }));
this.checkExpr(expr, expected);
}
private checkExpr(expr: ast.Expr, expected: Ty): Ty {
@ -256,13 +292,13 @@ export class Checker {
case "if":
return this.checkIfExpr(expr, k, expected);
case "loop":
return todo();
return this.checkLoopExpr(expr, k, expected);
case "while":
return todo();
return this.checkWhileExpr(expr, k, expected);
case "for":
return todo();
case "c_for":
return todo();
return this.checkCForExpr(expr, k, expected);
}
exhausted(k);
}
@ -427,6 +463,80 @@ export class Checker {
return bothRes.val;
}
private checkLoopExpr(
expr: ast.Expr,
kind: ast.LoopExpr,
expected: Ty,
): Ty {
this.exprTys.set(expr.id, expected);
const body = this.exprTy(kind.body, Ty({ tag: "unknown" }));
if (body.kind.tag !== "null") {
if (body.kind.tag !== "error") {
this.report("loop body must not yield a value", kind.body.span);
}
const ty = Ty({ tag: "error" });
this.exprTys.set(expr.id, ty);
return ty;
}
for (const { stmt, kind } of this.re.loopBreaks(expr.id)) {
this.checkBreakStmt(stmt, kind);
}
return this.exprTys.get(expr.id)!;
}
private checkWhileExpr(
expr: ast.Expr,
kind: ast.LoopExpr,
expected: Ty,
): Ty {
const ty = Ty({ tag: "null" });
this.exprTys.set(expr.id, ty);
const body = this.exprTy(kind.body, Ty({ tag: "unknown" }));
if (body.kind.tag !== "null") {
if (body.kind.tag !== "error") {
this.report("loop body must not yield a value", kind.body.span);
}
const ty = Ty({ tag: "error" });
this.exprTys.set(expr.id, ty);
return ty;
}
for (const { stmt, kind } of this.re.loopBreaks(expr.id)) {
this.checkBreakStmt(stmt, kind);
}
return ty;
}
private checkCForExpr(
expr: ast.Expr,
kind: ast.LoopExpr,
expected: Ty,
): Ty {
const ty = Ty({ tag: "null" });
this.exprTys.set(expr.id, ty);
const body = this.exprTy(kind.body, Ty({ tag: "unknown" }));
if (body.kind.tag !== "null") {
if (body.kind.tag !== "error") {
this.report("loop body must not yield a value", kind.body.span);
}
const ty = Ty({ tag: "error" });
this.exprTys.set(expr.id, ty);
return ty;
}
for (const { stmt, kind } of this.re.loopBreaks(expr.id)) {
this.checkBreakStmt(stmt, kind);
}
return ty;
}
private tyTy(ty: ast.Ty): Ty {
return this.tyTys.get(ty.id) ||
this.checkTy(ty);
@ -484,7 +594,7 @@ export class Checker {
return ty;
}
case "let": {
this.checkLetStmtTy(patRes.kind.stmt, patRes.kind.kind);
this.checkLetStmt(patRes.kind.stmt, patRes.kind.kind);
const ty = this.patTy(pat);
this.patTys.set(pat.id, ty);
return ty;

View File

@ -50,6 +50,12 @@ export class FnLowerer {
private paramLocals = new IdMap<LocalId, number>();
private loopInfos = new IdMap<AstId, {
loopBlock: BlockId;
endBlock: BlockId;
resultLocal?: LocalId;
}>();
public constructor(
private ctx: Ctx,
private re: Resols,
@ -107,7 +113,9 @@ export class FnLowerer {
case "let":
return this.lowerLetStmt(stmt, k);
case "return":
return todo(k.tag);
case "break":
return this.lowerBreakStmt(stmt, k);
case "continue":
return todo(k.tag);
case "assign":
@ -169,6 +177,27 @@ export class FnLowerer {
exhausted(k);
}
private lowerBreakStmt(stmt: ast.Stmt, kind: ast.BreakStmt) {
this.ch.checkBreakStmt(stmt, kind);
const re = this.re.loopRes(stmt.id);
if (re.tag === "error") {
return;
}
const info = this.loopInfos.get(re.expr.id)!;
if (kind.expr) {
const ty = this.ch.exprTy(kind.expr);
info.resultLocal = info.resultLocal ?? this.local(ty);
const rval = this.lowerExpr(kind.expr);
this.addStmt({
tag: "assign",
place: { local: info.resultLocal, proj: [] },
rval,
});
}
this.setTer({ tag: "goto", target: info.endBlock });
this.pushBlock();
}
private lowerAssignStmt(stmt: ast.Stmt, kind: ast.AssignStmt) {
this.ch.checkAssignStmt(stmt, kind);
const rval = this.lowerExpr(kind.value);
@ -262,10 +291,13 @@ export class FnLowerer {
case "if":
return this.lowerIfExpr(expr, k);
case "loop":
return this.lowerLoopExpr(expr, k);
case "while":
return this.lowerWhileExpr(expr, k);
case "for":
case "c_for":
return todo(k.tag);
case "c_for":
return this.lowerCForExpr(expr, k);
}
exhausted(k);
}
@ -367,8 +399,11 @@ export class FnLowerer {
}
const truthBlock = this.pushBlock();
this.lowerExpr(kind.truthy);
const exit = this.pushBlock();
this.setTer({ tag: "goto", target: exit.id }, truthBlock);
const exit = this.createBlock();
this.setTer({ tag: "goto", target: exit.id });
this.pushCreatedBlock(exit);
this.setTer({
tag: "switch",
discr,
@ -382,6 +417,146 @@ export class FnLowerer {
}
}
private lowerLoopExpr(expr: ast.Expr, kind: ast.LoopExpr): RVal {
const entryBlock = this.currentBlock!;
const loopBlock = this.pushBlock();
const endBlock = this.createBlock();
const info = {
loopBlock: loopBlock.id,
endBlock: endBlock.id,
resultLocal: undefined,
};
this.loopInfos.set(expr.id, info);
const rval = this.lowerExpr(kind.body);
// ignore value;
void rval;
this.setTer({ tag: "goto", target: loopBlock.id });
this.setTer({ tag: "goto", target: loopBlock.id }, entryBlock);
this.pushCreatedBlock(endBlock);
if (info.resultLocal) {
const ty = this.ch.exprTy(expr);
return {
tag: "use",
operand: this.copyOrMoveLocal(info.resultLocal, ty),
};
} else {
return {
tag: "use",
operand: { tag: "const", val: { tag: "null" } },
};
}
}
private lowerWhileExpr(expr: ast.Expr, kind: ast.WhileExpr): RVal {
const enterBlock = this.currentBlock!;
const condBlock = this.pushBlock();
this.setTer({ tag: "goto", target: condBlock.id }, enterBlock);
const condTy = this.ch.exprTy(kind.cond);
const condLocal = this.localMut(condTy);
const condVal = this.lowerExpr(kind.cond);
this.addStmt({
tag: "assign",
place: { local: condLocal, proj: [] },
rval: condVal,
});
if (this.ch.exprTy(expr).kind.tag !== "null") {
throw new Error();
}
const bodyBlock = this.pushBlock();
const exitBlock = this.createBlock();
this.loopInfos.set(expr.id, {
loopBlock: condBlock.id,
endBlock: exitBlock.id,
});
this.lowerExpr(kind.body);
this.setTer({ tag: "goto", target: condBlock.id });
this.pushCreatedBlock(exitBlock);
this.setTer({
tag: "switch",
discr: this.copyOrMoveLocal(condLocal, condTy),
targets: [{ value: 1, target: bodyBlock.id }],
otherwise: exitBlock.id,
}, condBlock);
return {
tag: "use",
operand: { tag: "const", val: { tag: "null" } },
};
}
private lowerCForExpr(expr: ast.Expr, kind: ast.CForExpr): RVal {
kind.decl && this.lowerStmt(kind.decl);
const enterBlock = this.currentBlock!;
if (this.ch.exprTy(expr).kind.tag !== "null") {
throw new Error();
}
let loopBlock: Block;
const exitBlock = this.createBlock();
if (kind.cond) {
const condBlock = this.pushBlock();
this.setTer({ tag: "goto", target: condBlock.id }, enterBlock);
const condTy = this.ch.exprTy(kind.cond);
const condLocal = this.localMut(condTy);
const condVal = this.lowerExpr(kind.cond);
this.addStmt({
tag: "assign",
place: { local: condLocal, proj: [] },
rval: condVal,
});
const bodyBlock = this.pushBlock();
this.setTer({
tag: "switch",
discr: this.copyOrMoveLocal(condLocal, condTy),
targets: [{ value: 1, target: bodyBlock.id }],
otherwise: exitBlock.id,
}, condBlock);
loopBlock = condBlock;
this.loopInfos.set(expr.id, {
loopBlock: condBlock.id,
endBlock: exitBlock.id,
});
} else {
loopBlock = this.pushBlock();
this.setTer({ tag: "goto", target: loopBlock.id }, loopBlock);
this.loopInfos.set(expr.id, {
loopBlock: loopBlock.id,
endBlock: exitBlock.id,
});
}
this.lowerExpr(kind.body);
if (kind.incr) {
this.lowerStmt(kind.incr);
}
this.setTer({ tag: "goto", target: loopBlock.id });
this.pushCreatedBlock(exitBlock);
return {
tag: "use",
operand: { tag: "const", val: { tag: "null" } },
};
}
private lowerExprToOperand(expr: ast.Expr): Operand {
const k = expr.kind;
switch (k.tag) {
@ -510,6 +685,21 @@ export class FnLowerer {
return block;
}
private createBlock(): Block {
const id = this.blockIds.nextThenStep();
const block: Block = {
id,
stmts: [],
terminator: { kind: { tag: "unset" } },
};
return block;
}
private pushCreatedBlock(block: Block) {
this.blocks.set(block.id, block);
this.currentBlock = block;
}
private setTer(kind: TerKind, block = this.currentBlock!) {
block.terminator = { kind };
}

View File

@ -1,8 +1,6 @@
fn main() {
let mut a = 5;
a = 10;
for (let mut i = 0; i < 10; i = i + 1) {}
}

View File

@ -33,6 +33,18 @@ export const ResolveError = (ident: ast.Ident): Resolve => ({
kind: { tag: "error" },
});
export type LoopResolve =
| { tag: "error" }
| { tag: "loop"; expr: ast.Expr; kind: ast.LoopExpr }
| { tag: "while"; expr: ast.Expr; kind: ast.WhileExpr }
| { tag: "for"; expr: ast.Expr; kind: ast.ForExpr }
| { tag: "cfor"; expr: ast.Expr; kind: ast.CForExpr };
export type LoopBreakResolve = {
stmt: ast.Stmt;
kind: ast.BreakStmt;
};
export type Redef = {
ident: ast.Ident;
};

View File

@ -13,6 +13,8 @@ import {
import {
FnSyms,
LocalSyms,
LoopBreakResolve,
LoopResolve,
PatResolve,
PatResolveKind,
Redef,
@ -26,6 +28,8 @@ export class Resols {
public constructor(
private exprResols: IdMap<AstId, Resolve>,
private patResols: IdMap<AstId, PatResolve>,
private loopsResols: IdMap<AstId, LoopResolve>,
private loopBreakResols: IdMap<AstId, LoopBreakResolve[]>,
) {}
public exprRes(id: AstId): Resolve {
@ -41,6 +45,20 @@ export class Resols {
}
return this.patResols.get(id)!;
}
public loopRes(id: AstId): LoopResolve {
if (!this.loopsResols.has(id)) {
throw new Error();
}
return this.loopsResols.get(id)!;
}
public loopBreaks(id: AstId): LoopBreakResolve[] {
if (!this.loopBreakResols.has(id)) {
throw new Error();
}
return this.loopBreakResols.get(id)!;
}
}
export class Resolver implements ast.Visitor {
@ -49,10 +67,14 @@ export class Resolver implements ast.Visitor {
private syms: Syms = this.rootSyms;
private exprResols = new IdMap<AstId, Resolve>();
private patResols = new IdMap<AstId, PatResolve>();
private patResols = new IdMap<AstId, PatResolve>();
private patResolveStack: PatResolveKind[] = [];
private loopsResols = new IdMap<AstId, LoopResolve>();
private loopBreakResols = new IdMap<AstId, LoopBreakResolve[]>();
private loopResolveStack: LoopResolve[] = [{ tag: "error" }];
public constructor(
private ctx: Ctx,
private entryFileAst: ast.File,
@ -63,6 +85,8 @@ export class Resolver implements ast.Visitor {
return new Resols(
this.exprResols,
this.patResols,
this.loopsResols,
this.loopBreakResols,
);
}
@ -101,6 +125,25 @@ export class Resolver implements ast.Visitor {
todo();
}
visitBreakStmt(stmt: ast.Stmt, kind: ast.BreakStmt): ast.VisitRes {
const res = this.loopResolveStack.at(-1)!;
if (res.tag === "error") {
this.report("no loop to break", stmt.span);
return;
}
this.loopsResols.set(stmt.id, res);
this.loopBreakResols.get(res.expr.id)!.push({ stmt, kind });
}
visitContinueStmt(stmt: ast.Stmt): ast.VisitRes {
const res = this.loopResolveStack.at(-1)!;
if (res.tag === "error") {
this.report("no loop to continue", stmt.span);
return;
}
this.loopsResols.set(stmt.id, res);
}
visitEnumItem(item: ast.Item, kind: ast.EnumItem): ast.VisitRes {
todo();
}
@ -176,6 +219,42 @@ export class Resolver implements ast.Visitor {
todo();
}
visitLoopExpr(expr: ast.Expr, kind: ast.LoopExpr): ast.VisitRes {
this.genericVisitLoop(expr, kind.body, { tag: "loop", expr, kind });
return "stop";
}
visitWhileExpr(expr: ast.Expr, kind: ast.WhileExpr): ast.VisitRes {
ast.visitExpr(this, kind.cond);
this.genericVisitLoop(expr, kind.body, { tag: "while", expr, kind });
return "stop";
}
visitForExpr(expr: ast.Expr, kind: ast.ForExpr): ast.VisitRes {
todo();
return "stop";
}
visitCForExpr(expr: ast.Expr, kind: ast.CForExpr): ast.VisitRes {
const outerSyms = this.syms;
this.syms = new LocalSyms(this.syms);
kind.decl && ast.visitStmt(this, kind.decl);
kind.cond && ast.visitExpr(this, kind.cond);
kind.incr && ast.visitStmt(this, kind.incr);
this.genericVisitLoop(expr, kind.body, { tag: "cfor", expr, kind });
this.syms = outerSyms;
return "stop";
}
private genericVisitLoop(expr: ast.Expr, body: ast.Expr, res: LoopResolve) {
this.loopResolveStack.push(res);
this.loopBreakResols.set(expr.id, []);
ast.visitExpr(this, body);
this.loopResolveStack.pop();
return "stop";
}
visitBindPat(pat: ast.Pat, kind: ast.BindPat): ast.VisitRes {
this.patResols.set(pat.id, { pat, kind: this.patResolveStack.at(-1)! });
const res = this.syms.defVal(kind.ident, {

View File

@ -22,10 +22,11 @@ export class HirStringifyer {
return "<error>;";
case "item":
return this.item(k.item);
case "let":
case "let": {
return `let ${this.pat(k.pat)}${
k.expr && ` = ${this.expr(k.expr, d)}` || ""
};`;
}
case "return":
return `return${k.expr && ` ${this.expr(k.expr, d)}` || ""};`;
case "break":
@ -33,7 +34,9 @@ export class HirStringifyer {
case "continue":
return `continue;`;
case "assign":
return `${this.expr(k.subject, d)} = ${this.expr(k.value, d)};`;
return `${this.expr(k.subject, d)} ${k.assignType} ${
this.expr(k.value, d)
};`;
case "expr":
return `${this.expr(k.expr, d)};`;
}
@ -119,10 +122,17 @@ export class HirStringifyer {
k.falsy && ` else ${this.expr(k.falsy, d)}` || ""
}`;
case "loop":
return `loop ${this.expr(k.body, d)}`;
case "while":
return `while ${this.expr(k.cond, d)} ${this.expr(k.body, d)}`;
case "for":
case "c_for":
return todo(k.tag);
case "c_for":
return `for (${k.decl && this.stmt(k.decl, d) || ";"}${
k.cond && ` ${this.expr(k.cond, d)}` || ""
};${k.incr && ` ${this.stmt(k.incr, d)}` || ""}) ${
this.expr(k.body, d)
}`;
}
exhausted(k);
}

View File

@ -52,11 +52,13 @@ export class MirFnStringifyer {
.filter((local) => !fn.paramLocals.has(local.id))
.map((local) => `#let ${this.localDef(local)}`)
.join("\n");
return `fn ${fn.label}(${paramsStr}) {\n${localsStr}\n${
fn.blocks.values().toArray()
.map((block) => this.block(block))
.join("\n")
}\n}`.replaceAll("#", " ");
const blocks = fn.blocks
.values()
.toArray()
.map((block) => this.block(block))
.join("\n");
return `fn ${fn.label}(${paramsStr}) {\n${localsStr}\n${blocks}\n}`
.replaceAll("#", " ");
}
private localDef(local: Local): string {