From dcdfc32da6a9eedf92ab14dd2843cfcaba945372 Mon Sep 17 00:00:00 2001 From: SimonFJ20 Date: Mon, 24 Mar 2025 16:16:43 +0100 Subject: [PATCH] compiler: add backup compiler --- backup-compiler/ast.ts | 51 +++ backup-compiler/deno.jsonc | 5 + backup-compiler/deno.lock | 11 + backup-compiler/front.ts | 829 ++++++++++++++++++++++++++++++++++++ backup-compiler/lir.ts | 101 +++++ backup-compiler/lir_gen.ts | 259 +++++++++++ backup-compiler/main.ts | 34 ++ backup-compiler/mir.ts | 143 +++++++ backup-compiler/mir_gen.ts | 293 +++++++++++++ backup-compiler/program.sbl | 15 + backup-compiler/ty.ts | 26 ++ 11 files changed, 1767 insertions(+) create mode 100644 backup-compiler/ast.ts create mode 100644 backup-compiler/deno.jsonc create mode 100644 backup-compiler/deno.lock create mode 100644 backup-compiler/front.ts create mode 100644 backup-compiler/lir.ts create mode 100644 backup-compiler/lir_gen.ts create mode 100644 backup-compiler/main.ts create mode 100644 backup-compiler/mir.ts create mode 100644 backup-compiler/mir_gen.ts create mode 100644 backup-compiler/program.sbl create mode 100644 backup-compiler/ty.ts diff --git a/backup-compiler/ast.ts b/backup-compiler/ast.ts new file mode 100644 index 0000000..47d72ae --- /dev/null +++ b/backup-compiler/ast.ts @@ -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[]; +}; diff --git a/backup-compiler/deno.jsonc b/backup-compiler/deno.jsonc new file mode 100644 index 0000000..3fecf5f --- /dev/null +++ b/backup-compiler/deno.jsonc @@ -0,0 +1,5 @@ +{ + "fmt": { + "indentWidth": 4 + } +} diff --git a/backup-compiler/deno.lock b/backup-compiler/deno.lock new file mode 100644 index 0000000..e2f1c1d --- /dev/null +++ b/backup-compiler/deno.lock @@ -0,0 +1,11 @@ +{ + "version": "4", + "specifiers": { + "jsr:@std/yaml@*": "1.0.5" + }, + "jsr": { + "@std/yaml@1.0.5": { + "integrity": "71ba3d334305ee2149391931508b2c293a8490f94a337eef3a09cade1a2a2742" + } + } +} diff --git a/backup-compiler/front.ts b/backup-compiler/front.ts new file mode 100644 index 0000000..2c48bbc --- /dev/null +++ b/backup-compiler/front.ts @@ -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(); + private exprTys = new Map(); + + 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, + private exprResols: Map, + ) {} + + 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(); + + 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(); + + 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(); + + 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(); + private exprResols = new Map(); + + 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 }; + } + }); +} diff --git a/backup-compiler/lir.ts b/backup-compiler/lir.ts new file mode 100644 index 0000000..4c0b2dd --- /dev/null +++ b/backup-compiler/lir.ts @@ -0,0 +1,101 @@ +import * as mir from "./mir.ts"; + +export type Program = { + strings: Map; + 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 ""; + 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; + } +} diff --git a/backup-compiler/lir_gen.ts b/backup-compiler/lir_gen.ts new file mode 100644 index 0000000..73a8f0a --- /dev/null +++ b/backup-compiler/lir_gen.ts @@ -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(); + private stmtFns = new Map(); + + 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(); + + private currentLabels: Label[] = []; + + private nextOffset = -8; + private localOffsets = new Map(); + + public constructor( + private fn: Fn, + private strings: StringIntern, + private stmtFns: Map, + ) {} + + 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(); + + 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 { + return this.strings; + } +} diff --git a/backup-compiler/main.ts b/backup-compiler/main.ts new file mode 100644 index 0000000..565da74 --- /dev/null +++ b/backup-compiler/main.ts @@ -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(); diff --git a/backup-compiler/mir.ts b/backup-compiler/mir.ts new file mode 100644 index 0000000..7db3d3c --- /dev/null +++ b/backup-compiler/mir.ts @@ -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; + 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 ""; + 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 ""; + case "unset": + return ""; + 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; + } + } +} diff --git a/backup-compiler/mir_gen.ts b/backup-compiler/mir_gen.ts new file mode 100644 index 0000000..ccc3d98 --- /dev/null +++ b/backup-compiler/mir_gen.ts @@ -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(); + private returnLocal!: Local; + private letLocals = new Map(); + + private blockIds = 0; + private blocks: Block[] = []; + + private returnBlock!: Block; + private currentBlock!: Block; + private loopExitBlocks = new Map(); + + 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)); + } +} + diff --git a/backup-compiler/program.sbl b/backup-compiler/program.sbl new file mode 100644 index 0000000..df28955 --- /dev/null +++ b/backup-compiler/program.sbl @@ -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 + diff --git a/backup-compiler/ty.ts b/backup-compiler/ty.ts new file mode 100644 index 0000000..8c17206 --- /dev/null +++ b/backup-compiler/ty.ts @@ -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 ``; + 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}`; + } + } +}