mirror of
https://github.com/Mercantec-GHC/h4-projekt-gruppe-0-sm.git
synced 2025-04-27 16:24:07 +02:00
compiler: finish example ast lower
This commit is contained in:
parent
e8c666412c
commit
f4886e045c
@ -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<void, string> {
|
||||
@ -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<Ty, string> {
|
||||
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();
|
||||
|
@ -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`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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<AstId, Fn>();
|
||||
|
||||
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),
|
||||
|
@ -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"
|
||||
|
2
slige/compiler/middle/mod.ts
Normal file
2
slige/compiler/middle/mod.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from "./mir.ts";
|
||||
export * from "./ast_lower.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();
|
||||
}
|
||||
|
@ -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 => ({
|
||||
|
@ -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();
|
||||
}
|
||||
|
4
slige/compiler/stringify/deno.jsonc
Normal file
4
slige/compiler/stringify/deno.jsonc
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "@slige/stringify",
|
||||
"exports": "./mod.ts",
|
||||
}
|
178
slige/compiler/stringify/hir.ts
Normal file
178
slige/compiler/stringify/hir.ts
Normal file
@ -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 "<error>;";
|
||||
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 "<error>;";
|
||||
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 "<error>";
|
||||
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 "<error>";
|
||||
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 "<error>";
|
||||
case "unknown":
|
||||
return "<unknown>";
|
||||
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);
|
||||
}
|
168
slige/compiler/stringify/mir.ts
Normal file
168
slige/compiler/stringify/mir.ts
Normal file
@ -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<BlockId, number>();
|
||||
private localIds = new IdMap<LocalId, number>();
|
||||
|
||||
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 "##<error>;";
|
||||
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 "##<unset>;";
|
||||
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 "<error>";
|
||||
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)!}`;
|
||||
}
|
||||
}
|
2
slige/compiler/stringify/mod.ts
Normal file
2
slige/compiler/stringify/mod.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from "./hir.ts";
|
||||
export * from "./mir.ts";
|
Loading…
x
Reference in New Issue
Block a user