diff --git a/slige/compiler/check/checker.ts b/slige/compiler/check/checker.ts index 3edb111..d31b518 100644 --- a/slige/compiler/check/checker.ts +++ b/slige/compiler/check/checker.ts @@ -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(); diff --git a/slige/compiler/middle/ast_lower.ts b/slige/compiler/middle/ast_lower.ts index 6a6c44f..669f150 100644 --- a/slige/compiler/middle/ast_lower.ts +++ b/slige/compiler/middle/ast_lower.ts @@ -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(); + private paramLocals = new IdMap(); + 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 }); } } diff --git a/slige/compiler/middle/mir.ts b/slige/compiler/middle/mir.ts index f655904..dceea9f 100644 --- a/slige/compiler/middle/mir.ts +++ b/slige/compiler/middle/mir.ts @@ -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; blocks: IdMap; entry: BlockId; + paramLocals: IdMap; + 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 }; diff --git a/slige/compiler/program.slg b/slige/compiler/program.slg index a79216a..5d9dedc 100644 --- a/slige/compiler/program.slg +++ b/slige/compiler/program.slg @@ -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); } diff --git a/slige/compiler/stringify/hir.ts b/slige/compiler/stringify/hir.ts index 6e11a30..4fac324 100644 --- a/slige/compiler/stringify/hir.ts +++ b/slige/compiler/stringify/hir.ts @@ -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(", ") diff --git a/slige/compiler/stringify/mir.ts b/slige/compiler/stringify/mir.ts index 1490de6..d1d201b 100644 --- a/slige/compiler/stringify/mir.ts +++ b/slige/compiler/stringify/mir.ts @@ -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 "##;"; 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 ``; 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); } } diff --git a/slige/compiler/ty/to_string.ts b/slige/compiler/ty/to_string.ts index 941c8bd..b3bae27 100644 --- a/slige/compiler/ty/to_string.ts +++ b/slige/compiler/ty/to_string.ts @@ -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 diff --git a/slige/compiler/ty/ty.ts b/slige/compiler/ty/ty.ts index 10fce19..ef01dd4 100644 --- a/slige/compiler/ty/ty.ts +++ b/slige/compiler/ty/ty.ts @@ -11,6 +11,7 @@ export type TyKind = | { tag: "unknown" } | { tag: "null" } | { tag: "int" } + | { tag: "bool" } | { tag: "fn"; item: ast.Item;