import * as ast from "@slige/ast"; import { Ctx, exhausted, todo } from "@slige/common"; import { Checker } from "@slige/check"; import { Ty, tyToString } from "@slige/ty"; export class HirStringifyer { public constructor( private ctx: Ctx, 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, d = 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, d)}` || "" };`; } case "return": return `return${k.expr && ` ${this.expr(k.expr, d)}` || ""};`; case "break": return `break${k.expr && ` ${this.expr(k.expr, d)}` || ""};`; case "continue": return `continue;`; case "assign": return `${this.expr(k.subject, d)} ${k.assignType} ${ this.expr(k.value, d) };`; case "expr": return `${this.expr(k.expr, d)};`; } 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 `struct ${ident}: ${ this.ty(this.ch.structItemTy(item, k)) };`; 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, d: number): 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": return todo(k.tag); case "struct": return `${k.path ? `${this.path(k.path)}` : "struct "} {${ [ k.fields .map((field) => `${indent(d + 1)}${field.ident.text}: ${ this.expr(field.expr, d + 1) },` ) .join("\n"), ].map((s) => `\n${s}\n${indent(d)}`) }}`; case "ref": case "deref": case "elem": case "field": case "index": return todo(k.tag); case "call": return `${this.expr(k.expr, d)}(${ k.args.map((arg) => this.expr(arg, d)).join(", ") })`; case "unary": return todo(k.tag); case "binary": return `${this.expr(k.left, d)} ${k.binaryType} ${ this.expr(k.right, d) }`; case "block": return this.block(k.block, d); case "if": return `if ${this.expr(k.cond, d)} ${this.expr(k.truthy, d)}${ 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": 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); } public pat(pat: ast.Pat): 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, d: number): string { if (block.stmts.length === 0 && !block.expr) { return "{}"; } return `{\n${ [ ...block.stmts .map((stmt) => this.stmt(stmt, d + 1)), ...(block.expr ? [this.expr(block.expr, d + 1)] : []), ] .map((str) => indent(d + 1) + str) .join("\n") }\n${indent(d)}}`; } public path(path: ast.Path): string { return path.segments .map((seg) => seg.ident.text) .join("::"); } public ty(ty: Ty): string { return tyToString(this.ctx, ty); } } function indent(depth: number): string { return " ".repeat(depth); }