mirror of
https://github.com/Mercantec-GHC/h4-projekt-gruppe-0-sm.git
synced 2025-04-27 08:24:05 +02:00
compiler: add backup compiler
This commit is contained in:
parent
ece66bd2b8
commit
dcdfc32da6
51
backup-compiler/ast.ts
Normal file
51
backup-compiler/ast.ts
Normal 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[];
|
||||
};
|
5
backup-compiler/deno.jsonc
Normal file
5
backup-compiler/deno.jsonc
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"fmt": {
|
||||
"indentWidth": 4
|
||||
}
|
||||
}
|
11
backup-compiler/deno.lock
generated
Normal file
11
backup-compiler/deno.lock
generated
Normal 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
829
backup-compiler/front.ts
Normal 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
101
backup-compiler/lir.ts
Normal 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
259
backup-compiler/lir_gen.ts
Normal 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
34
backup-compiler/main.ts
Normal 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
143
backup-compiler/mir.ts
Normal 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
293
backup-compiler/mir_gen.ts
Normal 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));
|
||||
}
|
||||
}
|
||||
|
15
backup-compiler/program.sbl
Normal file
15
backup-compiler/program.sbl
Normal 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
26
backup-compiler/ty.ts
Normal 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}`;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user