2024-12-10 14:36:41 +01:00
|
|
|
import { Builtins } from "./arch.ts";
|
2024-12-10 10:39:12 +01:00
|
|
|
import { BinaryType, Expr, Stmt } from "./ast.ts";
|
2024-12-10 14:36:41 +01:00
|
|
|
import { LocalLeaf, Locals, LocalsFnRoot } from "./lowerer_locals.ts";
|
2024-12-10 09:03:03 +01:00
|
|
|
import { Ops } from "./mod.ts";
|
2024-12-10 15:45:19 +01:00
|
|
|
import { Assembler } from "./program_builder.ts";
|
2024-12-10 21:42:15 +01:00
|
|
|
import { VType, vtypeToString } from "./vtype.ts";
|
2024-12-10 09:03:03 +01:00
|
|
|
|
2024-12-10 14:36:41 +01:00
|
|
|
export class Lowerer {
|
2024-12-10 15:45:19 +01:00
|
|
|
private program = new Assembler();
|
2024-12-10 14:36:41 +01:00
|
|
|
private locals: Locals = new LocalsFnRoot();
|
|
|
|
private fnStmtIdAddrMap: { [key: number]: number } = {};
|
2024-12-10 09:03:03 +01:00
|
|
|
|
2024-12-10 14:36:41 +01:00
|
|
|
public lower(stmts: Stmt[]) {
|
|
|
|
for (const stmt of stmts) {
|
|
|
|
this.lowerStaticStmt(stmt);
|
2024-12-10 09:03:03 +01:00
|
|
|
}
|
2024-12-10 10:39:12 +01:00
|
|
|
}
|
|
|
|
|
2024-12-10 14:36:41 +01:00
|
|
|
public finish(): number[] {
|
2024-12-10 15:45:19 +01:00
|
|
|
return this.program.assemble();
|
2024-12-10 10:39:12 +01:00
|
|
|
}
|
|
|
|
|
2024-12-10 14:36:41 +01:00
|
|
|
private lowerStaticStmt(stmt: Stmt) {
|
|
|
|
switch (stmt.kind.type) {
|
|
|
|
case "fn":
|
|
|
|
return this.lowerFnStmt(stmt);
|
|
|
|
case "error":
|
|
|
|
case "break":
|
|
|
|
case "return":
|
|
|
|
case "let":
|
|
|
|
case "assign":
|
|
|
|
case "expr":
|
2024-12-10 09:03:03 +01:00
|
|
|
}
|
2024-12-10 14:36:41 +01:00
|
|
|
throw new Error(`unhandled static statement '${stmt.kind.type}'`);
|
2024-12-10 09:03:03 +01:00
|
|
|
}
|
|
|
|
|
2024-12-10 14:36:41 +01:00
|
|
|
private lowerStmt(stmt: Stmt) {
|
2024-12-10 09:03:03 +01:00
|
|
|
switch (stmt.kind.type) {
|
|
|
|
case "error":
|
|
|
|
case "break":
|
|
|
|
case "return":
|
|
|
|
break;
|
2024-12-10 14:36:41 +01:00
|
|
|
case "fn":
|
|
|
|
return this.lowerFnStmt(stmt);
|
2024-12-10 09:03:03 +01:00
|
|
|
case "let":
|
|
|
|
return this.lowerLetStmt(stmt);
|
|
|
|
case "assign":
|
2024-12-10 14:36:41 +01:00
|
|
|
break;
|
2024-12-10 09:03:03 +01:00
|
|
|
case "expr":
|
2024-12-10 14:36:41 +01:00
|
|
|
this.lowerExpr(stmt.kind.expr);
|
|
|
|
this.program.push(Ops.Pop);
|
|
|
|
return;
|
2024-12-10 10:39:12 +01:00
|
|
|
}
|
2024-12-10 14:36:41 +01:00
|
|
|
throw new Error(`unhandled stmt '${stmt.kind.type}'`);
|
2024-12-10 09:03:03 +01:00
|
|
|
}
|
|
|
|
|
2024-12-10 14:36:41 +01:00
|
|
|
private lowerFnStmt(stmt: Stmt) {
|
|
|
|
if (stmt.kind.type !== "fn") {
|
|
|
|
throw new Error();
|
|
|
|
}
|
|
|
|
const outerLocals = this.locals;
|
|
|
|
this.locals = new LocalsFnRoot(outerLocals);
|
|
|
|
const outerProgram = this.program;
|
2024-12-10 15:45:19 +01:00
|
|
|
this.program = new Assembler();
|
2024-12-10 14:36:41 +01:00
|
|
|
|
|
|
|
for (const { ident } of stmt.kind.params) {
|
|
|
|
this.locals.allocSym(ident);
|
|
|
|
this.program.push(
|
|
|
|
Ops.StoreLocal,
|
|
|
|
this.locals.symId(ident),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
this.lowerExpr(stmt.kind.body);
|
|
|
|
this.program.push(Ops.Return);
|
|
|
|
|
|
|
|
this.locals = outerLocals;
|
2024-12-10 15:45:19 +01:00
|
|
|
outerProgram.concat(this.program);
|
2024-12-10 14:36:41 +01:00
|
|
|
this.program = outerProgram;
|
|
|
|
}
|
|
|
|
|
|
|
|
private lowerLetStmt(stmt: Stmt) {
|
2024-12-10 09:03:03 +01:00
|
|
|
if (stmt.kind.type !== "let") {
|
|
|
|
throw new Error();
|
|
|
|
}
|
2024-12-10 10:39:12 +01:00
|
|
|
this.lowerExpr(stmt.kind.value);
|
|
|
|
this.locals.allocSym(stmt.kind.param.ident),
|
|
|
|
this.program.push(Ops.StoreLocal);
|
|
|
|
this.program.push(this.locals.symId(stmt.kind.param.ident));
|
2024-12-10 09:03:03 +01:00
|
|
|
}
|
|
|
|
|
2024-12-10 14:36:41 +01:00
|
|
|
private lowerExpr(expr: Expr) {
|
2024-12-10 09:03:03 +01:00
|
|
|
switch (expr.kind.type) {
|
|
|
|
case "error":
|
|
|
|
break;
|
2024-12-10 14:36:41 +01:00
|
|
|
case "sym":
|
|
|
|
return this.lowerSymExpr(expr);
|
|
|
|
case "null":
|
|
|
|
break;
|
2024-12-10 09:03:03 +01:00
|
|
|
case "int":
|
2024-12-10 14:36:41 +01:00
|
|
|
return this.lowerIntExpr(expr);
|
|
|
|
case "bool":
|
|
|
|
break;
|
|
|
|
case "string":
|
|
|
|
return this.lowerStringExpr(expr);
|
2024-12-10 09:03:03 +01:00
|
|
|
case "ident":
|
2024-12-10 14:36:41 +01:00
|
|
|
break;
|
2024-12-10 09:03:03 +01:00
|
|
|
case "group":
|
2024-12-10 14:36:41 +01:00
|
|
|
break;
|
2024-12-10 09:03:03 +01:00
|
|
|
case "field":
|
2024-12-10 14:36:41 +01:00
|
|
|
break;
|
2024-12-10 09:03:03 +01:00
|
|
|
case "index":
|
2024-12-10 14:36:41 +01:00
|
|
|
break;
|
2024-12-10 09:03:03 +01:00
|
|
|
case "call":
|
2024-12-10 14:36:41 +01:00
|
|
|
return this.lowerCallExpr(expr);
|
2024-12-10 09:03:03 +01:00
|
|
|
case "unary":
|
|
|
|
break;
|
|
|
|
case "binary":
|
2024-12-10 10:39:12 +01:00
|
|
|
return this.lowerBinaryExpr(expr);
|
2024-12-10 09:03:03 +01:00
|
|
|
case "if":
|
2024-12-10 14:36:41 +01:00
|
|
|
return this.lowerIfExpr(expr);
|
2024-12-10 09:03:03 +01:00
|
|
|
case "loop":
|
|
|
|
break;
|
2024-12-10 14:36:41 +01:00
|
|
|
case "block":
|
|
|
|
return this.lowerBlockExpr(expr);
|
|
|
|
}
|
|
|
|
throw new Error(`unhandled expr '${expr.kind.type}'`);
|
|
|
|
}
|
|
|
|
|
|
|
|
private lowerSymExpr(expr: Expr) {
|
|
|
|
if (expr.kind.type !== "sym") {
|
|
|
|
throw new Error();
|
|
|
|
}
|
|
|
|
if (expr.kind.sym.type === "let") {
|
|
|
|
this.program.push(
|
|
|
|
Ops.LoadLocal,
|
|
|
|
this.locals.symId(expr.kind.ident),
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (expr.kind.sym.type === "fn_param") {
|
|
|
|
this.program.push(
|
|
|
|
Ops.LoadLocal,
|
|
|
|
this.locals.symId(expr.kind.ident),
|
|
|
|
);
|
|
|
|
return;
|
2024-12-10 09:03:03 +01:00
|
|
|
}
|
2024-12-10 14:36:41 +01:00
|
|
|
if (expr.kind.sym.type === "fn") {
|
|
|
|
const addr = this.fnStmtIdAddrMap[expr.kind.sym.stmt.id];
|
|
|
|
this.program.push(Ops.PushPtr, addr);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
throw new Error(`unhandled sym type '${expr.kind.sym.type}'`);
|
2024-12-10 09:03:03 +01:00
|
|
|
}
|
|
|
|
|
2024-12-10 14:36:41 +01:00
|
|
|
private lowerIntExpr(expr: Expr) {
|
2024-12-10 09:03:03 +01:00
|
|
|
if (expr.kind.type !== "int") {
|
|
|
|
throw new Error();
|
|
|
|
}
|
2024-12-10 14:36:41 +01:00
|
|
|
this.program.push(Ops.PushInt, expr.kind.value);
|
2024-12-10 09:03:03 +01:00
|
|
|
}
|
|
|
|
|
2024-12-10 14:36:41 +01:00
|
|
|
private lowerStringExpr(expr: Expr) {
|
|
|
|
if (expr.kind.type !== "string") {
|
2024-12-10 09:03:03 +01:00
|
|
|
throw new Error();
|
|
|
|
}
|
2024-12-10 14:36:41 +01:00
|
|
|
this.program.push(Ops.PushString);
|
|
|
|
for (let i = 0; i < expr.kind.value.length; ++i) {
|
|
|
|
this.program.push(expr.kind.value.charCodeAt(i));
|
2024-12-10 09:03:03 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-10 14:36:41 +01:00
|
|
|
private lowerBinaryExpr(expr: Expr) {
|
2024-12-10 09:03:03 +01:00
|
|
|
if (expr.kind.type !== "binary") {
|
|
|
|
throw new Error();
|
|
|
|
}
|
|
|
|
this.lowerExpr(expr.kind.left);
|
|
|
|
this.lowerExpr(expr.kind.right);
|
2024-12-10 10:39:12 +01:00
|
|
|
if (expr.vtype!.type === "int") {
|
2024-12-10 09:03:03 +01:00
|
|
|
switch (expr.kind.binaryType) {
|
|
|
|
case "+":
|
|
|
|
this.program.push(Ops.Add);
|
2024-12-10 10:39:12 +01:00
|
|
|
return;
|
2024-12-10 09:03:03 +01:00
|
|
|
case "*":
|
|
|
|
this.program.push(Ops.Multiply);
|
2024-12-10 10:39:12 +01:00
|
|
|
return;
|
2024-12-10 09:03:03 +01:00
|
|
|
case "==":
|
|
|
|
case "-":
|
|
|
|
case "/":
|
|
|
|
case "!=":
|
|
|
|
case "<":
|
|
|
|
case ">":
|
|
|
|
case "<=":
|
|
|
|
case ">=":
|
|
|
|
case "or":
|
|
|
|
case "and":
|
|
|
|
}
|
|
|
|
}
|
2024-12-10 14:36:41 +01:00
|
|
|
if (expr.vtype!.type === "string") {
|
|
|
|
if (expr.kind.binaryType === "+") {
|
|
|
|
this.program.push(Ops.Builtin, Builtins.StringAdd);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2024-12-10 10:39:12 +01:00
|
|
|
throw new Error(
|
2024-12-10 14:36:41 +01:00
|
|
|
`unhandled binaryType` +
|
|
|
|
` '${vtypeToString(expr.kind.left.vtype!)}'` +
|
|
|
|
` ${expr.kind.binaryType}` +
|
|
|
|
` '${vtypeToString(expr.kind.left.vtype!)}'`,
|
2024-12-10 10:39:12 +01:00
|
|
|
);
|
2024-12-10 09:03:03 +01:00
|
|
|
}
|
2024-12-10 14:36:41 +01:00
|
|
|
|
|
|
|
private lowerCallExpr(expr: Expr) {
|
|
|
|
if (expr.kind.type !== "call") {
|
|
|
|
throw new Error();
|
|
|
|
}
|
|
|
|
for (const arg of expr.kind.args) {
|
|
|
|
this.lowerExpr(arg);
|
|
|
|
}
|
|
|
|
this.lowerExpr(expr.kind.subject);
|
|
|
|
}
|
|
|
|
|
|
|
|
private lowerIfExpr(expr: Expr) {
|
|
|
|
if (expr.kind.type !== "if") {
|
|
|
|
throw new Error();
|
|
|
|
}
|
2024-12-10 15:45:19 +01:00
|
|
|
|
|
|
|
const falseLabel = this.program.makeLabel();
|
|
|
|
const doneLabel = this.program.makeLabel();
|
|
|
|
|
2024-12-10 14:36:41 +01:00
|
|
|
this.lowerExpr(expr.kind.cond);
|
2024-12-10 15:45:19 +01:00
|
|
|
|
|
|
|
this.program.push(Ops.Not, Ops.JumpIfTrue);
|
|
|
|
this.program.addRef(falseLabel);
|
|
|
|
|
2024-12-10 14:36:41 +01:00
|
|
|
this.lowerExpr(expr.kind.truthy);
|
2024-12-10 15:45:19 +01:00
|
|
|
|
|
|
|
this.program.push(Ops.Jump);
|
|
|
|
this.program.addRef(doneLabel);
|
|
|
|
|
|
|
|
this.program.setLabel(falseLabel);
|
|
|
|
|
|
|
|
this.lowerExpr(expr.kind.falsy!);
|
|
|
|
|
|
|
|
this.program.setLabel(doneLabel);
|
2024-12-10 14:36:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
private lowerBlockExpr(expr: Expr) {
|
|
|
|
if (expr.kind.type !== "block") {
|
|
|
|
throw new Error();
|
|
|
|
}
|
|
|
|
const outerLocals = this.locals;
|
|
|
|
this.locals = new LocalLeaf(this.locals);
|
|
|
|
for (const stmt of expr.kind.stmts) {
|
|
|
|
this.lowerStmt(stmt);
|
|
|
|
}
|
|
|
|
if (expr.kind.expr) {
|
|
|
|
this.lowerExpr(expr.kind.expr);
|
|
|
|
} else {
|
|
|
|
this.program.push(Ops.PushNull);
|
|
|
|
}
|
|
|
|
this.locals = outerLocals;
|
|
|
|
}
|
2024-12-10 10:39:12 +01:00
|
|
|
}
|