compiler: add backup compiler

This commit is contained in:
SimonFJ20 2025-03-24 16:16:43 +01:00
parent ece66bd2b8
commit dcdfc32da6
11 changed files with 1767 additions and 0 deletions

51
backup-compiler/ast.ts Normal file
View File

@ -0,0 +1,51 @@
export type Block = {
id: number;
lineEntry: number;
lineExit: number;
stmts: Stmt[];
};
export type Stmt = {
id: number;
line: number;
kind: StmtKind;
};
export type StmtKind =
| { tag: "error" }
| { tag: "fn" } & FnStmt
| { tag: "let"; ident: string; expr?: Expr }
| { tag: "loop"; body: Block }
| { tag: "if"; expr: Expr; truthy: Block; falsy?: Block }
| { tag: "return"; expr?: Expr }
| { tag: "break" }
| { tag: "assign"; subject: Expr; expr: Expr }
| { tag: "expr"; expr: Expr };
export type FnStmt = {
ident: string;
attrs: Attr[];
params: string[];
body: Block;
};
export type Expr = {
id: number;
line: number;
kind: ExprKind;
};
export type ExprKind =
| { tag: "error" }
| { tag: "ident"; ident: string }
| { tag: "int"; val: number }
| { tag: "string"; val: string }
| { tag: "call"; expr: Expr; args: Expr[] }
| { tag: "binary"; op: BinaryOp; left: Expr; right: Expr };
export type BinaryOp = "<" | "==" | "+" | "*";
export type Attr = {
ident: string;
args: Expr[];
};

View File

@ -0,0 +1,5 @@
{
"fmt": {
"indentWidth": 4
}
}

11
backup-compiler/deno.lock generated Normal file
View File

@ -0,0 +1,11 @@
{
"version": "4",
"specifiers": {
"jsr:@std/yaml@*": "1.0.5"
},
"jsr": {
"@std/yaml@1.0.5": {
"integrity": "71ba3d334305ee2149391931508b2c293a8490f94a337eef3a09cade1a2a2742"
}
}
}

829
backup-compiler/front.ts Normal file
View File

