compiler: if + other things

This commit is contained in:
SimonFJ20 2025-02-10 12:20:29 +01:00
parent 87561c624d
commit 010a3e7d19
8 changed files with 342 additions and 133 deletions

View File

@ -26,7 +26,7 @@ export class Checker {
public constructor(
private ctx: Ctx,
private entryFileAst: ast.File,
private resols: resolve.Resols,
private re: resolve.Resols,
) {
this.currentFile = ctx.entryFile();
}
@ -48,20 +48,20 @@ export class Checker {
const exprTy = kind.expr && Ok(this.exprTy(kind.expr));
const tyTy = kind.ty && Ok(this.tyTy(kind.ty));
const ty = exprTy !== undefined
? tyTy !== undefined
? this.resolveTys(exprTy.val, tyTy.val)
: exprTy
: exprTy;
const ty = exprTy !== undefined && tyTy !== undefined
? this.resolveTys(exprTy.val, tyTy.val)
: exprTy || tyTy;
this.stmtChecked.add(stmt.id);
if (ty === undefined) {
this.assignPatTy(kind.pat, Ty({ tag: "error" }));
this.report("type amfibious, specify type or value", stmt.span);
return Ty({ tag: "error" });
}
if (!ty.ok) {
this.assignPatTy(kind.pat, Ty({ tag: "error" }));
this.report(ty.val, stmt.span);
return Ty({ tag: "error" });
}
@ -116,7 +116,7 @@ export class Checker {
case "int":
return Ty({ tag: "int" });
case "bool":
return todo();
return Ty({ tag: "bool" });
case "str":
return todo();
case "group":
@ -141,12 +141,58 @@ export class Checker {
return this.checkCallExpr(expr, k, expected);
case "unary":
return todo();
case "binary":
return todo();
case "block":
return todo();
case "if":
return todo();
case "binary": {
const res = this.resolveTys(
this.exprTy(k.left),
this.exprTy(k.right),
);
if (!res.ok) {
this.exprTys.set(expr.id, Ty({ tag: "error" }));
this.report(res.val, expr.span);
return Ty({ tag: "error" });
}
this.exprTys.set(expr.id, res.val);
return res.val;
}
case "block": {
const ty = this.checkBlock(k.block, expected);
return ty;
}
case "if": {
const cond = this.exprTy(k.cond);
const condRes = this.resolveTys(cond, Ty({ tag: "bool" }));
if (!condRes.ok) {
this.exprTys.set(expr.id, Ty({ tag: "error" }));
this.report("if-condition must be a boolean", k.cond.span);
return Ty({ tag: "error" });
}
const truthy = this.exprTy(k.truthy);
if (!k.falsy) {
const truthyRes = this.resolveTys(
truthy,
Ty({ tag: "null" }),
);
if (!truthyRes.ok) {
this.exprTys.set(expr.id, Ty({ tag: "error" }));
this.report(
"if there isn't a falsy-clause, then the truthy clause must evaluate to null",
k.truthy.span,
);
return Ty({ tag: "error" });
}
this.exprTys.set(expr.id, Ty({ tag: "null" }));
return Ty({ tag: "null" });
}
const falsy = this.exprTy(k.falsy);
const bothRes = this.resolveTys(truthy, falsy);
if (!bothRes.ok) {
this.exprTys.set(expr.id, Ty({ tag: "error" }));
this.report(bothRes.val, k.truthy.span);
return Ty({ tag: "error" });
}
this.exprTys.set(expr.id, bothRes.val);
return bothRes.val;
}
case "loop":
return todo();
case "while":
@ -164,7 +210,7 @@ export class Checker {
kind: ast.PathExpr,
expected: Ty,
): Ty {
const res = this.resols.exprRes(expr.id);
const res = this.re.exprRes(expr.id);
switch (res.kind.tag) {
case "error":
return Ty({ tag: "error" });
@ -179,7 +225,7 @@ export class Checker {
return resu.val;
}
case "local": {
const patRes = this.resols.patRes(res.kind.id);
const patRes = this.re.patRes(res.kind.id);
const ty = this.patTy(patRes.pat);
const resu = this.resolveTys(ty, expected);
if (!resu.ok) {
@ -256,7 +302,7 @@ export class Checker {
}
private checkPat(pat: ast.Pat): Ty {
const patRes = this.resols.patRes(pat.id);
const patRes = this.re.patRes(pat.id);
const k = pat.kind;
switch (k.tag) {
case "error":
@ -303,12 +349,13 @@ export class Checker {
if (b.kind.tag === "unknown") {
return Res.Ok(a);
}
const as = tyToString(this.ctx, a);
const bs = tyToString(this.ctx, b);
const incompat = () =>
Res.Err(
const incompat = () => {
const as = tyToString(this.ctx, a);
const bs = tyToString(this.ctx, b);
return Res.Err(
`type '${as}' not compatible with type '${bs}'`,
);
};
switch (a.kind.tag) {
case "unknown":
return this.resolveTys(b, a);
@ -324,6 +371,12 @@ export class Checker {
}
return Res.Ok(a);
}
case "bool": {
if (b.kind.tag !== "bool") {
return incompat();
}
return Res.Ok(a);
}
case "fn": {
if (b.kind.tag !== "fn") {
return incompat();

View File

@ -3,15 +3,8 @@ import { Checker } from "@slige/check";
import { AstId, Ctx, exhausted, IdMap, Ids, Res, todo } from "@slige/common";
import { Resols } from "@slige/resolve";
import { Ty } from "@slige/ty";
import {
BinaryType,
Operand,
Place,
ProjElem,
StmtKind,
TerKind,
} from "./mir.ts";
import { Block, BlockId, Fn, Local, LocalId, RVal, Stmt, Ter } from "./mir.ts";
import { BinaryType, Operand, ProjElem, StmtKind, TerKind } from "./mir.ts";
import { Block, BlockId, Fn, Local, LocalId, RVal } from "./mir.ts";
import { MirFnStringifyer } from "@slige/stringify";
export class AstLowerer implements ast.Visitor {
@ -55,6 +48,8 @@ export class FnLowerer {
private reLocals = new IdMap<AstId, LocalId>();
private paramLocals = new IdMap<LocalId, number>();
public constructor(
private ctx: Ctx,
private re: Resols,
@ -85,7 +80,10 @@ export class FnLowerer {
label: this.ctx.identText(this.item.ident.id),
locals: this.locals,
blocks: this.blocks,
entry,
entry: entry.id,
paramLocals: this.paramLocals,
astItem: this.item,
astItemKind: this.kind,
});
}
@ -112,8 +110,13 @@ export class FnLowerer {
case "break":
case "continue":
case "assign":
case "expr":
return todo(k.tag);
case "expr": {
const rval = this.lowerExpr(k.expr);
// ignore the fuck out of the value
void rval;
return;
}
}
exhausted(k);
}
@ -122,13 +125,7 @@ export class FnLowerer {
const val = kind.expr && this.lowerExpr(kind.expr);
this.allocatePat(kind.pat);
if (val) {
const local = this.local(this.ch.patTy(kind.pat));
this.addStmt({
tag: "assign",
place: { local: local, proj: [] },
rval: val,
});
this.assignPat(kind.pat, local, []);
this.assignPatRVal(kind.pat, val);
}
}
@ -139,7 +136,7 @@ export class FnLowerer {
return;
case "bind": {
const ty = this.ch.patTy(pat);
const local = this.local(ty);
const local = this.local(ty, k.ident);
this.reLocals.set(pat.id, local);
return;
}
@ -149,7 +146,27 @@ export class FnLowerer {
exhausted(k);
}
private assignPat(pat: ast.Pat, local: LocalId, proj: ProjElem[]) {
private assignPatRVal(pat: ast.Pat, rval: RVal) {
const k = pat.kind;
switch (k.tag) {
case "error":
return;
case "bind": {
const patLocal = this.reLocals.get(pat.id)!;
this.addStmt({
tag: "assign",
place: { local: patLocal, proj: [] },
rval,
});
return;
}
case "path":
return todo();
}
exhausted(k);
}
private assignPat(pat: ast.Pat, local: LocalId, proj: ProjElem[] = []) {
const k = pat.kind;
switch (k.tag) {
case "error":
@ -180,19 +197,12 @@ export class FnLowerer {
case "path":
return this.lowerPathExpr(expr, k);
case "null":
return {
tag: "use",
operand: { tag: "const", val: { tag: "null" } },
};
case "int":
case "bool":
return {
tag: "use",
operand: {
tag: "const",
val: { tag: "int", value: k.value },
},
operand: this.lowerExprToOperand(expr),
};
case "bool":
case "str":
case "group":
case "array":
@ -211,7 +221,9 @@ export class FnLowerer {
case "binary":
return this.lowerBinaryExpr(expr, k);
case "block":
return this.lowerBlock(k.block);
case "if":
return this.lowerIfExpr(expr, k);
case "loop":
case "while":
case "for":
@ -226,63 +238,25 @@ export class FnLowerer {
switch (re.kind.tag) {
case "error":
return { tag: "error" };
case "fn": {
const ty = this.ch.fnItemTy(re.kind.item, re.kind.kind);
const local = this.local(ty);
case "fn":
case "local":
return {
tag: "use",
operand: { tag: "move", place: { local, proj: [] } },
operand: this.lowerPathExprToOperand(expr, kind),
};
}
case "local": {
const ty = this.ch.exprTy(expr);
const local = this.local(ty);
this.reLocals.set(re.kind.id, local);
const isCopyable = (() => {
switch (ty.kind.tag) {
case "error":
case "unknown":
return false;
case "null":
case "int":
return true;
case "fn":
return false;
}
exhausted(ty.kind);
})();
return {
tag: "use",
operand: {
tag: isCopyable ? "copy" : "move",
place: { local, proj: [] },
},
};
}
}
exhausted(re.kind);
}
private lowerCallExpr(expr: ast.Expr, kind: ast.CallExpr): RVal {
const args = kind.args.map((arg) =>
this.rvalAsOperand(this.lowerExpr(arg), this.ch.exprTy(arg))
);
const func = this.rvalAsOperand(
this.lowerExpr(kind.expr),
this.ch.exprTy(expr),
);
const args = kind.args.map((arg) => this.lowerExprToOperand(arg));
const func = this.lowerExprToOperand(kind.expr);
return { tag: "call", func, args };
}
private lowerBinaryExpr(expr: ast.Expr, kind: ast.BinaryExpr): RVal {
const left = this.rvalAsOperand(
this.lowerExpr(kind.left),
this.ch.exprTy(kind.left),
);
const right = this.rvalAsOperand(
this.lowerExpr(kind.right),
this.ch.exprTy(kind.right),
);
const left = this.lowerExprToOperand(kind.left);
const right = this.lowerExprToOperand(kind.right);
const binaryType = ((kind): BinaryType => {
switch (kind.binaryType) {
case "+":
@ -315,19 +289,139 @@ export class FnLowerer {
return { tag: "binary", binaryType, left, right };
}
private rvalAsOperand(rval: RVal, ty: Ty): Operand {
const local = this.local(ty);
this.addStmt({ tag: "assign", place: { local, proj: [] }, rval });
return { tag: "move", place: { local, proj: [] } };
private lowerIfExpr(expr: ast.Expr, kind: ast.IfExpr): RVal {
const cond = this.lowerExprToOperand(kind.cond);
const condBlock = this.currentBlock!;
if (kind.falsy) {
return todo();
} else {
if (this.ch.exprTy(expr).kind.tag !== "null") {
throw new Error();
}
const truthBlock = this.pushBlock();
this.lowerExpr(kind.truthy);
const exit = this.pushBlock();
this.setTer({ tag: "goto", target: exit.id }, truthBlock);
this.setTer({
tag: "switch",
discr: cond,
targets: [{ value: 1, target: truthBlock.id }],
otherwise: exit.id,
}, condBlock);
return {
tag: "use",
operand: { tag: "const", val: { tag: "null" } },
};
}
}
private local(ty: Ty): LocalId {
private lowerExprToOperand(expr: ast.Expr): Operand {
const k = expr.kind;
switch (k.tag) {
case "error":
return { tag: "error" };
case "path":
return this.lowerPathExprToOperand(expr, k);
case "null":
return { tag: "const", val: { tag: "null" } };
case "int":
return {
tag: "const",
val: { tag: "int", value: k.value },
};
case "bool":
return {
tag: "const",
val: { tag: "int", value: k.value ? 1 : 0 },
};
case "str":
case "group":
case "array":
case "repeat":
case "struct":
case "ref":
case "deref":
case "elem":
case "field":
case "index":
case "call":
case "unary":
case "binary":
case "block":
case "if":
case "loop":
case "while":
case "for":
case "c_for": {
const ty = this.ch.exprTy(expr);
const rval = this.lowerExpr(expr);
const local = this.local(ty);
this.addStmt({
tag: "assign",
place: { local, proj: [] },
rval,
});
return { tag: "move", place: { local, proj: [] } };
}
}
exhausted(k);
}
private lowerPathExprToOperand(
expr: ast.Expr,
kind: ast.PathExpr,
): Operand {
const re = this.re.exprRes(expr.id);
switch (re.kind.tag) {
case "error":
return { tag: "error" };
case "local": {
const patRes = this.re.patRes(re.kind.id);
const ty = this.ch.exprTy(expr);
let local: LocalId;
if (this.reLocals.has(re.kind.id)) {
local = this.reLocals.get(re.kind.id)!;
} else {
local = this.local(ty);
this.reLocals.set(re.kind.id, local);
}
if (patRes.kind.tag === "fn_param") {
this.paramLocals.set(local, patRes.kind.paramIdx);
}
const isCopyable = (() => {
switch (ty.kind.tag) {
case "error":
case "unknown":
return false;
case "null":
case "int":
case "bool":
return true;
case "fn":
return false;
}
exhausted(ty.kind);
})();
return {
tag: isCopyable ? "copy" : "move",
place: { local, proj: [] },
};
}
case "fn": {
const { item, kind } = re.kind;
return { tag: "const", val: { tag: "fn", item, kind } };
}
}
exhausted(re.kind);
}
private local(ty: Ty, ident?: ast.Ident): LocalId {
const id = this.localIds.nextThenStep();
this.locals.set(id, { id, ty });
this.locals.set(id, { id, ty, ident });
return id;
}
private pushBlock(): BlockId {
private pushBlock(): Block {
const id = this.blockIds.nextThenStep();
const block: Block = {
id,
@ -336,15 +430,15 @@ export class FnLowerer {
};
this.blocks.set(id, block);
this.currentBlock = block;
return id;
return block;
}
private setTer(kind: TerKind) {
this.currentBlock!.terminator = { kind };
private setTer(kind: TerKind, block = this.currentBlock!) {
block.terminator = { kind };
}
private addStmt(kind: StmtKind) {
this.currentBlock!.stmts.push({ kind });
private addStmt(kind: StmtKind, block = this.currentBlock!) {
block.stmts.push({ kind });
}
}

View File

@ -1,4 +1,5 @@
import { IdBase, IdMap } from "@slige/common";
import * as ast from "@slige/ast";
import { Ty } from "@slige/ty";
export type Fn = {
@ -6,6 +7,9 @@ export type Fn = {
locals: IdMap<LocalId, Local>;
blocks: IdMap<BlockId, Block>;
entry: BlockId;
paramLocals: IdMap<LocalId, number>;
astItem: ast.Item;
astItemKind: ast.FnItem;
};
export type LocalId = IdBase & { readonly _: unique symbol };
@ -13,6 +17,7 @@ export type LocalId = IdBase & { readonly _: unique symbol };
export type Local = {
id: LocalId;
ty: Ty;
ident?: ast.Ident;
};
export type BlockId = IdBase & { readonly _: unique symbol };
@ -102,6 +107,7 @@ export type BinaryType =
export type UnaryType = "not" | "neg";
export type Operand =
| { tag: "error" }
| { tag: "copy"; place: Place }
| { tag: "move"; place: Place }
| { tag: "const"; val: Const };
@ -109,5 +115,5 @@ export type Operand =
export type Const =
| { tag: "null" }
| { tag: "int"; value: number }
| { tag: "bool"; value: boolean }
| { tag: "string"; value: string };
| { tag: "str"; value: string }
| { tag: "fn"; item: ast.Item; kind: ast.FnItem };

View File

@ -5,7 +5,10 @@ fn add(lhs: int, rhs: int) -> int {
fn main() {
let a = 5;
let b = 7;
let b = 7 + a;
if true {}
let c = add(a, b);
}

View File

@ -112,7 +112,9 @@ export class HirStringifyer {
this.expr(k.right)
}`;
case "block":
return todo(k.tag);
case "if":
return `if ${this.expr(k.cond)}`;
case "loop":
case "while":
case "for":
@ -164,6 +166,8 @@ export class HirStringifyer {
return "null";
case "int":
return "int";
case "bool":
return "bool";
case "fn":
return `fn ${k.item.ident}(${
k.params.map((param) => this.ty(param)).join(", ")

View File

@ -26,11 +26,35 @@ export class MirFnStringifyer {
) {}
public fn(fn: Fn): string {
return `fn ${fn.label} {\n${
fn.locals.values().toArray()
.map((local) => this.localDef(local))
.join("\n")
}\n${
for (
const [idx, id] of fn.blocks
.keys()
.toArray()
.entries()
) {
this.blockIds.set(id, idx);
}
for (
const [idx, id] of fn.locals
.keys()
.toArray()
.entries()
) {
this.localIds.set(id, idx);
}
const locals = fn.locals.values().toArray();
const paramsStr = locals
.filter((local) => fn.paramLocals.has(local.id))
.map((local) => [local, fn.paramLocals.get(local.id)!] as const)
.toSorted((a, b) => a[1] - b[1])
.map(([local]) => fn.locals.get(local.id)!)
.map((local) => this.localDef(local))
.join(", ");
const localsStr = locals
.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")
@ -38,19 +62,20 @@ export class MirFnStringifyer {
}
private localDef(local: Local): string {
const id = this.localIds.size;
this.localIds.set(local.id, id);
return `#let %${id}: ${tyToString(this.ctx, local.ty)}`;
const ident = local.ident && ` // ${local.ident.text}` || "";
return `${this.local(local.id)}: ${this.ty(local.ty)}${ident}`;
}
private block(block: Block): string {
const id = this.blockIds.size;
this.blockIds.set(block.id, id);
const id = this.blockIds.get(block.id);
return `#.b${id}: {\n${
block.stmts
.map((stmt) => this.stmt(stmt))
[
...block.stmts
.map((stmt) => this.stmt(stmt)),
this.ter(block.terminator),
]
.join("\n")
}\n${this.ter(block.terminator)}\n#}`;
}\n#}`;
}
private stmt(stmt: Stmt): string {
@ -76,8 +101,17 @@ export class MirFnStringifyer {
case "unset":
return "##<unset>;";
case "goto":
case "switch":
return todo(k.tag);
return `##goto ${this.blockId(k.target)}`;
case "switch": {
const discr = this.operand(k.discr);
const targets = k.targets
.map((target) =>
`\n###${target.value} => ${this.blockId(target.target)}`
)
.join("");
const otherwise = this.blockId(k.otherwise);
return `##switch ${discr}${targets}\n###_ => ${otherwise}`;
}
case "return":
return `##return;`;
case "unreachable":
@ -122,14 +156,16 @@ export class MirFnStringifyer {
case "ref":
case "ptr":
return todo(rval.tag);
case "binary":
return `${this.operand(rval.left)} ${rval.binaryType} ${
this.operand(rval.right)
}`;
case "binary": {
const op = rval.binaryType;
const left = this.operand(rval.left);
const right = this.operand(rval.right);
return `${op}(${left}, ${right})`;
}
case "unary":
return todo(rval.tag);
case "call":
return `${this.operand(rval.func)}(${
return `call ${this.operand(rval.func)}(${
rval.args.map((arg) => this.operand(arg)).join(", ")
})`;
}
@ -138,6 +174,8 @@ export class MirFnStringifyer {
private operand(operand: Operand): string {
switch (operand.tag) {
case "error":
return `<error>`;
case "copy":
return `copy ${this.place(operand.place)}`;
case "move":
@ -154,15 +192,23 @@ export class MirFnStringifyer {
return `null`;
case "int":
return `${val.value}`;
case "bool":
return `${val.value}`;
case "string":
case "str":
return `"${val.value}"`;
case "fn":
return `${val.item.ident.text}`;
}
exhausted(val);
}
private blockId(id: BlockId): string {
return `.b${this.blockIds.get(id)!}`;
}
private local(local: LocalId): string {
return `%${this.localIds.get(local)!}`;
return `_${this.localIds.get(local)!}`;
}
private ty(ty: Ty): string {
return tyToString(this.ctx, ty);
}
}

View File

@ -12,6 +12,8 @@ export function tyToString(ctx: Ctx, ty: Ty): string {
return `null`;
case "int":
return `int`;
case "bool":
return `bool`;
case "fn": {
const identText = ctx.identText(k.item.ident.id);
const params = k.params

View File

@ -11,6 +11,7 @@ export type TyKind =
| { tag: "unknown" }
| { tag: "null" }
| { tag: "int" }
| { tag: "bool" }
| {
tag: "fn";
item: ast.Item;