import { Builtins } from "./arch.ts"; import { BinaryType, Expr, Stmt } from "./ast.ts"; import { LocalLeaf, Locals, LocalsFnRoot } from "./lowerer_locals.ts"; import { Ops } from "./mod.ts"; import { Assembler } from "./program_builder.ts"; import { VType, vtypeToString } from "./vtype.ts"; export class Lowerer { private program = new Assembler(); private locals: Locals = new LocalsFnRoot(); private fnStmtIdAddrMap: { [key: number]: number } = {}; public lower(stmts: Stmt[]) { for (const stmt of stmts) { this.lowerStaticStmt(stmt); } } public finish(): number[] { return this.program.assemble(); } 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": } throw new Error(`unhandled static statement '${stmt.kind.type}'`); } private lowerStmt(stmt: Stmt) { switch (stmt.kind.type) { case "error": case "break": case "return": break; case "fn": return this.lowerFnStmt(stmt); case "let": return this.lowerLetStmt(stmt); case "assign": break; case "expr": this.lowerExpr(stmt.kind.expr); this.program.push(Ops.Pop); return; } throw new Error(`unhandled stmt '${stmt.kind.type}'`); } 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; this.program = new Assembler(); 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; outerProgram.concat(this.program); this.program = outerProgram; } private lowerLetStmt(stmt: Stmt) { if (stmt.kind.type !== "let") { throw new Error(); } 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)); } private lowerExpr(expr: Expr) { switch (expr.kind.type) { case "error": break; case "sym": return this.lowerSymExpr(expr); case "null": break; case "int": return this.lowerIntExpr(expr); case "bool": break; case "string": return this.lowerStringExpr(expr); case "ident": break; case "group": break; case "field": break; case "index": break; case "call": return this.lowerCallExpr(expr); case "unary": break; case "binary": return this.lowerBinaryExpr(expr); case "if": return this.lowerIfExpr(expr); case "loop": break; 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; } 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}'`); } private lowerIntExpr(expr: Expr) { if (expr.kind.type !== "int") { throw new Error(); } this.program.push(Ops.PushInt, expr.kind.value); } private lowerStringExpr(expr: Expr) { if (expr.kind.type !== "string") { throw new Error(); } this.program.push(Ops.PushString); for (let i = 0; i < expr.kind.value.length; ++i) { this.program.push(expr.kind.value.charCodeAt(i)); } } private lowerBinaryExpr(expr: Expr) { if (expr.kind.type !== "binary") { throw new Error(); } this.lowerExpr(expr.kind.left); this.lowerExpr(expr.kind.right); if (expr.vtype!.type === "int") { switch (expr.kind.binaryType) { case "+": this.program.push(Ops.Add); return; case "*": this.program.push(Ops.Multiply); return; case "==": case "-": case "/": case "!=": case "<": case ">": case "<=": case ">=": case "or": case "and": } } if (expr.vtype!.type === "string") { if (expr.kind.binaryType === "+") { this.program.push(Ops.Builtin, Builtins.StringAdd); return; } } throw new Error( `unhandled binaryType` + ` '${vtypeToString(expr.kind.left.vtype!)}'` + ` ${expr.kind.binaryType}` + ` '${vtypeToString(expr.kind.left.vtype!)}'`, ); } 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(); } const falseLabel = this.program.makeLabel(); const doneLabel = this.program.makeLabel(); this.lowerExpr(expr.kind.cond); this.program.push(Ops.Not, Ops.JumpIfTrue); this.program.addRef(falseLabel); this.lowerExpr(expr.kind.truthy); this.program.push(Ops.Jump); this.program.addRef(doneLabel); this.program.setLabel(falseLabel); this.lowerExpr(expr.kind.falsy!); this.program.setLabel(doneLabel); } 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; } }