@ -0,0 +1,829 @@
import {
Attr,
BinaryOp,
Block,
Expr,
ExprKind,
Stmt,
StmtKind,
} from "./ast.ts";
import { Ty, tyToString } from "./ty.ts";
export class Checker {
private stmtTys = new Map<number, Ty>();
private exprTys = new Map<number, Ty>();
public errorOccured = false;
public constructor(
private re: Resols,
) {}
public fnStmtTy(stmt: Stmt): Ty {
const k = stmt.kind;
if (k.tag !== "fn") {
throw new Error();
}
if (this.stmtTys.has(stmt.id)) {
return this.stmtTys.get(stmt.id)!;
}
const params = k.params.map((_): Ty => ({ tag: "int" }));
const returnTy: Ty = { tag: "int" };
const ty: Ty = { tag: "fn", stmt, params, returnTy };
this.stmtTys.set(stmt.id, ty);
return ty;
}
public paramTy(stmt: Stmt, i: number): Ty {
const ty = this.fnStmtTy(stmt);
if (ty.tag !== "fn") {
throw new Error();
}
return ty.params[i];
}
public letStmtTy(stmt: Stmt): Ty {
const k = stmt.kind;
if (k.tag !== "let") {
throw new Error();
}
if (this.stmtTys.has(stmt.id)) {
return this.stmtTys.get(stmt.id)!;
}
const ty: Ty = k.expr ? this.exprTy(k.expr) : { tag: "int" };
this.stmtTys.set(stmt.id, ty);
return ty;
}
public exprTy(expr: Expr): Ty {
if (this.exprTys.has(expr.id)) {
return this.exprTys.get(expr.id)!;
}
const ty = ((): Ty => {
const k = expr.kind;
switch (k.tag) {
case "error":
return { tag: "error" };
case "ident": {
const re = this.re.expr(expr);
if (!re) {
throw new Error();
}
switch (re.tag) {
case "fn":
return this.fnStmtTy(re.stmt);
case "param":
return this.paramTy(re.stmt, re.i);
case "let":
return this.letStmtTy(re.stmt);
case "loop":
throw new Error();
}
throw new Error();
}
case "int":
return { tag: "int" };
case "string":
return { tag: "string" };
case "call": {
const callee = this.exprTy(k.expr);
if (callee.tag !== "fn") {
this.report("call to non-function", expr.line);
return { tag: "error" };
}
if (callee.params.length !== k.args.length) {
this.report(
`argument mismatch, expected ${callee.params.length}, got ${k.args.length}`,
expr.line,
);
return { tag: "error" };
}
const args = k.args.map((arg) => this.exprTy(arg));
for (const [i, param] of callee.params.entries()) {
if (!this.assignable(args[i], param)) {
this.report(
`argument mismatch, type '${
tyToString(args[i])
}' not assignable to '${tyToString(param)}'`,
expr.line,
);
}
}
return callee.returnTy;
}
case "binary": {
const left = this.exprTy(k.left);
const right = this.exprTy(k.right);
const cfg = (op: BinaryOp, l: Ty, r: Ty = l) =>
k.op === op && this.assignable(left, l) &&
this.assignable(right, r);
if (cfg("<", { tag: "int" })) {
return { tag: "int" };
}
if (cfg("==", { tag: "int" })) {
return { tag: "int" };
}
if (cfg("+", { tag: "int" })) {
return { tag: "int" };
}
if (cfg("*", { tag: "int" })) {
return { tag: "int" };
}
this.report(
`cannot '${k.op}' type '${tyToString(left)}' with '${
tyToString(right)
}'`,
expr.line,
);
return { tag: "error" };
}
}
const _: never = k;
})();
this.exprTys.set(expr.id, ty);
return ty;
}
private assignable(a: Ty, b: Ty): boolean {
if (a.tag !== b.tag) {
return false;
}
if (a.tag === "fn" && b.tag === "fn" && a.stmt.id !== b.stmt.id) {
return false;
}
return true;
}
private report(msg: string, line: number) {
this.errorOccured = true;
//console.error(`parser: ${msg} on line ${line}`);
throw new Error(`parser: ${msg} on line ${line}`);
}
}
export type Resolve =
| { tag: "fn"; stmt: Stmt }
| { tag: "param"; stmt: Stmt; i: number }
| { tag: "let"; stmt: Stmt }
| { tag: "loop"; stmt: Stmt };
export function resolveToString(re: Resolve): string {
switch (re.tag) {
case "fn":
return `fn(id: ${re.stmt.id}, line: ${re.stmt.line})`;
case "param":
return `param(i: ${re.i})`;
case "let":
return `let(id: ${re.stmt.id}, line: ${re.stmt.line})`;
case "loop":
return `loop(id: ${re.stmt.id}, line: ${re.stmt.line})`;
}
}
export class Resols {
public constructor(
private stmtResols: Map<number, Resolve>,
private exprResols: Map<number, Resolve>,
) {}
public stmt(stmt: Stmt): Resolve | undefined {
return this.stmtResols.get(stmt.id);
}
public expr(expr: Expr): Resolve | undefined {
return this.exprResols.get(expr.id);
}
}
interface Syms {
val(ident: string): Resolve | undefined;
defineVal(ident: string, res: Resolve): void;
}
export class RootSyms implements Syms {
private exprResols = new Map<string, Resolve>();
val(ident: string): Resolve | undefined {
return this.exprResols.get(ident);
}
defineVal(ident: string, re: Resolve): void {
this.exprResols.set(ident, re);
}
}
export class FnSyms implements Syms {
private exprResols = new Map<string, Resolve>();
public constructor(
private parent: Syms,
) {}
val(ident: string): Resolve | undefined {
const local = this.exprResols.get(ident);
if (local) {
return local;
}
const parent = this.parent.val(ident);
if (!parent) {
return undefined;
}
if (parent.tag === "let") {
return undefined;
}
return parent;
}
defineVal(ident: string, re: Resolve): void {
this.exprResols.set(ident, re);
}
}
export class NormalSyms implements Syms {
private exprResols = new Map<string, Resolve>();
public constructor(
private parent: Syms,
) {}
val(ident: string): Resolve | undefined {
return this.exprResols.get(ident) ?? this.parent.val(ident);
}
defineVal(ident: string, re: Resolve): void {
this.exprResols.set(ident, re);
}
}
export class Resolver {
private syms: Syms = new RootSyms();
private stmtResols = new Map<number, Resolve>();
private exprResols = new Map<number, Resolve>();
private blockFnsStack: Stmt[][] = [];
private loopStack: Stmt[] = [];
public errorOccured = false;
public constructor(
private ast: Stmt[],
) {}
public resolve(): Resols {
this.resolveStmts(this.ast);
return new Resols(
this.stmtResols,
this.exprResols,
);
}
private resolveStmts(stmts: Stmt[]) {
this.blockFnsStack.push([]);
for (const stmt of stmts) {
this.resolveStmt(stmt);
}
const blockFns = this.blockFnsStack.pop()!;
for (const fn of blockFns) {
const outerLoops = this.loopStack;
this.loopStack = [];
const outerSyms = this.syms;
this.syms = new FnSyms(outerSyms);
const k = fn.kind;
if (k.tag !== "fn") {
throw new Error();
}
for (const [i, param] of k.params.entries()) {
this.syms.defineVal(param, { tag: "param", stmt: fn, i });
}
this.resolveBlock(k.body);
this.syms = outerSyms;
this.loopStack = outerLoops;
}
}
private resolveBlock(block: Block) {
const outerSyms = this.syms;
this.syms = new NormalSyms(outerSyms);
this.resolveStmts(block.stmts);
this.syms = outerSyms;
}
private resolveStmt(stmt: Stmt) {
const k = stmt.kind;
switch (k.tag) {
case "error":
return;
case "fn":
this.syms.defineVal(k.ident, { tag: "fn", stmt });
this.blockFnsStack.at(-1)!.push(stmt);
return;
case "let":
this.syms.defineVal(k.ident, { tag: "let", stmt });
k.expr && this.resolveExpr(k.expr);
return;
case "loop":
this.loopStack.push(stmt);
this.resolveBlock(k.body);
this.loopStack.pop();
return;
case "if":
this.resolveExpr(k.expr);
this.resolveBlock(k.truthy);
k.falsy && this.resolveBlock(k.falsy);
return;
case "return":
k.expr && this.resolveExpr(k.expr);
return;
case "break": {
const loop = this.loopStack.at(-1);
if (!loop) {
return this.report("break outside loop", stmt.line);
}
this.stmtResols.set(stmt.id, { tag: "loop", stmt: loop });
return;
}
case "assign":
this.resolveExpr(k.subject);
this.resolveExpr(k.expr);
return;
case "expr":
this.resolveExpr(k.expr);
return;
}
const _: never = k;
}
private resolveExpr(expr: Expr) {
const k = expr.kind;
switch (k.tag) {
case "error":
return;
case "ident": {
const re = this.syms.val(k.ident);
if (!re) {
this.report(`ident '${k.ident}' not defined`, expr.line);
return;
}
this.exprResols.set(expr.id, re);
return;
}
case "int":
return;
case "string":
return;
case "call":
this.resolveExpr(k.expr);
for (const arg of k.args) {
this.resolveExpr(arg);
}
return;
case "binary":
this.resolveExpr(k.left);
this.resolveExpr(k.right);
return;
}
const _: never = k;
}
private report(msg: string, line: number) {
this.errorOccured = true;
//console.error(`parser: ${msg} on line ${line}`);
throw new Error(`parser: ${msg} on line ${line}`);
}
}
export class Parser {
private toks: Tok[];
private i = 0;
private blockIds = 0;
private stmtIds = 0;
private exprIds = 0;
private last: Tok;
private eaten?: Tok;
public errorOccured = false;
public constructor(private text: string) {
this.toks = lex(this.text);
this.last = this.toks[0];
}
public parse() {
return this.parseStmts();
}
private parseStmts(): Stmt[] {
const stmts: Stmt[] = [];
while (!this.done()) {
stmts.push(this.parseStmt());
}
return stmts;
}
private parseBlock(): Block {
const lineEntry = this.curr().line;
this.step();
const stmts: Stmt[] = [];
if (!this.done() && !this.test("}")) {
stmts.push(this.parseStmt());
while (!this.done() && !this.test("}")) {
stmts.push(this.parseStmt());
}
}
const id = this.blockIds++;
if (!this.eat("}")) {
this.report("expected '}'");
return { id, lineEntry, lineExit: 0, stmts: [] };
}
const lineExit = this.eaten!.line;
return { id, lineEntry, lineExit, stmts };
}
private parseStmt(): Stmt {
const attrs = this.parseAttrs();
if (this.test("fn")) {
return this.parseFnStmt(attrs);
} else if (this.test("let")) {
return this.parseLetStmt();
} else if (this.test("loop")) {
return this.parseLoopStmt();
} else if (this.test("if")) {
return this.parseIfStmt();
} else if (this.test("return")) {
return this.parseReturnStmt();
} else if (this.test("break")) {
return this.parseBreakStmt();
} else {
const subject = this.parseExpr();
let stmt: Stmt;
if (this.eat("=")) {
const expr = this.parseExpr();
stmt = this.stmt(
{ tag: "assign", subject, expr },
subject.line,
);
} else {
stmt = this.stmt({ tag: "expr", expr: subject }, subject.line);
}
if (!this.eat(";")) {
this.report("expected ';'");
return this.stmt({ tag: "error" }, stmt.line);
}
return stmt;
}
}
private parseAttrs(): Attr[] {
const attrs: Attr[] = [];
while (this.eat("#")) {
if (!this.eat("[")) {
this.report("expected '['");
return attrs;
}
if (!this.eat("ident")) {
this.report("expected 'ident'");
return attrs;
}
const ident = this.eaten!.identVal!;
const args: Expr[] = [];
if (this.eat("(")) {
if (!this.done() && !this.test(")")) {
args.push(this.parseExpr());
while (!this.done() && !this.test(")")) {
if (!this.eat(",")) {
this.report("expected ','");
return attrs;
}
if (this.test(")")) {
break;
}
args.push(this.parseExpr());
}
}
if (!this.eat(")")) {
this.report("expected ')'");
return attrs;
}
}
if (!this.eat("]")) {
this.report("expected ']'");
return attrs;
}
attrs.push({ ident, args });
}
return attrs;
}
private parseFnStmt(attrs: Attr[]): Stmt {
const line = this.curr().line;
this.step();
if (!this.eat("ident")) {
this.report("expected 'ident'");
return this.stmt({ tag: "error" }, line);
}
const ident = this.eaten!.identVal!;
if (!this.eat("(")) {
this.report("expected '('");
return this.stmt({ tag: "error" }, line);
}
const params: string[] = [];
if (!this.done() && !this.test(")")) {
if (!this.eat("ident")) {
this.report("expected 'ident'");
return this.stmt({ tag: "error" }, line);
}
params.push(this.eaten!.identVal!);
while (!this.done() && !this.test(")")) {
if (!this.eat(",")) {
this.report("expected ','");
return this.stmt({ tag: "error" }, line);
}
if (this.test(")")) {
break;
}
if (!this.eat("ident")) {
this.report("expected 'ident'");
return this.stmt({ tag: "error" }, line);
}
params.push(this.eaten!.identVal!);
}
}
if (!this.eat(")")) {
this.report("expected ')'");
return this.stmt({ tag: "error" }, line);
}
if (!this.test("{")) {
this.report("expected block");
return this.stmt({ tag: "error" }, line);
}
const body = this.parseBlock();
return this.stmt({ tag: "fn", ident, attrs, params, body }, line);
}
private parseLetStmt(): Stmt {
const line = this.curr().line;
this.step();
if (!this.eat("ident")) {
this.report("expected 'ident'");
return this.stmt({ tag: "error" }, line);
}
const ident = this.eaten!.identVal!;
if (!this.eat("=")) {
if (!this.eat(";")) {
this.report("expected ';'");
return this.stmt({ tag: "error" }, line);
}
return this.stmt({ tag: "let", ident }, line);
}
const expr = this.parseExpr();
if (!this.eat(";")) {
this.report("expected ';'");
return this.stmt({ tag: "error" }, line);
}
return this.stmt({ tag: "let", ident, expr }, line);
}
private parseLoopStmt(): Stmt {
const line = this.curr().line;
this.step();
if (!this.test("{")) {
this.report("expected block");
return this.stmt({ tag: "error" }, line);
}
const body = this.parseBlock();
return this.stmt({ tag: "loop", body }, line);
}
private parseIfStmt(): Stmt {
const line = this.curr().line;
this.step();
const expr = this.parseExpr();
if (!this.test("{")) {
this.report("expected block");
return this.stmt({ tag: "error" }, line);
}
const truthy = this.parseBlock();
if (!this.eat("else")) {
return this.stmt({ tag: "if", expr, truthy }, line);
}
if (!this.test("{")) {
this.report("expected block");
return this.stmt({ tag: "error" }, line);
}
const falsy = this.parseBlock();
return this.stmt({ tag: "if", expr, truthy, falsy }, line);
}
private parseReturnStmt(): Stmt {
const line = this.curr().line;
this.step();
if (this.eat(";")) {
return this.stmt({ tag: "return" }, line);
}
const expr = this.parseExpr();
if (!this.eat(";")) {
this.report("expected ';'");
return this.stmt({ tag: "error" }, line);
}
return this.stmt({ tag: "return", expr }, line);
}
private parseBreakStmt(): Stmt {
const line = this.curr().line;
this.step();
if (!this.eat(";")) {
this.report("expected ';'");
return this.stmt({ tag: "error" }, line);
}
return this.stmt({ tag: "break" }, line);
}
private parseExpr(): Expr {
return this.parseBinaryExpr();
}
private parseBinaryExpr(prec = 4): Expr {
if (prec == 0) {
return this.parsePostfixExpr();
}
const ops: [BinaryOp, number][] = [
["<", 4],
["==", 3],
["+", 2],
["*", 1],
];
let left = this.parseBinaryExpr(prec - 1);
let should_continue = true;
while (should_continue) {
should_continue = false;
for (const [op, p] of ops) {
if (prec >= p && this.eat(op)) {
const right = this.parseBinaryExpr(prec - 1);
left = this.expr(
{ tag: "binary", op, left, right },
left.line,
);
should_continue = true;
break;
}
}
}
return left;
}
private parsePostfixExpr(): Expr {
let expr = this.parseOperandExpr();
while (true) {
if (this.eat("(")) {
const args: Expr[] = [];
if (!this.done() && !this.test(")")) {
args.push(this.parseExpr());
while (!this.done() && !this.test(")")) {
if (!this.eat(",")) {
this.report("expected ','");
return this.expr({ tag: "error" }, this.last.line);
}
if (this.test(")")) {
break;
}
args.push(this.parseExpr());
}
}
if (!this.eat(")")) {
this.report("expected ')'");
return this.expr({ tag: "error" }, this.last.line);
}
expr = this.expr({ tag: "call", expr, args }, expr.line);
} else {
break;
}
}
return expr;
}
private parseOperandExpr(): Expr {
if (this.eat("ident")) {
return this.expr(
{ tag: "ident", ident: this.eaten!.identVal! },
this.eaten!.line,
);
} else if (this.eat("int")) {
return this.expr(
{ tag: "int", val: this.eaten!.intVal! },
this.eaten!.line,
);
} else if (this.eat("string")) {
return this.expr(
{ tag: "string", val: this.eaten?.stringVal! },
this.eaten!.line,
);
} else {
this.report("expected expr");
return this.expr({ tag: "error" }, this.last!.line);
}
}
private stmt(kind: StmtKind, line: number): Stmt {
const id = this.stmtIds++;
return { id, line, kind };
}
private expr(kind: ExprKind, line: number): Expr {
const id = this.exprIds++;
return { id, line, kind };
}
private eat(type: string): boolean {
if (this.test(type)) {
this.eaten = this.curr();
this.step();
return true;
}
return false;
}
private step() {
this.i += 1;
if (!this.done()) {
this.last = this.curr();
}
}
private test(type: string) {
return !this.done() && this.curr().type === type;
}
private curr(): Tok {
return this.toks[this.i];
}
private done(): boolean {
return this.i >= this.toks.length;
}
private report(msg: string, line = this.last.line) {
this.errorOccured = true;
//console.error(`parser: ${msg} on line ${line}`);
throw new Error(`parser: ${msg} on line ${line}`);
}
}
export type Tok = {
type: string;
line: number;
intVal?: number;
stringVal?: string;
identVal?: string;
};
export function lex(text: string): Tok[] {
const ops = "(){}[]<>+*=,;#\n";
const kws = ["let", "fn", "return", "if", "else", "loop", "break"];
return ops
.split("")
.reduce((text, op) =>
text
.replaceAll(/\/\/.*?$/mg, "")
.replaceAll(op, ` ${op} `)
.replaceAll(" = = ", " == ")
.replaceAll(/\\ /g, "\\SPACE"), text)
.split(/[ \t\r]/)
.filter((val) => val !== "")
.reduce<[[string, number][], number]>(
([toks, line], tok) =>
[
[...toks, [tok, line]],
tok === "\n" ? line + 1 : line,
] as const,
[[], 1],
)[0]
.filter(([val, _line]) => val !== "\n")
.map(([val, line]): Tok => {
if (/^[0-9]+$/.test(val)) {
return { type: "int", line, intVal: parseInt(val) };
} else if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(val)) {
return kws.includes(val)
? { type: val, line }
: { type: "ident", line, identVal: val };
} else if (/^".*?"$/.test(val)) {
return {
type: "string",
line,
stringVal: val
.slice(1, val.length - 1)
.replace(/\\SPACE/g, " ")
.replace(/\\n/g, "\n")
.replace(/\\/g, ""),
};
} else {
return { type: val, line };
}
});
}

