Compare commits
No commits in common. "fea76133dc9decaf58b2ddbbd75000630f73658a" and "6b93bf0fc651d936bde98e95297148f99b90f451" have entirely different histories.
fea76133dc
...
6b93bf0fc6
@ -72,8 +72,6 @@ export class Node {
|
||||
return visit(k.expr);
|
||||
case "LetStmt":
|
||||
return visit(k.param, k.expr);
|
||||
case "IfStmt":
|
||||
return visit(k.cond, k.truthy, k.falsy);
|
||||
case "Param":
|
||||
return visit(k.ty);
|
||||
case "IdentExpr":
|
||||
@ -87,7 +85,7 @@ export class Node {
|
||||
case "IdentTy":
|
||||
return visit();
|
||||
}
|
||||
k satisfies never;
|
||||
const _: never = k;
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,7 +104,6 @@ export type NodeKind =
|
||||
}
|
||||
| { tag: "ReturnStmt"; expr: Node | null }
|
||||
| { tag: "LetStmt"; param: Node; expr: Node }
|
||||
| { tag: "IfStmt"; cond: Node; truthy: Node; falsy: Node | null }
|
||||
| { tag: "Param"; ident: string; ty: Node | null }
|
||||
| { tag: "IdentExpr"; ident: string }
|
||||
| { tag: "IntExpr"; value: number }
|
||||
|
||||
@ -48,8 +48,6 @@ export class Parser {
|
||||
return this.parseReturnStmt();
|
||||
} else if (this.test("let")) {
|
||||
return this.parseLetStmt();
|
||||
} else if (this.test("if")) {
|
||||
return this.parseIfStmt();
|
||||
} else {
|
||||
const place = this.parseExpr();
|
||||
if (this.eat("=")) {
|
||||
@ -107,18 +105,6 @@ export class Parser {
|
||||
return ast.Node.create(loc, "LetStmt", { param, expr });
|
||||
}
|
||||
|
||||
parseIfStmt(): ast.Node {
|
||||
const loc = this.loc();
|
||||
this.step();
|
||||
const cond = this.parseExpr();
|
||||
const truthy = this.parseBlock();
|
||||
let falsy: ast.Node | null = null;
|
||||
if (this.eat("else")) {
|
||||
falsy = this.parseBlock();
|
||||
}
|
||||
return ast.Node.create(loc, "IfStmt", { cond, truthy, falsy });
|
||||
}
|
||||
|
||||
parseParam(): ast.Node {
|
||||
const loc = this.loc();
|
||||
const ident = this.mustEat("ident").value;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import * as ast from "./ast.ts";
|
||||
import * as front from "./front/mod.ts";
|
||||
import * as middle from "./middle.ts";
|
||||
import { FnInterpreter } from "./mir_interpreter.ts";
|
||||
import { MirInterpreter } from "./mir_interpreter.ts";
|
||||
|
||||
const filename = Deno.args[0];
|
||||
const text = await Deno.readTextFile(filename);
|
||||
@ -38,4 +38,5 @@ if (!Deno.args.includes("--test")) {
|
||||
console.log(mainMiddleFn.pretty());
|
||||
}
|
||||
|
||||
new FnInterpreter(mainMiddleFn, []).eval();
|
||||
const interp = new MirInterpreter();
|
||||
interp.eval(mainMiddleFn);
|
||||
|
||||
@ -21,7 +21,7 @@ export class MiddleLowerer {
|
||||
}
|
||||
|
||||
class FnLowerer {
|
||||
private bbs: BasicBlock[] = [new BasicBlock()];
|
||||
private bbs: BasicBlock[] = [new BasicBlock([])];
|
||||
private localMap = new Map<number, Inst>();
|
||||
|
||||
constructor(
|
||||
@ -63,44 +63,6 @@ class FnLowerer {
|
||||
this.pushInst(Ty.Void, "Return", { source });
|
||||
return;
|
||||
}
|
||||
if (stmt.is("IfStmt")) {
|
||||
const cond = this.lowerExpr(stmt.kind.cond);
|
||||
const condBlock = this.bbs.at(-1)!;
|
||||
|
||||
this.bbs.push(new BasicBlock());
|
||||
const truthy = this.bbs.at(-1)!;
|
||||
this.lowerBlock(stmt.kind.truthy.as("Block"));
|
||||
const truthyEnd = this.bbs.at(-1)!;
|
||||
|
||||
let falsy: BasicBlock | null = null;
|
||||
let falsyEnd: BasicBlock | null = null;
|
||||
|
||||
if (stmt.kind.falsy) {
|
||||
this.bbs.push(new BasicBlock());
|
||||
falsy = this.bbs.at(-1)!;
|
||||
this.lowerBlock(stmt.kind.falsy.as("Block"));
|
||||
falsyEnd = this.bbs.at(-1)!;
|
||||
}
|
||||
|
||||
this.bbs.push(new BasicBlock());
|
||||
const done = this.bbs.at(-1)!;
|
||||
|
||||
condBlock.insts.push(
|
||||
new Inst(Ty.Void, {
|
||||
tag: "Branch",
|
||||
cond,
|
||||
truthy,
|
||||
falsy: falsy ?? done,
|
||||
}),
|
||||
);
|
||||
truthyEnd.insts.push(
|
||||
new Inst(Ty.Void, { tag: "Jump", target: falsy ?? done }),
|
||||
);
|
||||
falsyEnd?.insts.push(
|
||||
new Inst(Ty.Void, { tag: "Jump", target: done }),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (stmt.is("AssignStmt")) {
|
||||
const source = this.lowerExpr(stmt.kind.expr);
|
||||
const target = this.lowerAssignPlace(stmt.kind.place);
|
||||
@ -295,7 +257,9 @@ class PrettyCx {
|
||||
}
|
||||
|
||||
export class BasicBlock {
|
||||
public insts: Inst[] = [];
|
||||
constructor(
|
||||
public insts: Inst[],
|
||||
) {}
|
||||
|
||||
pretty(cx: PrettyCx): string {
|
||||
return `bb${cx.bbId(this)}:\n${
|
||||
@ -316,9 +280,7 @@ export class Inst {
|
||||
pretty(cx: PrettyCx): string {
|
||||
const r = (v: Inst) => `_${cx.regId(v)}`;
|
||||
|
||||
return `${`${r(this)}:`.padEnd(4, " ")} ${
|
||||
this.ty.pretty().padEnd(4, " ")
|
||||
} = ${this.kind.tag}${
|
||||
return `${r(this)}: ${this.ty.pretty()} = ${this.kind.tag}${
|
||||
(() => {
|
||||
const k = this.kind;
|
||||
switch (k.tag) {
|
||||
@ -340,13 +302,7 @@ export class Inst {
|
||||
case "LocalLoad":
|
||||
return ` ${r(k.source)}`;
|
||||
case "LocalStore":
|
||||
return ` ${r(k.target)} = ${r(k.source)}`;
|
||||
case "Jump":
|
||||
return ` bb${cx.bbId(k.target)}`;
|
||||
case "Branch":
|
||||
return ` ${r(k.cond)} ? bb${cx.bbId(k.truthy)} : bb${
|
||||
cx.bbId(k.falsy)
|
||||
}`;
|
||||
return ` ${r(k.target)}, ${r(k.source)}`;
|
||||
case "Return":
|
||||
return ` ${r(k.source)}`;
|
||||
case "Eq":
|
||||
@ -369,7 +325,7 @@ export class Inst {
|
||||
case "DebugPrint":
|
||||
return ` ${k.args.map(r).join(", ")}`;
|
||||
}
|
||||
k satisfies never;
|
||||
const _: never = k;
|
||||
})()
|
||||
}`;
|
||||
}
|
||||
@ -386,8 +342,6 @@ export type InstKind =
|
||||
| { tag: "AllocLocal" }
|
||||
| { tag: "LocalLoad"; source: Inst }
|
||||
| { tag: "LocalStore"; target: Inst; source: Inst }
|
||||
| { tag: "Jump"; target: BasicBlock }
|
||||
| { tag: "Branch"; cond: Inst; truthy: BasicBlock; falsy: BasicBlock }
|
||||
| { tag: "Return"; source: Inst }
|
||||
| { tag: BinaryOp; left: Inst; right: Inst }
|
||||
| { tag: "DebugPrint"; args: Inst[] };
|
||||
|
||||
@ -1,24 +1,19 @@
|
||||
import * as mir from "./middle.ts";
|
||||
|
||||
export class FnInterpreter {
|
||||
private regs = new Map<mir.Inst, Val>();
|
||||
private locals: (Val | null)[] = [];
|
||||
private localMap = new Map<mir.Inst, number>();
|
||||
private bb: mir.BasicBlock;
|
||||
private instIdx = 0;
|
||||
export class MirInterpreter {
|
||||
constructor() {}
|
||||
|
||||
constructor(
|
||||
private fn: mir.Fn,
|
||||
private args: Val[],
|
||||
) {
|
||||
this.bb = this.fn.bbs[0];
|
||||
eval(fn: mir.Fn) {
|
||||
this.evalFn(fn, []);
|
||||
}
|
||||
|
||||
eval(): Val {
|
||||
while (this.instIdx < this.bb.insts.length) {
|
||||
const inst = this.bb.insts[this.instIdx];
|
||||
this.instIdx += 1;
|
||||
private evalFn(fn: mir.Fn, args: Val[]): Val {
|
||||
const regs = new Map<mir.Inst, Val>();
|
||||
const locals: (Val | null)[] = [];
|
||||
const localMap = new Map<mir.Inst, number>();
|
||||
|
||||
let bb = fn.bbs[0];
|
||||
for (const inst of bb.insts) {
|
||||
const k = inst.kind;
|
||||
switch (k.tag) {
|
||||
case "Error":
|
||||
@ -27,61 +22,42 @@ export class FnInterpreter {
|
||||
case "Int":
|
||||
case "Bool":
|
||||
case "Fn":
|
||||
this.regs.set(inst, new Val(k));
|
||||
break;
|
||||
regs.set(inst, new Val(k));
|
||||
continue;
|
||||
case "Param":
|
||||
this.regs.set(inst, this.args[k.idx]);
|
||||
break;
|
||||
regs.set(inst, args[k.idx]);
|
||||
continue;
|
||||
case "Call": {
|
||||
const fn = this.regs.get(k.callee);
|
||||
const fn = regs.get(k.callee);
|
||||
if (!fn || fn.kind.tag !== "Fn") {
|
||||
throw new Error();
|
||||
}
|
||||
const args = k.args.map((arg) => this.regs.get(arg)!);
|
||||
const val = new FnInterpreter(fn.kind.fn, args).eval();
|
||||
this.regs.set(inst, val);
|
||||
break;
|
||||
const args = k.args.map((arg) => regs.get(arg)!);
|
||||
const val = this.evalFn(fn.kind.fn, args);
|
||||
regs.set(inst, val);
|
||||
continue;
|
||||
}
|
||||
case "AllocLocal":
|
||||
this.localMap.set(inst, this.locals.length);
|
||||
this.locals.push(null);
|
||||
break;
|
||||
localMap.set(inst, locals.length);
|
||||
locals.push(null);
|
||||
continue;
|
||||
case "LocalLoad":
|
||||
if (!this.localMap.has(k.source)) {
|
||||
if (!localMap.has(k.source)) {
|
||||
throw new Error();
|
||||
}
|
||||
if (this.locals[this.localMap.get(k.source)!] === null) {
|
||||
if (locals[localMap.get(k.source)!] === null) {
|
||||
throw new Error();
|
||||
}
|
||||
this.regs.set(
|
||||
inst,
|
||||
this.locals[this.localMap.get(k.source)!]!,
|
||||
);
|
||||
break;
|
||||
regs.set(inst, locals[localMap.get(k.source)!]!);
|
||||
continue;
|
||||
case "LocalStore":
|
||||
if (!this.localMap.has(k.target)) {
|
||||
if (!localMap.has(k.target)) {
|
||||
throw new Error();
|
||||
}
|
||||
this.locals[this.localMap.get(k.target)!] = this.regs.get(
|
||||
k.source,
|
||||
)!;
|
||||
break;
|
||||
case "Jump": {
|
||||
this.bb = k.target;
|
||||
this.instIdx = 0;
|
||||
break;
|
||||
}
|
||||
case "Branch": {
|
||||
const cond = this.regs.get(k.cond)!;
|
||||
if (cond.kind.tag !== "Bool") {
|
||||
throw new Error();
|
||||
}
|
||||
this.bb = cond.kind.value ? k.truthy : k.falsy;
|
||||
this.instIdx = 0;
|
||||
break;
|
||||
}
|
||||
locals[localMap.get(k.target)!] = regs.get(k.source)!;
|
||||
continue;
|
||||
case "Return":
|
||||
return this.regs.get(k.source)!;
|
||||
return regs.get(k.source)!;
|
||||
case "Eq":
|
||||
case "Ne":
|
||||
case "Lt":
|
||||
@ -97,77 +73,71 @@ export class FnInterpreter {
|
||||
case "Sub":
|
||||
case "Mul":
|
||||
case "Div":
|
||||
case "Rem":
|
||||
this.evalBinaryOp(inst, k);
|
||||
break;
|
||||
case "Rem": {
|
||||
const left = regs.get(k.left)!;
|
||||
const right = regs.get(k.right)!;
|
||||
const lk = left.kind;
|
||||
const rk = right.kind;
|
||||
|
||||
if (lk.tag === "Int" && rk.tag === "Int") {
|
||||
const left = lk.value;
|
||||
const right = lk.value;
|
||||
|
||||
const value = (() => {
|
||||
const Int = (value: number) =>
|
||||
new Val({ tag: "Int", value });
|
||||
const Bool = (value: boolean) =>
|
||||
new Val({ tag: "Bool", value });
|
||||
|
||||
switch (k.tag) {
|
||||
case "Eq":
|
||||
return Bool(left === right);
|
||||
case "Ne":
|
||||
return Bool(left !== right);
|
||||
case "Lt":
|
||||
return Bool(left < right);
|
||||
case "Gt":
|
||||
return Bool(left > right);
|
||||
case "Lte":
|
||||
return Bool(left <= right);
|
||||
case "Gte":
|
||||
return Bool(left >= right);
|
||||
case "BitOr":
|
||||
case "BitXor":
|
||||
case "BitAnd":
|
||||
case "Shl":
|
||||
case "Shr":
|
||||
break;
|
||||
case "Add":
|
||||
return Int(left + right);
|
||||
case "Sub":
|
||||
return Int(left - right);
|
||||
case "Mul":
|
||||
return Int(left * right);
|
||||
case "Div":
|
||||
return Int(Math.floor(left / right));
|
||||
case "Rem":
|
||||
return Int(left % right);
|
||||
}
|
||||
throw new Error(`'${k.tag}' not handled`);
|
||||
})();
|
||||
regs.set(inst, value);
|
||||
continue;
|
||||
}
|
||||
throw new Error(`'${k.tag}' not handled`);
|
||||
}
|
||||
case "DebugPrint":
|
||||
console.log(
|
||||
k.args
|
||||
.map((a) => this.regs.get(a)!.pretty())
|
||||
.map((a) => regs.get(a)!.pretty())
|
||||
.join(", "),
|
||||
);
|
||||
break;
|
||||
default:
|
||||
k satisfies never;
|
||||
continue;
|
||||
}
|
||||
k satisfies never;
|
||||
}
|
||||
return Val.Void;
|
||||
}
|
||||
|
||||
private evalBinaryOp(
|
||||
inst: mir.Inst,
|
||||
k: mir.InstKind & { tag: mir.BinaryOp },
|
||||
) {
|
||||
const left = this.regs.get(k.left)!;
|
||||
const right = this.regs.get(k.right)!;
|
||||
|
||||
if (left.kind.tag === "Int" && right.kind.tag === "Int") {
|
||||
const l = left.kind.value;
|
||||
const r = right.kind.value;
|
||||
|
||||
const value = (() => {
|
||||
const Int = (value: number) => new Val({ tag: "Int", value });
|
||||
const Bool = (value: boolean) =>
|
||||
new Val({ tag: "Bool", value });
|
||||
|
||||
switch (k.tag) {
|
||||
case "Eq":
|
||||
return Bool(l === r);
|
||||
case "Ne":
|
||||
return Bool(l !== r);
|
||||
case "Lt":
|
||||
return Bool(l < r);
|
||||
case "Gt":
|
||||
return Bool(l > r);
|
||||
case "Lte":
|
||||
return Bool(l <= r);
|
||||
case "Gte":
|
||||
return Bool(l >= r);
|
||||
case "BitOr":
|
||||
case "BitXor":
|
||||
case "BitAnd":
|
||||
case "Shl":
|
||||
case "Shr":
|
||||
break;
|
||||
case "Add":
|
||||
return Int(l + r);
|
||||
case "Sub":
|
||||
return Int(l - r);
|
||||
case "Mul":
|
||||
return Int(l * r);
|
||||
case "Div":
|
||||
return Int(Math.floor(l / r));
|
||||
case "Rem":
|
||||
return Int(l % r);
|
||||
}
|
||||
throw new Error(`'${k.tag}' not handled`);
|
||||
})();
|
||||
|
||||
this.regs.set(inst, value);
|
||||
return;
|
||||
}
|
||||
throw new Error(`'${k.tag}' not handled`);
|
||||
}
|
||||
}
|
||||
|
||||
class Val {
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
// expect: 2
|
||||
|
||||
fn main()
|
||||
{
|
||||
let a = 1;
|
||||
|
||||
if true {
|
||||
a = 2;
|
||||
}
|
||||
|
||||
print_int(a);
|
||||
}
|
||||
@ -15,7 +15,7 @@ run_test_file() {
|
||||
|
||||
echo "- $(basename $file)"
|
||||
set +e
|
||||
output=$(deno run -A --check $SRC_DIR/main.ts $file --test)
|
||||
output=$(deno run -A $SRC_DIR/main.ts $file --test)
|
||||
status=$?
|
||||
set -e
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user