From f4886e045c82f49b05e1cac9f547afe79dc2095e Mon Sep 17 00:00:00 2001 From: SimonFJ20 Date: Thu, 6 Feb 2025 14:52:45 +0100 Subject: [PATCH] compiler: finish example ast lower --- slige/compiler/check/checker.ts | 82 ++++++++++-- slige/compiler/common/diagnostics.ts | 128 ++++++++++--------- slige/compiler/deno.jsonc | 15 ++- slige/compiler/main.ts | 13 +- slige/compiler/middle/ast_lower.ts | 120 +++++++++++++++++- slige/compiler/middle/mir.ts | 7 +- slige/compiler/middle/mod.ts | 2 + slige/compiler/parse/lexer.ts | 2 +- slige/compiler/resolve/cx.ts | 2 +- slige/compiler/resolve/resolver.ts | 9 +- slige/compiler/stringify/deno.jsonc | 4 + slige/compiler/stringify/hir.ts | 178 +++++++++++++++++++++++++++ slige/compiler/stringify/mir.ts | 168 +++++++++++++++++++++++++ slige/compiler/stringify/mod.ts | 2 + 14 files changed, 644 insertions(+), 88 deletions(-) create mode 100644 slige/compiler/middle/mod.ts create mode 100644 slige/compiler/stringify/deno.jsonc create mode 100644 slige/compiler/stringify/hir.ts create mode 100644 slige/compiler/stringify/mir.ts create mode 100644 slige/compiler/stringify/mod.ts diff --git a/slige/compiler/check/checker.ts b/slige/compiler/check/checker.ts index 3aaf75b..a46ba3e 100644 --- a/slige/compiler/check/checker.ts +++ b/slige/compiler/check/checker.ts @@ -48,7 +48,13 @@ export class Checker { const exprTy = kind.expr && Ok(this.exprTy(kind.expr)); const tyTy = kind.ty && Ok(this.tyTy(kind.ty)); - const ty = exprTy && tyTy && this.resolveTys(exprTy.val, tyTy.val); + const ty = exprTy !== undefined + ? tyTy !== undefined + ? this.resolveTys(exprTy.val, tyTy.val) + : exprTy + : exprTy; + + this.stmtChecked.add(stmt.id); if (ty === undefined) { this.report("type amfibious, specify type or value", stmt.span); @@ -65,8 +71,6 @@ export class Checker { this.report(res.val, stmt.span); return Ty({ tag: "error" }); } - - this.stmtChecked.add(stmt.id); } private assignPatTy(pat: ast.Pat, ty: Ty): Res { @@ -108,9 +112,9 @@ export class Checker { case "path": return this.checkPathExpr(expr, k, expected); case "null": - return todo(); + return Ty({ tag: "null" }); case "int": - return todo(); + return Ty({ tag: "int" }); case "bool": return todo(); case "str": @@ -134,7 +138,7 @@ export class Checker { case "index": return todo(); case "call": - return todo(); + return this.checkCallExpr(expr, k, expected); case "unary": return todo(); case "binary": @@ -188,6 +192,35 @@ export class Checker { exhausted(res.kind); } + private checkCallExpr( + expr: ast.Expr, + kind: ast.CallExpr, + expected: Ty, + ): Ty { + const fnTy = this.exprTy(kind.expr); + if (fnTy.kind.tag !== "fn") { + if (fnTy.kind.tag === "error") { + return fnTy; + } + const ty = tyToString(this.ctx, fnTy); + console.log(kind.expr.span); + this.report(`type '${ty}' not fucking callable`, kind.expr.span); + return Ty({ tag: "error" }); + } + const paramTys = fnTy.kind.params; + if (paramTys.length !== kind.args.length) { + this.report( + "not enough/too many fucking arguments", + kind.expr.span, + ); + return Ty({ tag: "error" }); + } + const _args = kind.args.map((arg, i) => + this.checkExpr(arg, paramTys[i]) + ); + return fnTy.kind.returnTy; + } + private tyTy(ty: ast.Ty): Ty { return this.tyTys.get(ty.id) || this.checkTy(ty); @@ -227,10 +260,29 @@ export class Checker { return todo(); case "bind": { switch (patRes.kind.tag) { - case "fn_param": - return todo(); - case "let": - return todo(); + case "fn_param": { + const fnTy = this.fnItemTy( + patRes.kind.item, + patRes.kind.kind, + ); + if (fnTy.kind.tag !== "fn") { + throw new Error(); + } + const paramTy = fnTy.kind.params[patRes.kind.paramIdx]; + this.assignPatTy( + patRes.kind.kind.params[patRes.kind.paramIdx].pat, + paramTy, + ); + const ty = this.patTy(pat); + this.patTys.set(pat.id, ty); + return ty; + } + case "let": { + this.checkLetStmtTy(patRes.kind.stmt, patRes.kind.kind); + const ty = this.patTy(pat); + this.patTys.set(pat.id, ty); + return ty; + } } exhausted(patRes.kind); return todo(); @@ -242,6 +294,12 @@ export class Checker { } private resolveTys(a: Ty, b: Ty): Res { + if (a.kind.tag === "error" || b.kind.tag === "error") { + return Res.Ok(a); + } + if (b.kind.tag === "unknown") { + return Res.Ok(a); + } const as = tyToString(this.ctx, a); const bs = tyToString(this.ctx, b); const incompat = () => @@ -249,10 +307,8 @@ export class Checker { `type '${as}' not compatible with type '${bs}'`, ); switch (a.kind.tag) { - case "error": - return Res.Ok(b); case "unknown": - return Res.Ok(b); + return this.resolveTys(b, a); case "null": { if (b.kind.tag !== "null") { return incompat(); diff --git a/slige/compiler/common/diagnostics.ts b/slige/compiler/common/diagnostics.ts index 35d7e6a..7f5d06a 100644 --- a/slige/compiler/common/diagnostics.ts +++ b/slige/compiler/common/diagnostics.ts @@ -25,6 +25,10 @@ export type Report = { pos?: Pos; }; +export type ReportLocation = + | { file: File; span: Span } + | { file: File; pos: Pos }; + function severityColor(severity: "fatal" | "error" | "warning" | "info") { switch (severity) { case "fatal": @@ -40,6 +44,13 @@ function severityColor(severity: "fatal" | "error" | "warning" | "info") { } export function prettyPrintReport(ctx: Ctx, rep: Report) { + if (rep.span && rep.span.begin.idx === rep.span.end.idx) { + return prettyPrintReport(ctx, { + ...rep, + span: undefined, + pos: rep.span.begin, + }); + } const { severity, msg } = rep; const origin = rep.origin ? `\x1b[1m${rep.origin}:\x1b[0m ` : ""; console.error( @@ -47,71 +58,72 @@ export function prettyPrintReport(ctx: Ctx, rep: Report) { severityColor(severity) }${severity}:\x1b[0m \x1b[37m${msg}\x1b[0m`, ); - if (rep.file && (rep.span || rep.pos)) { - const errorLineOffset = 2; - const { absPath: path } = ctx.fileInfo(rep.file); - const { line, col } = rep.span?.begin ?? rep.pos!; - console.error(` --> ./${path}:${line}:${col}`); - if (rep.span) { - const spanLines = ctx.fileSpanText(rep.file, rep.span).split("\n"); - spanLines.pop(); - if (spanLines.length == 1) { - console.error( - `${rep.span.begin.line.toString().padStart(4, " ")}| ${ - spanLines[0] - }`, - ); - console.error( - ` | ${severityColor(severity)}${ - " ".repeat(rep.span.begin.col) - }${ - "~".repeat(rep.span.end.col - rep.span.begin.col) - }\x1b[0m`, - ); - return; - } - for (let i = 0; i < spanLines.length; i++) { - console.error( - `${ - (rep.span.begin.line + i).toString().padStart(4, " ") - }| ${spanLines[i]}`, - ); - if (i == 0) { - console.error( - ` | ${" ".repeat(rep.span.begin.col - 1)}${ - severityColor(severity) - }${ - "~".repeat( - spanLines[i].length - (rep.span.begin.col - 1), - ) - }\x1b[0m`, - ); - } else if (i == spanLines.length - 1) { - console.error( - ` | ${severityColor(severity)}${ - "~".repeat(rep.span.end.col) - }\x1b[0m`, - ); - } else { - console.error( - ` | ${severityColor(severity)}${ - "~".repeat(spanLines[i].length) - }\x1b[0m`, - ); - } - } - } else if (rep.pos) { + if (!rep.file) { + return; + } + const errorLineOffset = 2; + const { absPath: path } = ctx.fileInfo(rep.file); + const { line, col } = rep.span?.begin ?? rep.pos!; + console.error(` --> ./${path}:${line}:${col}`); + if (rep.span) { + const spanLines = ctx.fileSpanText(rep.file, rep.span).split("\n"); + spanLines.pop(); + if (spanLines.length == 1) { console.error( - `${rep.pos.line.toString().padStart(4, " ")}| ${ - ctx.filePosLineText(rep.file, rep.pos) + `${rep.span.begin.line.toString().padStart(4, " ")}| ${ + spanLines[0] }`, ); console.error( ` | ${severityColor(severity)}${ - " ".repeat(rep.pos.col) - }^\x1b[0m`, + " ".repeat(rep.span.begin.col - 1) + }${ + "~".repeat(rep.span.end.col - rep.span.begin.col + 1) + }\x1b[0m`, ); + return; } + for (let i = 0; i < spanLines.length; i++) { + console.error( + `${(rep.span.begin.line + i).toString().padStart(4, " ")}| ${ + spanLines[i] + }`, + ); + if (i == 0) { + console.error( + ` | ${" ".repeat(rep.span.begin.col - 1)}${ + severityColor(severity) + }${ + "~".repeat( + spanLines[i].length - (rep.span.begin.col - 1), + ) + }\x1b[0m`, + ); + } else if (i == spanLines.length - 1) { + console.error( + ` | ${severityColor(severity)}${ + "~".repeat(rep.span.end.col) + }\x1b[0m`, + ); + } else { + console.error( + ` | ${severityColor(severity)}${ + "~".repeat(spanLines[i].length) + }\x1b[0m`, + ); + } + } + } else if (rep.pos) { + console.error( + `${rep.pos.line.toString().padStart(4, " ")}| ${ + ctx.filePosLineText(rep.file, rep.pos) + }`, + ); + console.error( + ` | ${severityColor(severity)}${ + " ".repeat(rep.pos.col - 1) + }^\x1b[0m`, + ); } } diff --git a/slige/compiler/deno.jsonc b/slige/compiler/deno.jsonc index b8ccffe..5f22490 100644 --- a/slige/compiler/deno.jsonc +++ b/slige/compiler/deno.jsonc @@ -1,15 +1,24 @@ { - "workspace": ["./ast", "./check", "./middle", "./parse", "./resolve", "./ty", "./common"], + "workspace": [ + "./ast", + "./check", + "./middle", + "./parse", + "./resolve", + "./ty", + "./common", + "./stringify" + ], "lint": { "rules": { "tags": ["recommended"], "exclude": [ "verbatim-module-syntax", "no-unused-vars" - ], + ] } }, "fmt": { "indentWidth": 4 - }, + } } diff --git a/slige/compiler/main.ts b/slige/compiler/main.ts index f03c2b7..7bc3c46 100644 --- a/slige/compiler/main.ts +++ b/slige/compiler/main.ts @@ -5,6 +5,7 @@ import { Ctx, File } from "@slige/common"; import { Resolver } from "./resolve/resolver.ts"; import { Checker } from "./check/checker.ts"; import { AstLowerer } from "./middle/ast_lower.ts"; +import { HirStringifyer } from "@slige/stringify"; async function main() { const filePath = Deno.args[0]; @@ -43,7 +44,17 @@ export class PackCompiler { .collect(); const resols = new Resolver(this.ctx, entryFileAst).resolve(); const checker = new Checker(this.ctx, entryFileAst, resols); - new AstLowerer(this.ctx, resols, checker, entryFileAst).lower(); + console.log( + "=== HIR ===\n" + new HirStringifyer(checker).file(entryFileAst), + ); + const astLowerer = new AstLowerer( + this.ctx, + resols, + checker, + entryFileAst, + ); + astLowerer.lower(); + console.log("=== MIR ===\n" + astLowerer.mirString()); } public enableDebug() { diff --git a/slige/compiler/middle/ast_lower.ts b/slige/compiler/middle/ast_lower.ts index 2c970e1..6a6c44f 100644 --- a/slige/compiler/middle/ast_lower.ts +++ b/slige/compiler/middle/ast_lower.ts @@ -3,10 +3,20 @@ 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, StmtKind, TerKind } from "./mir.ts"; +import { + BinaryType, + Operand, + Place, + ProjElem, + StmtKind, + TerKind, +} from "./mir.ts"; import { Block, BlockId, Fn, Local, LocalId, RVal, Stmt, Ter } from "./mir.ts"; +import { MirFnStringifyer } from "@slige/stringify"; export class AstLowerer implements ast.Visitor { + private fns = new IdMap(); + public constructor( private ctx: Ctx, private re: Resols, @@ -19,7 +29,18 @@ export class AstLowerer implements ast.Visitor { } visitFnItem(item: ast.Item, kind: ast.FnItem): ast.VisitRes { - new FnLowerer(this.ctx, this.re, this.ch, item, kind).lower(); + const fn = new FnLowerer(this.ctx, this.re, this.ch, item, kind) + .lower(); + if (fn.ok) { + this.fns.set(item.id, fn.val); + } + } + + public mirString(): string { + return this.fns.values() + .map((fn) => new MirFnStringifyer(this.ctx).fn(fn)) + .toArray() + .join("\n"); } } @@ -46,7 +67,10 @@ export class FnLowerer { const entry = this.pushBlock(); const fnTy = this.ch.fnItemTy(this.item, this.kind); - const returnPlace = this.local(fnTy); + if (fnTy.kind.tag !== "fn") { + throw new Error(); + } + const returnPlace = this.local(fnTy.kind.returnTy); const returnVal = this.lowerBlock(this.kind.body!); this.addStmt({ @@ -81,12 +105,68 @@ export class FnLowerer { case "error": return { tag: "error" }; case "item": + return todo(k.tag); case "let": + return this.lowerLetStmt(stmt, k); case "return": case "break": case "continue": case "assign": case "expr": + return todo(k.tag); + } + exhausted(k); + } + + private lowerLetStmt(_stmt: ast.Stmt, kind: ast.LetStmt) { + 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, []); + } + } + + private allocatePat(pat: ast.Pat) { + const k = pat.kind; + switch (k.tag) { + case "error": + return; + case "bind": { + const ty = this.ch.patTy(pat); + const local = this.local(ty); + this.reLocals.set(pat.id, local); + 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": + return; + case "bind": { + const patLocal = this.reLocals.get(pat.id)!; + this.addStmt({ + tag: "assign", + place: { local: patLocal, proj: [] }, + rval: { + tag: "use", + operand: { tag: "move", place: { local, proj } }, + }, + }); + return; + } + case "path": return todo(); } exhausted(k); @@ -100,7 +180,18 @@ export class FnLowerer { case "path": return this.lowerPathExpr(expr, k); case "null": + return { + tag: "use", + operand: { tag: "const", val: { tag: "null" } }, + }; case "int": + return { + tag: "use", + operand: { + tag: "const", + val: { tag: "int", value: k.value }, + }, + }; case "bool": case "str": case "group": @@ -112,7 +203,9 @@ export class FnLowerer { case "elem": case "field": case "index": + return todo(k.tag); case "call": + return this.lowerCallExpr(expr, k); case "unary": return todo(k.tag); case "binary": @@ -133,8 +226,14 @@ export class FnLowerer { switch (re.kind.tag) { case "error": return { tag: "error" }; - case "fn": - return todo(); + case "fn": { + const ty = this.ch.fnItemTy(re.kind.item, re.kind.kind); + const local = this.local(ty); + return { + tag: "use", + operand: { tag: "move", place: { local, proj: [] } }, + }; + } case "local": { const ty = this.ch.exprTy(expr); const local = this.local(ty); @@ -164,6 +263,17 @@ export class FnLowerer { 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), + ); + return { tag: "call", func, args }; + } + private lowerBinaryExpr(expr: ast.Expr, kind: ast.BinaryExpr): RVal { const left = this.rvalAsOperand( this.lowerExpr(kind.left), diff --git a/slige/compiler/middle/mir.ts b/slige/compiler/middle/mir.ts index b42175a..f655904 100644 --- a/slige/compiler/middle/mir.ts +++ b/slige/compiler/middle/mir.ts @@ -51,8 +51,7 @@ export type TerKind = } | { tag: "return" } | { tag: "unreachable" } - | { tag: "drop"; place: Place; target: BlockId } - | { tag: "call"; func: Operand; args: Operand[]; dest: Operand }; + | { tag: "drop"; place: Place; target: BlockId }; export type SwitchTarget = { value: number; @@ -67,7 +66,6 @@ export type Place = { // https://doc.rust-lang.org/beta/nightly-rustc/rustc_middle/mir/type.PlaceElem.html export type ProjElem = | { tag: "deref" } - | { tag: "repeat" } | { tag: "field"; fieldIdx: number } | { tag: "index"; local: LocalId } | { tag: "downcast"; variantIdx: number }; @@ -80,7 +78,8 @@ export type RVal = | { tag: "ref"; place: Place; mut: boolean } | { tag: "ptr"; place: Place; mut: boolean } | { tag: "binary"; binaryType: BinaryType; left: Operand; right: Operand } - | { tag: "unary"; unaryType: UnaryType; operand: Operand }; + | { tag: "unary"; unaryType: UnaryType; operand: Operand } + | { tag: "call"; func: Operand; args: Operand[] }; export type BinaryType = | "add" diff --git a/slige/compiler/middle/mod.ts b/slige/compiler/middle/mod.ts new file mode 100644 index 0000000..353fac5 --- /dev/null +++ b/slige/compiler/middle/mod.ts @@ -0,0 +1,2 @@ +export * from "./mir.ts"; +export * from "./ast_lower.ts"; diff --git a/slige/compiler/parse/lexer.ts b/slige/compiler/parse/lexer.ts index 510f4a8..d8bbe55 100644 --- a/slige/compiler/parse/lexer.ts +++ b/slige/compiler/parse/lexer.ts @@ -203,7 +203,7 @@ export class Lexer implements TokenIter { let val = this.current(); this.step(); while (this.test(tailPat)) { - end = begin; + end = this.pos(); val += this.current(); this.step(); } diff --git a/slige/compiler/resolve/cx.ts b/slige/compiler/resolve/cx.ts index e782633..3362d97 100644 --- a/slige/compiler/resolve/cx.ts +++ b/slige/compiler/resolve/cx.ts @@ -25,7 +25,7 @@ export type PatResolve = { }; export type PatResolveKind = - | { tag: "fn_param"; paramIdx: number } + | { tag: "fn_param"; item: ast.Item; kind: ast.FnItem; paramIdx: number } | { tag: "let"; stmt: ast.Stmt; kind: ast.LetStmt }; export const ResolveError = (ident: ast.Ident): Resolve => ({ diff --git a/slige/compiler/resolve/resolver.ts b/slige/compiler/resolve/resolver.ts index ecc0e71..72626ca 100644 --- a/slige/compiler/resolve/resolver.ts +++ b/slige/compiler/resolve/resolver.ts @@ -118,12 +118,17 @@ export class Resolver implements ast.Visitor { } private popAndVisitFnBodies() { - for (const [_item, kind] of this.fnBodiesToCheck.at(-1)!) { + for (const [item, kind] of this.fnBodiesToCheck.at(-1)!) { const outerSyms = this.syms; this.syms = new FnSyms(this.syms); this.syms = new LocalSyms(this.syms); for (const [paramIdx, param] of kind.params.entries()) { - this.patResolveStack.push({ tag: "fn_param", paramIdx }); + this.patResolveStack.push({ + tag: "fn_param", + item, + kind, + paramIdx, + }); ast.visitParam(this, param); this.patResolveStack.pop(); } diff --git a/slige/compiler/stringify/deno.jsonc b/slige/compiler/stringify/deno.jsonc new file mode 100644 index 0000000..8d0cb0b --- /dev/null +++ b/slige/compiler/stringify/deno.jsonc @@ -0,0 +1,4 @@ +{ + "name": "@slige/stringify", + "exports": "./mod.ts", +} diff --git a/slige/compiler/stringify/hir.ts b/slige/compiler/stringify/hir.ts new file mode 100644 index 0000000..6e11a30 --- /dev/null +++ b/slige/compiler/stringify/hir.ts @@ -0,0 +1,178 @@ +import * as ast from "@slige/ast"; +import { exhausted, todo } from "@slige/common"; +import { Checker } from "@slige/check"; +import { Ty } from "@slige/ty"; + +export class HirStringifyer { + public constructor( + private ch: Checker, + ) {} + + public file(file: ast.File, depth = 0): string { + return file.stmts.map((stmt) => + indent(depth) + this.stmt(stmt, depth + 1) + ).join("\n"); + } + + public stmt(stmt: ast.Stmt, depth = 0): string { + const k = stmt.kind; + switch (k.tag) { + case "error": + return ";"; + case "item": + return this.item(k.item); + case "let": + return `let ${this.pat(k.pat)}${ + k.expr && ` = ${this.expr(k.expr)}` || "" + };`; + case "return": + return `return${k.expr && ` ${this.expr(k.expr)}` || ""};`; + case "break": + return `break${k.expr && ` ${this.expr(k.expr)}` || ""};`; + case "continue": + return `continue;`; + case "assign": + return `${this.expr(k.subject)} = ${this.expr(k.value)};`; + case "expr": + return `${this.expr(k.expr)};`; + } + exhausted(k); + } + + public item(item: ast.Item, depth = 0): string { + const ident = item.ident.text; + const pub = item.pub ? "pub " : ""; + const k = item.kind; + switch (k.tag) { + case "error": + return ";"; + case "mod_block": + return `${pub}mod ${ident} ${this.block(k.block, depth)}`; + case "mod_file": + return `${pub}mod ${ident} {\n${ + this.file(k.ast!, depth + 1) + }\n}`; + case "enum": + return todo(); + case "struct": + return todo(); + case "fn": { + const ty = this.ch.fnItemTy(item, k); + if (ty.kind.tag !== "fn") { + throw new Error(); + } + const params = k.params + .map((param) => this.pat(param.pat)) + .join(", "); + return `${pub}fn ${ident}(${params}) -> ${ + this.ty(ty.kind.returnTy) + } ${this.block(k.body!, depth)}`; + } + case "use": + return todo(); + case "type_alias": + return todo(); + } + exhausted(k); + } + + public expr(expr: ast.Expr, depth = 0): string { + const k = expr.kind; + switch (k.tag) { + case "error": + return ""; + case "path": + return this.path(k.path); + case "null": + return "null"; + case "int": + return `${k.value}`; + case "bool": + return `${k.value}`; + case "str": + return `"${k.value}"`; + case "group": + case "array": + case "repeat": + case "struct": + case "ref": + case "deref": + case "elem": + case "field": + case "index": + return todo(k.tag); + case "call": + return `${this.expr(k.expr)}(${ + k.args.map((arg) => this.expr(arg)).join(", ") + })`; + case "unary": + return todo(k.tag); + case "binary": + return `${this.expr(k.left)} ${k.binaryType} ${ + this.expr(k.right) + }`; + case "block": + case "if": + case "loop": + case "while": + case "for": + case "c_for": + return todo(k.tag); + } + exhausted(k); + } + + public pat(pat: ast.Pat, depth = 0): string { + const k = pat.kind; + switch (k.tag) { + case "error": + return ""; + case "bind": + return `${k.mut ? "mut " : ""}${k.ident.text}: ${ + this.ty(this.ch.patTy(pat)) + }`; + case "path": + return todo(); + } + exhausted(k); + } + + public block(block: ast.Block, depth = 0): string { + return `{\n${ + [ + ...block.stmts + .map((stmt) => this.stmt(stmt, depth + 1)), + ...(block.expr ? [this.expr(block.expr)] : []), + ] + .map((str) => indent(depth + 1) + str) + .join("\n") + }\n${indent(depth)}}`; + } + + public path(path: ast.Path): string { + return path.segments.map((seg) => seg.ident.text).join("::"); + } + + public ty(ty: Ty): string { + const k = ty.kind; + switch (k.tag) { + case "error": + return ""; + case "unknown": + return ""; + case "null": + return "null"; + case "int": + return "int"; + case "fn": + return `fn ${k.item.ident}(${ + k.params.map((param) => this.ty(param)).join(", ") + }) -> ${this.ty(k.returnTy)}`; + } + exhausted(k); + } +} + +function indent(depth: number): string { + return " ".repeat(depth); +} diff --git a/slige/compiler/stringify/mir.ts b/slige/compiler/stringify/mir.ts new file mode 100644 index 0000000..1490de6 --- /dev/null +++ b/slige/compiler/stringify/mir.ts @@ -0,0 +1,168 @@ +import { + Block, + BlockId, + Const, + Fn, + Local, + LocalId, + Operand, + Place, + ProjElem, + RVal, + Stmt, + StmtKind, + Ter, +} from "@slige/middle"; +import { Ctx, exhausted, IdMap, todo } from "@slige/common"; +import { Checker } from "@slige/check"; +import { Ty, tyToString } from "@slige/ty"; + +export class MirFnStringifyer { + private blockIds = new IdMap(); + private localIds = new IdMap(); + + public constructor( + private ctx: Ctx, + ) {} + + public fn(fn: Fn): string { + return `fn ${fn.label} {\n${ + fn.locals.values().toArray() + .map((local) => this.localDef(local)) + .join("\n") + }\n${ + fn.blocks.values().toArray() + .map((block) => this.block(block)) + .join("\n") + }\n}`.replaceAll("#", " "); + } + + private localDef(local: Local): string { + const id = this.localIds.size; + this.localIds.set(local.id, id); + return `#let %${id}: ${tyToString(this.ctx, local.ty)}`; + } + + private block(block: Block): string { + const id = this.blockIds.size; + this.blockIds.set(block.id, id); + return `#.b${id}: {\n${ + block.stmts + .map((stmt) => this.stmt(stmt)) + .join("\n") + }\n${this.ter(block.terminator)}\n#}`; + } + + private stmt(stmt: Stmt): string { + const k = stmt.kind; + switch (k.tag) { + case "error": + return "##;"; + case "assign": + return `##${this.place(k.place)} = ${this.rval(k.rval)}`; + case "fake_read": + case "deinit": + case "live": + case "dead": + case "mention": + return todo(); + } + exhausted(k); + } + + private ter(ter: Ter): string { + const k = ter.kind; + switch (k.tag) { + case "unset": + return "##;"; + case "goto": + case "switch": + return todo(k.tag); + case "return": + return `##return;`; + case "unreachable": + case "drop": + return todo(k.tag); + } + exhausted(k); + } + + private place(place: Place): string { + return this.placeWithProj(place.local, place.proj); + } + + private placeWithProj(local: LocalId, elems: ProjElem[]): string { + if (elems.length === 0) { + return this.local(local); + } + const elem = elems[0]; + const tail = elems.slice(1); + switch (elem.tag) { + case "deref": + return `*${this.placeWithProj(local, tail)}`; + case "field": + return `${this.placeWithProj(local, tail)}.${elem.fieldIdx}`; + case "index": + return `${this.placeWithProj(local, tail)}[${ + this.local(elem.local) + }]`; + case "downcast": + return todo(); + } + exhausted(elem); + } + + private rval(rval: RVal): string { + switch (rval.tag) { + case "error": + return ""; + case "use": + return `${this.operand(rval.operand)}`; + case "repeat": + case "ref": + case "ptr": + return todo(rval.tag); + case "binary": + return `${this.operand(rval.left)} ${rval.binaryType} ${ + this.operand(rval.right) + }`; + case "unary": + return todo(rval.tag); + case "call": + return `${this.operand(rval.func)}(${ + rval.args.map((arg) => this.operand(arg)).join(", ") + })`; + } + exhausted(rval); + } + + private operand(operand: Operand): string { + switch (operand.tag) { + case "copy": + return `copy ${this.place(operand.place)}`; + case "move": + return `move ${this.place(operand.place)}`; + case "const": + return `${this.constVal(operand.val)}`; + } + exhausted(operand); + } + + private constVal(val: Const): string { + switch (val.tag) { + case "null": + return `null`; + case "int": + return `${val.value}`; + case "bool": + return `${val.value}`; + case "string": + return `"${val.value}"`; + } + exhausted(val); + } + + private local(local: LocalId): string { + return `%${this.localIds.get(local)!}`; + } +} diff --git a/slige/compiler/stringify/mod.ts b/slige/compiler/stringify/mod.ts new file mode 100644 index 0000000..28b54e5 --- /dev/null +++ b/slige/compiler/stringify/mod.ts @@ -0,0 +1,2 @@ +export * from "./hir.ts"; +export * from "./mir.ts";