101
backup-compiler/lir.ts Normal file
View File

@ -0,0 +1,101 @@
import * as mir from "./mir.ts";
export type Program = {
strings: Map<number, string>;
fns: Fn[];
};
export type Fn = {
id: number;
label: string;
mir: mir.Fn;
lines: Line[];
};
export type Line = {
labels: Label[];
ins: Ins;
};
export type Ins =
| { tag: "error" }
| { tag: "nop" }
| { tag: "mov_int"; reg: Reg; val: number }
| { tag: "mov_string"; reg: Reg; stringId: number }
| { tag: "mov_fn"; reg: Reg; fn: Fn }
| { tag: "push"; reg: Reg }
| { tag: "pop"; reg: Reg }
| { tag: "load"; reg: Reg; offset: number }
| { tag: "store"; offset: number; reg: Reg }
| { tag: "call_reg"; reg: Reg }
| { tag: "call_fn"; fn: Fn }
| { tag: "jmp"; target: Label }
| { tag: "jnz_reg"; reg: Reg; target: Label }
| { tag: "ret" }
| { tag: "lt" | "eq" | "add" | "mul"; dst: Reg; src: Reg };
export type Reg = number;
export type Label = number;
export class ProgramStringifyer {
public constructor(
private program: Program,
) {}
public stringify(): string {
return this.program.fns
.map((fn) =>
`${fn.label}:\n${
fn.lines
.map((label) =>
`${
label.labels
.map((label) => `.${label}:\n`)
.join()
} ${this.ins(label.ins)}\n`
)
.join("")
}`
)
.join("");
}
private ins(ins: Ins): string {
switch (ins.tag) {
case "error":
return "<error>";
case "nop":
return "nop";
case "mov_int":
return `mov_int %${ins.reg}, ${ins.val}`;
case "mov_string":
return `mov_string %${ins.reg}, string+${ins.stringId}`;
case "mov_fn":
return `mov_fn %${ins.reg}, ${ins.fn.label}`;
case "push":
return `push %${ins.reg}`;
case "pop":
return `pop %${ins.reg}`;
case "load":
return `load %${ins.reg}, ${ins.offset}`;
case "store":
return `store ${ins.offset}, %${ins.reg}`;
case "call_reg":
return `call_reg %${ins.reg}`;
case "call_fn":
return `call_fn ${ins.fn.label}`;
case "jmp":
return `jmp .b${ins.target}`;
case "jnz_reg":
return `jmp %${ins.reg}, .b${ins.target}`;
case "ret":
return "ret";
case "lt":
case "eq":
case "add":
case "mul":
return `${ins.tag} %${ins.dst}, %${ins.src}`;
}
const _: never = ins;
}
}

