diff --git a/slige/compiler/check/checker.ts b/slige/compiler/check/checker.ts index 7cbb76c..7d9572f 100644 --- a/slige/compiler/check/checker.ts +++ b/slige/compiler/check/checker.ts @@ -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; diff --git a/slige/compiler/middle/ast_lower.ts b/slige/compiler/middle/ast_lower.ts index 0cb7612..4488aa7 100644 --- a/slige/compiler/middle/ast_lower.ts +++ b/slige/compiler/middle/ast_lower.ts @@ -50,6 +50,12 @@ export class FnLowerer { private paramLocals = new IdMap(); + private loopInfos = new IdMap(); + 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 }; } diff --git a/slige/compiler/program.slg b/slige/compiler/program.slg index 88879f2..7cbefd6 100644 --- a/slige/compiler/program.slg +++ b/slige/compiler/program.slg @@ -1,8 +1,6 @@ fn main() { - let mut a = 5; - - a = 10; + for (let mut i = 0; i < 10; i = i + 1) {} } diff --git a/slige/compiler/resolve/cx.ts b/slige/compiler/resolve/cx.ts index 3362d97..0abcbd2 100644 --- a/slige/compiler/resolve/cx.ts +++ b/slige/compiler/resolve/cx.ts @@ -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; }; diff --git a/slige/compiler/resolve/resolver.ts b/slige/compiler/resolve/resolver.ts index 72626ca..0089566 100644 --- a/slige/compiler/resolve/resolver.ts +++ b/slige/compiler/resolve/resolver.ts @@ -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, private patResols: IdMap, + private loopsResols: IdMap, + private loopBreakResols: IdMap, ) {} 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(); - private patResols = new IdMap(); + private patResols = new IdMap(); private patResolveStack: PatResolveKind[] = []; + private loopsResols = new IdMap(); + private loopBreakResols = new IdMap(); + 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, { diff --git a/slige/compiler/stringify/hir.ts b/slige/compiler/stringify/hir.ts index b6599a8..cd2289e 100644 --- a/slige/compiler/stringify/hir.ts +++ b/slige/compiler/stringify/hir.ts @@ -22,10 +22,11 @@ export class HirStringifyer { return ";"; 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); } diff --git a/slige/compiler/stringify/mir.ts b/slige/compiler/stringify/mir.ts index 48b7708..e5aa5a9 100644 --- a/slige/compiler/stringify/mir.ts +++ b/slige/compiler/stringify/mir.ts @@ -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 {