259
backup-compiler/lir_gen.ts Normal file
View File

@ -0,0 +1,259 @@
import {
Fn,
Ins,
Label,
Line,
Program,
ProgramStringifyer,
Reg,
} from "./lir.ts";
import { MirGen } from "./mir_gen.ts";
import * as ast from "./ast.ts";
import * as mir from "./mir.ts";
export class LirGen {
private strings = new StringIntern();
private fnIds = 0;
private fns = new Map<number, Fn>();
private stmtFns = new Map<number, Fn>();
public constructor(
private ast: ast.Stmt[],
private mirGen: MirGen,
) {}
public generate(): Program {
for (const stmt of this.ast) {
if (stmt.kind.tag !== "fn") {
throw new Error("only functions can compile top level");
}
const mir = this.mirGen.fnMir(stmt, stmt.kind);
const id = this.fnIds++;
const label = `sbc__${stmt.kind.ident}`;
const fn: Fn = { id, label, mir, lines: [] };
this.fns.set(id, fn);
this.stmtFns.set(stmt.id, fn);
}
for (const id of this.fns.keys()) {
const fn = this.fns.get(id)!;
const stmtKind = fn.mir.stmt.kind;
if (stmtKind.tag !== "fn") {
throw new Error();
}
// if (stmtKind.attrs.at(0)?.ident === "c_function") {
// const arg = stmtKind.attrs.at(0)!.args.at(0);
// if (!arg || arg.kind.tag !== "string") {
// throw new Error("incorrect args for attribute");
// }
// const label = arg.kind.val;
// new CFunctionGen(fn, label).generate();
// continue;
// }
new FnGen(
fn,
this.strings,
this.stmtFns,
).generate();
}
return {
fns: this.fns.values().toArray(),
strings: this.strings.done(),
};
}
}
class FnGen {
private regIds = 0;
private labelIds = 0;
private blockLabels = new Map<number, Label>();
private currentLabels: Label[] = [];
private nextOffset = -8;
private localOffsets = new Map<number, number>();
public constructor(
private fn: Fn,
private strings: StringIntern,
private stmtFns: Map<number, Fn>,
) {}
public generate() {
for (const block of this.fn.mir.blocks) {
const label = this.labelIds++;
this.blockLabels.set(block.id, label);
}
for (const local of this.fn.mir.locals) {
this.localOffsets.set(local.id, this.nextOffset);
this.nextOffset -= 8;
}
for (const block of this.fn.mir.blocks) {
this.currentLabels.push(this.blockLabels.get(block.id)!);
for (const stmt of block.stmts) {
this.lowerStmt(stmt);
}
this.lowerTer(block.ter);
}
if (this.currentLabels.length > 0) {
this.pushIns({ tag: "nop" });
}
}
private lowerStmt(stmt: mir.Stmt) {
const k = stmt.kind;
switch (k.tag) {
case "error":
this.pushIns({ tag: "error" });
return;
case "push": {
switch (k.val.tag) {
case "string": {
const reg = this.reg();
const stringId = this.strings.intern(k.val.val);
this.pushIns({ tag: "mov_string", reg, stringId });
this.pushIns({ tag: "push", reg });
return;
}
case "int": {
const reg = this.reg();
this.pushIns({ tag: "mov_int", reg, val: k.val.val });
this.pushIns({ tag: "push", reg });
return;
}
case "fn": {
const reg = this.reg();
this.pushIns({
tag: "mov_fn",
reg,
fn: this.stmtFns.get(k.val.stmt.id)!,
});
this.pushIns({ tag: "push", reg });
return;
}
}
const __: never = k.val;
return;
}
case "pop": {
const reg = this.reg();
this.pushIns({ tag: "pop", reg });
return;
}
case "load": {
const reg = this.reg();
const offset = this.localOffsets.get(k.local.id)!;
this.pushIns({ tag: "load", reg, offset });
this.pushIns({ tag: "push", reg });
return;
}
case "store": {
const reg = this.reg();
const offset = this.localOffsets.get(k.local.id)!;
this.pushIns({ tag: "pop", reg });
this.pushIns({ tag: "store", offset, reg });
return;
}
case "call": {
const reg = this.reg();
this.pushIns({ tag: "pop", reg });
this.pushIns({ tag: "call_reg", reg });
return;
}
case "lt":
case "eq":
case "add":
case "mul": {
const dst = this.reg();
const src = this.reg();
this.pushIns({ tag: "pop", reg: src });
this.pushIns({ tag: "pop", reg: dst });
this.pushIns({ tag: k.tag, dst, src });
this.pushIns({ tag: "push", reg: dst });
return;
}
}
const _: never = k;
}
private lowerTer(ter: mir.Ter) {
const k = ter.kind;
switch (k.tag) {
case "error":
this.pushIns({ tag: "error" });
return;
case "unset":
this.pushIns({ tag: "error" });
return;
case "return":
this.pushIns({ tag: "ret" });
return;
case "goto":
this.pushIns({
tag: "jmp",
target: this.blockLabels.get(k.target.id)!,
});
return;
case "if": {
const reg = this.reg();
this.pushIns({ tag: "pop", reg });
this.pushIns({
tag: "jnz_reg",
reg,
target: this.blockLabels.get(k.falsy.id)!,
});
this.pushIns({
tag: "jmp",
target: this.blockLabels.get(k.falsy.id)!,
});
return;
}
}
const _: never = k;
}
private pushIns(ins: Ins) {
this.fn.lines.push({ labels: this.currentLabels, ins });
this.currentLabels = [];
}
private reg(): Reg {
const reg = this.regIds++;
return reg;
}
}
class CFunctionGen {
public constructor(
private fn: Fn,
private label: string,
) {}
public generate() {
}
}
class StringIntern {
private ids = 0;
private strings = new Map<number, string>();
public intern(value: string): number {
const entry = this.strings
.entries()
.find(([_id, v]) => v === value);
if (entry) {
return entry[0];
}
const id = this.ids++;
this.strings.set(id, value);
return id;
}
public done(): Map<number, string> {
return this.strings;
}
}

34
backup-compiler/main.ts Normal file
View File

@ -0,0 +1,34 @@
import * as yaml from "jsr:@std/yaml";
import { Checker, Parser, Resolver } from "./front.ts";
import { MirGen } from "./mir_gen.ts";
import { FnStringifyer } from "./mir.ts";
import { LirGen } from "./lir_gen.ts";
import { ProgramStringifyer } from "./lir.ts";
async function main() {
const text = await Deno.readTextFile(Deno.args[0]);
const ast = new Parser(text).parse();
console.log("=== AST ===");
console.log(yaml.stringify(ast));
const re = new Resolver(ast).resolve();
const ch = new Checker(re);
const mirGen = new MirGen(re, ch);
console.log("=== MIR ===");
for (const stmt of ast) {
if (stmt.kind.tag !== "fn") {
throw new Error("only functions can compile top level");
}
const fnMir = mirGen.fnMir(stmt, stmt.kind);
console.log(new FnStringifyer(fnMir).stringify());
}
const lir = new LirGen(ast, mirGen).generate();
console.log("=== LIR ===");
console.log(new ProgramStringifyer(lir).stringify());
}
main();

143
backup-compiler/mir.ts Normal file
View File

@ -0,0 +1,143 @@
import { Ty, tyToString } from "./ty.ts";
import * as ast from "./ast.ts";
export type Fn = {
stmt: ast.Stmt;
locals: Local[];
paramLocals: Map<number, Local>;
returnLocal: Local;
blocks: Block[];
entry: Block;
exit: Block;
};
export type Block = {
id: number;
stmts: Stmt[];
ter: Ter;
};
export type Local = {
id: number;
ty: Ty;
ident?: string;
stmt?: ast.Stmt;
};
export type Stmt = {
kind: StmtKind;
};
export const Stmt = (kind: StmtKind): Stmt => ({ kind });
export type StmtKind =
| { tag: "error" }
| { tag: "push"; val: Val; ty: Ty }
| { tag: "pop" }
| { tag: "load"; local: Local }
| { tag: "store"; local: Local }
| { tag: "call"; args: number }
| { tag: "lt" | "eq" | "add" | "mul" };
export type Ter = {
kind: TerKind;
};
export const Ter = (kind: TerKind): Ter => ({ kind });
export type TerKind =
| { tag: "error" }
| { tag: "unset" }
| { tag: "return" }
| { tag: "goto"; target: Block }
| {
tag: "if";
truthy: Block;
falsy: Block;
};
export type Val =
| { tag: "int"; val: number }
| { tag: "string"; val: string }
| { tag: "fn"; stmt: ast.Stmt };
export class FnStringifyer {
public constructor(
private fn: Fn,
) {}
public stringify(): string {
const kind = this.fn.stmt.kind;
if (kind.tag !== "fn") {
throw new Error();
}
return `${kind.ident}:\n${
this.fn.locals
.map((local) => ` %${local.id}: ${tyToString(local.ty)}\n`)
.join("")
}${
this.fn.blocks
.map((block) =>
` .b${block.id}:\n${
block.stmts
.map((stmt) => ` ${this.stmt(stmt)}\n`)
.join("")
} ${this.ter(block.ter)}\n`
)
.join("")
}`;
}
private stmt(stmt: Stmt): string {
const k = stmt.kind;
switch (k.tag) {
case "error":
return "<error>";
case "push":
return `push (${tyToString(k.ty)}) ${this.val(k.val)}`;
case "pop":
return "pop";
case "load":
return `load %${k.local.id}`;
case "store":
return `store %${k.local.id}`;
case "call":
return `call ${k.args}`;
case "lt":
case "eq":
case "add":
case "mul":
return k.tag;
}
}
private ter(ter: Ter): string {
const k = ter.kind;
switch (k.tag) {
case "error":
return "<error>";
case "unset":
return "<unset>";
case "return":
return "return";
case "goto":
return `goto .b${k.target.id}`;
case "if":
return `goto .b${k.truthy.id}, .b${k.falsy.id}`;
}
}
private val(val: Val): string {
switch (val.tag) {
case "string":
return JSON.stringify(val.val);
case "int":
return `${val.val}`;
case "fn":
if (val.stmt.kind.tag !== "fn") {
throw new Error();
}
return val.stmt.kind.ident;
}
}
}

293
backup-compiler/mir_gen.ts Normal file
View File

@ -0,0 +1,293 @@
import { Checker, Resols } from "./front.ts";
import * as ast from "./ast.ts";
import { Block, Fn, Local, Stmt, StmtKind, Ter, TerKind, Val } from "./mir.ts";
import { Ty } from "./ty.ts";
export class MirGen {
public constructor(
private re: Resols,
private ch: Checker,
) {}
public fnMir(stmt: ast.Stmt, stmtKind: ast.FnStmt): Fn {
return new FnMirGen(this.re, this.ch, stmt, stmtKind).generate();
}
}
export class FnMirGen {
private localIds = 0;
private locals: Local[] = [];
private paramLocals = new Map<number, Local>();
private returnLocal!: Local;
private letLocals = new Map<number, Local>();
private blockIds = 0;
private blocks: Block[] = [];
private returnBlock!: Block;
private currentBlock!: Block;
private loopExitBlocks = new Map<number, Block>();
public constructor(
private re: Resols,
private ch: Checker,
private stmt: ast.Stmt,
private stmtKind: ast.FnStmt,
) {}
public generate(): Fn {
const fnTy = this.ch.fnStmtTy(this.stmt);
if (fnTy.tag !== "fn") {
throw new Error();
}
this.returnLocal = this.local(fnTy.returnTy);
for (const [i, param] of this.stmtKind.params.entries()) {
const ty = this.ch.paramTy(this.stmt, i);
const local = this.local(ty, param);
this.paramLocals.set(i, local);
}
const entry = this.block();
const exit = this.block();
this.currentBlock = entry;
this.lowerBlock(this.stmtKind.body);
entry.ter = Ter({ tag: "goto", target: exit });
exit.ter = Ter({ tag: "return" });
return {
stmt: this.stmt,
locals: this.locals,
paramLocals: this.paramLocals,
returnLocal: this.returnLocal,
blocks: this.blocks,
entry: entry,
exit: this.returnBlock,
};
}
private lowerBlock(block: ast.Block) {
for (const stmt of block.stmts) {
this.lowerStmt(stmt);
}
}
private lowerStmt(stmt: ast.Stmt) {
const k = stmt.kind;
switch (k.tag) {
case "error":
throw new Error();
case "fn":
throw new Error("cannot lower");
case "let": {
const ty = this.ch.letStmtTy(stmt);
const local = this.local(ty);
this.letLocals.set(stmt.id, local);
if (k.expr) {
this.lowerExpr(k.expr);
this.pushStmt({ tag: "store", local });
}
return;
}
case "loop": {
const entry = this.currentBlock;
const exit = this.block();
const loop = this.block();
this.loopExitBlocks.set(stmt.id, exit);
this.currentBlock = loop;
this.lowerBlock(k.body);
entry.ter = Ter({ tag: "goto", target: loop });
loop.ter = Ter({ tag: "goto", target: exit });
this.currentBlock = exit;
return;
}
case "if": {
this.lowerExpr(k.expr);
const entry = this.currentBlock;
const exit = this.block();
const truthy = this.block();
this.currentBlock = truthy;
this.lowerBlock(k.truthy);
truthy.ter = Ter({ tag: "goto", target: exit });
let falsy = exit;
if (k.falsy) {
falsy = this.block();
this.currentBlock = falsy;
this.lowerBlock(k.falsy);
falsy.ter = Ter({ tag: "goto", target: exit });
}
entry.ter = Ter({ tag: "if", truthy, falsy });
return;
}
case "return": {
if (k.expr) {
this.lowerExpr(k.expr);
this.pushStmt({
tag: "store",
local: this.returnLocal,
});
}
this.currentBlock.ter = Ter({
tag: "goto",
target: this.returnBlock,
});
this.currentBlock = this.block();
return;
}
case "break": {
const re = this.re.stmt(stmt)!;
const target = this.loopExitBlocks.get(re!.stmt.id)!;
this.currentBlock.ter = Ter({ tag: "goto", target });
this.currentBlock = this.block();
return;
}
case "assign": {
const re = this.re.expr(k.subject)!;
let local: Local;
switch (re.tag) {
case "fn":
throw new Error("cannot assign to expression");
case "let":
local = this.letLocals.get(re.stmt.id)!;
break;
case "loop":
throw new Error("cannot assign to expression");
case "param":
local = this.paramLocals.get(re.i)!;
break;
}
this.lowerExpr(k.expr);
this.pushStmt({ tag: "store", local });
return;
}
case "expr": {
const expr = this.lowerExpr(k.expr);
void expr;
this.pushStmt({ tag: "pop" });
return;
}
}
const _: never = k;
}
private lowerExpr(expr: ast.Expr) {
const k = expr.kind;
switch (k.tag) {
case "error":
throw new Error();
case "ident": {
const re = this.re.expr(expr);
if (!re) {
throw new Error();
}
const ty = this.ch.exprTy(expr);
switch (re.tag) {
case "fn": {
this.pushStmt({
tag: "push",
val: { tag: "fn", stmt: re.stmt },
ty,
});
return;
}
case "param": {
const local = this.paramLocals.get(re.i);
if (!local) {
throw new Error();
}
this.pushStmt({ tag: "load", local });
return;
}
case "let": {
const local = this.letLocals.get(re.stmt.id);
if (!local) {
throw new Error();
}
this.pushStmt({ tag: "load", local });
return;
}
case "loop":
throw new Error();
}
const __: never = re;
return;
}
case "int": {
const ty = this.ch.exprTy(expr);
this.pushStmt({
tag: "push",
val: { tag: "int", val: k.val },
ty,
});
return;
}
case "string": {
const ty = this.ch.exprTy(expr);
this.pushStmt({
tag: "push",
val: { tag: "string", val: k.val },
ty,
});
return;
}
case "call": {
for (const arg of k.args) {
this.lowerExpr(arg);
}
this.lowerExpr(k.expr);
this.pushStmt({
tag: "call",
args: k.args.length,
});
return;
}
case "binary": {
switch (k.op) {
case "<":
this.pushStmt({ tag: "lt" });
return;
case "==":
this.pushStmt({ tag: "eq" });
return;
case "+":
this.pushStmt({ tag: "add" });
return;
case "*":
this.pushStmt({ tag: "mul" });
return;
}
const __: never = k.op;
return;
}
}
const _: never = k;
}
private local(ty: Ty, ident?: string, stmt?: ast.Stmt): Local {
const id = this.localIds++;
const local: Local = { id, ty, ident, stmt };
this.locals.push(local);
return local;
}
private block(): Block {
const id = this.blockIds++;
const block: Block = { id, stmts: [], ter: Ter({ tag: "unset" }) };
this.blocks.push(block);
return block;
}
private pushStmt(kind: StmtKind) {
this.currentBlock.stmts.push(Stmt(kind));
}
}

View File

@ -0,0 +1,15 @@
#[c_function("print_int")]
fn print_int(value) {}
// #[c_function("println")]
// fn println(value) {}
fn main() {
// println("hello\ world");
let a = 4;
print_int(a + 2);
}
// vim: syntax=rust commentstring=//\ %s

26
backup-compiler/ty.ts Normal file
View File

@ -0,0 +1,26 @@
import * as ast from "./ast.ts";
export type Ty =
| { tag: "error" }
| { tag: "int" }
| { tag: "string" }
| { tag: "fn"; stmt: ast.Stmt; params: Ty[]; returnTy: Ty };
export function tyToString(ty: Ty): string {
switch (ty.tag) {
case "error":
return `<error>`;
case "int":
return `int`;
case "string":
return `string`;
case "fn": {
const k = ty.stmt.kind as ast.StmtKind & { tag: "fn" };
const params = ty.params
.map((param, i) => `${k.params[i]}: ${tyToString(param)}`)
.join(", ");
const returnTy = tyToString(ty.returnTy);
return `fn ${k.ident}(${params}) -> ${returnTy}`;
}
}
}