diff --git a/compiler/arch.ts b/compiler/arch.ts index ba900a9..1ab81c1 100644 --- a/compiler/arch.ts +++ b/compiler/arch.ts @@ -5,38 +5,121 @@ export type Ins = Ops | number; export type Ops = typeof Ops; export const Ops = { - Nop: 0, - PushNull: 1, - PushInt: 2, - PushBool: 3, - PushString: 4, - PushPtr: 5, - Pop: 6, - LoadLocal: 7, - StoreLocal: 8, - Call: 9, - Return: 10, - Jump: 11, - JumpIfTrue: 12, - Add: 13, - Subtract: 14, - Multiply: 15, - Divide: 16, - Remainder: 17, - Equal: 18, - LessThan: 19, - And: 20, - Or: 21, - Xor: 22, - Not: 23, - SourceMap: 24, - Builtin: 25, - ReserveStatics: 26, - LoadStatic: 27, - StoreStatic: 28, + Nop: 0x00, + PushNull: 0x01, + PushInt: 0x02, + PushBool: 0x03, + PushString: 0x04, + PushPtr: 0x05, + Pop: 0x06, + ReserveStatic: 0x07, + LoadStatic: 0x08, + StoreStatic: 0x09, + LoadLocal: 0x0a, + StoreLocal: 0x0b, + Call: 0x0c, + Return: 0x0d, + Jump: 0x0e, + JumpIfTrue: 0x0f, + Builtin: 0x10, + Add: 0x20, + Subtract: 0x21, + Multiply: 0x22, + Divide: 0x23, + Remainder: 0x24, + Equal: 0x25, + LessThan: 0x26, + And: 0x27, + Or: 0x28, + Xor: 0x29, + Not: 0x2a, + SourceMap: 0x30, } as const; export type Builtins = typeof Builtins; export const Builtins = { - StringAdd: 0, + StringConcat: 0x10, + StringEqual: 0x11, + ArraySet: 0x20, + StructSet: 0x30, } as const; + +export function opToString(op: number): string { + switch (op) { + case Ops.Nop: + return "Nop"; + case Ops.PushNull: + return "PushNull"; + case Ops.PushInt: + return "PushInt"; + case Ops.PushBool: + return "PushBool"; + case Ops.PushString: + return "PushString"; + case Ops.PushPtr: + return "PushPtr"; + case Ops.Pop: + return "Pop"; + case Ops.ReserveStatic: + return "ReserveStatic"; + case Ops.LoadStatic: + return "LoadStatic"; + case Ops.StoreStatic: + return "StoreStatic"; + case Ops.LoadLocal: + return "LoadLocal"; + case Ops.StoreLocal: + return "StoreLocal"; + case Ops.Call: + return "Call"; + case Ops.Return: + return "Return"; + case Ops.Jump: + return "Jump"; + case Ops.JumpIfTrue: + return "JumpIfTrue"; + case Ops.Builtin: + return "Builtin"; + case Ops.Add: + return "Add"; + case Ops.Subtract: + return "Subtract"; + case Ops.Multiply: + return "Multiply"; + case Ops.Divide: + return "Divide"; + case Ops.Remainder: + return "Remainder"; + case Ops.Equal: + return "Equal"; + case Ops.LessThan: + return "LessThan"; + case Ops.And: + return "And"; + case Ops.Or: + return "Or"; + case Ops.Xor: + return "Xor"; + case Ops.Not: + return "Not"; + case Ops.SourceMap: + return "SourceMap"; + default: + return ``; + } +} + +export function builtinToString(builtin: number): string { + switch (builtin) { + case Builtins.StringConcat: + return "StringConcat"; + case Builtins.StringEqual: + return "StringEqual"; + case Builtins.ArraySet: + return "ArraySet"; + case Builtins.StructSet: + return "StructSet"; + default: + return ``; + } +} diff --git a/compiler/assembler.ts b/compiler/assembler.ts index 83097e9..92b17b9 100644 --- a/compiler/assembler.ts +++ b/compiler/assembler.ts @@ -1,4 +1,4 @@ -import { Ops } from "./arch.ts"; +import { Ops, opToString } from "./arch.ts"; export type Line = { labels?: number[]; @@ -95,3 +95,101 @@ export class Assembler { return output; } } + +export type Line2 = { labels?: number[]; ins: Ins2 }; + +export type Ins2 = [Ops, ...Lit[]]; + +export type Lit = number | string | boolean | Label; + +export class Assembler2 { + private lines: Line2[] = []; + private addedLabels: number[] = []; + private labelCounter = 0; + + public add(ins: Ins2): Assembler2 { + if (this.addedLabels.length > 0) { + this.lines.push({ ins, labels: this.addedLabels }); + this.addedLabels = []; + return this; + } + this.lines.push({ ins }); + return this; + } + + public makeLabel(): Label { + return { label: this.labelCounter++ }; + } + + public setLabel({ label }: Label) { + this.addedLabels.push(label); + } + + public assemble(): number[] { + let ip = 0; + const output: number[] = []; + const locs: { [key: number]: number } = {}; + const refs: { [key: number]: number } = {}; + for (const line of this.lines) { + for (const lit of line.ins as Lit[]) { + if (typeof lit === "number") { + output.push(lit); + ip += 1; + } else if (typeof lit === "boolean") { + output.push(lit ? 1 : 0); + ip += 1; + } else if (typeof lit === "string") { + for (let i = 0; i < lit.length; ++i) { + output.push(lit.charCodeAt(i)); + ip += 1; + } + } else { + output.push(0); + refs[ip] = lit.label; + ip += 1; + } + } + } + for (let i = 0; i < output.length; ++i) { + if (!(i in refs)) { + continue; + } + if (!(refs[i] in locs)) { + console.error( + `Assembler: label '${refs[i]}' used at ${i} not defined`, + ); + continue; + } + output[i] = locs[refs[i]]; + } + return output; + } + + public printProgram() { + for (const line of this.lines) { + for (const label of line.labels ?? []) { + console.log(`.L${label}:`); + } + const op = opToString(line.ins[0] as unknown as number).padEnd( + 13, + " ", + ); + const args = (line.ins.slice(1) as Lit[]).map((lit) => { + if (typeof lit === "number") { + return lit; + } else if (typeof lit === "boolean") { + return lit.toString(); + } else if (typeof lit === "string") { + return '"' + + lit.replaceAll("\\", "\\\\").replaceAll("\0", "\\0") + .replaceAll("\n", "\\n").replaceAll("\t", "\\t") + .replaceAll("\r", "\\r") + + '"'; + } else { + return `.L${lit.label}`; + } + }).join(", "); + console.log(` ${op} ${args}`); + } + } +} diff --git a/compiler/lowerer.ts b/compiler/lowerer.ts index 85c0a3e..3a20b09 100644 --- a/compiler/lowerer.ts +++ b/compiler/lowerer.ts @@ -2,13 +2,14 @@ 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 { Assembler, Label } from "./assembler.ts"; import { VType, vtypeToString } from "./vtype.ts"; export class Lowerer { private program = new Assembler(); private locals: Locals = new LocalsFnRoot(); private fnStmtIdAddrMap: { [key: number]: number } = {}; + private breakStack: Label[] = []; public lower(stmts: Stmt[]) { for (const stmt of stmts) { @@ -37,7 +38,9 @@ export class Lowerer { private lowerStmt(stmt: Stmt) { switch (stmt.kind.type) { case "error": + break; case "break": + return this.lowerBreakStmt(stmt); case "return": break; case "fn": @@ -45,7 +48,7 @@ export class Lowerer { case "let": return this.lowerLetStmt(stmt); case "assign": - break; + return this.lowerAssignStmt(stmt); case "expr": this.lowerExpr(stmt.kind.expr); this.program.push(Ops.Pop); @@ -54,6 +57,49 @@ export class Lowerer { throw new Error(`unhandled stmt '${stmt.kind.type}'`); } + private lowerAssignStmt(stmt: Stmt) { + if (stmt.kind.type !== "assign") { + throw new Error(); + } + this.lowerExpr(stmt.kind.value); + switch (stmt.kind.subject.kind.type) { + case "field": { + this.lowerExpr(stmt.kind.subject.kind.subject); + this.addPushStringOp(stmt.kind.subject.kind.value); + this.program.addOp(Ops.Builtin); + this.program.addLit(Builtins.StructSet); + return; + } + case "index": { + this.lowerExpr(stmt.kind.subject.kind.subject); + this.lowerExpr(stmt.kind.subject.kind.value); + this.program.addOp(Ops.Builtin); + this.program.addLit(Builtins.ArraySet); + return; + } + case "sym": { + this.program.addOp(Ops.StoreLocal); + this.program.addLit( + this.locals.symId(stmt.kind.subject.kind.sym.ident), + ); + return; + } + default: + throw new Error(); + } + } + + private lowerBreakStmt(stmt: Stmt) { + if (stmt.kind.type !== "break") { + throw new Error(); + } + if (stmt.kind.expr) { + this.lowerExpr(stmt.kind.expr); + } + this.program.addOp(Ops.Jump); + this.program.addRef(this.breakStack.at(-1)!); + } + private lowerFnStmt(stmt: Stmt) { if (stmt.kind.type !== "fn") { throw new Error(); @@ -119,7 +165,7 @@ export class Lowerer { case "if": return this.lowerIfExpr(expr); case "loop": - break; + return this.lowerLoopExpr(expr); case "block": return this.lowerBlockExpr(expr); } @@ -163,9 +209,13 @@ export class Lowerer { 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)); + this.addPushStringOp(expr.kind.value); + } + + private addPushStringOp(value: string) { + this.program.addOp(Ops.PushString); + for (let i = 0; i < value.length; ++i) { + this.program.addLit(value.charCodeAt(i)); } } @@ -175,34 +225,42 @@ export class Lowerer { } this.lowerExpr(expr.kind.left); this.lowerExpr(expr.kind.right); - if (expr.vtype!.type === "int") { + const vtype = expr.kind.left.vtype!; + if (vtype.type === "int") { switch (expr.kind.binaryType) { case "+": - this.program.push(Ops.Add); + this.program.addOp(Ops.Add); return; case "*": - this.program.push(Ops.Multiply); + this.program.addOp(Ops.Multiply); return; case "==": - case "-": - case "/": - case "!=": - case "<": - case ">": - case "<=": + this.program.addOp(Ops.Equal); + return; case ">=": - case "or": - case "and": + this.program.addOp(Ops.LessThan); + this.program.addOp(Ops.Not); + return; + default: } } - if (expr.vtype!.type === "string") { + if (vtype.type === "string") { if (expr.kind.binaryType === "+") { - this.program.push(Ops.Builtin, Builtins.StringAdd); + this.program.push(Ops.Builtin, Builtins.StringConcat); + return; + } + if (expr.kind.binaryType === "==") { + this.program.push(Ops.Builtin, Builtins.StringEqual); + return; + } + if (expr.kind.binaryType === "!=") { + this.program.push(Ops.Builtin, Builtins.StringEqual, Ops.Not); return; } } throw new Error( `unhandled binaryType` + + ` '${vtypeToString(expr.vtype!)}' aka. ` + ` '${vtypeToString(expr.kind.left.vtype!)}'` + ` ${expr.kind.binaryType}` + ` '${vtypeToString(expr.kind.left.vtype!)}'`, @@ -239,11 +297,34 @@ export class Lowerer { this.program.setLabel(falseLabel); - this.lowerExpr(expr.kind.falsy!); + if (expr.kind.falsy) { + this.lowerExpr(expr.kind.falsy!); + } this.program.setLabel(doneLabel); } + private lowerLoopExpr(expr: Expr) { + if (expr.kind.type !== "loop") { + throw new Error(); + } + const contineLabel = this.program.makeLabel(); + const breakLabel = this.program.makeLabel(); + + this.breakStack.push(breakLabel); + + this.program.setLabel(contineLabel); + this.lowerExpr(expr.kind.body); + this.program.addOp(Ops.Jump); + this.program.addRef(breakLabel); + this.program.setLabel(breakLabel); + if (expr.vtype!.type === "null") { + this.program.addOp(Ops.PushNull); + } + + this.breakStack.pop(); + } + private lowerBlockExpr(expr: Expr) { if (expr.kind.type !== "block") { throw new Error(); @@ -256,8 +337,12 @@ export class Lowerer { if (expr.kind.expr) { this.lowerExpr(expr.kind.expr); } else { - this.program.push(Ops.PushNull); + this.program.addOp(Ops.PushNull); } this.locals = outerLocals; } + + public printProgram() { + this.program.printProgram(); + } } diff --git a/compiler/main.ts b/compiler/main.ts index b5e47b9..f77ae0a 100644 --- a/compiler/main.ts +++ b/compiler/main.ts @@ -15,5 +15,6 @@ new Checker().check(ast); // console.log(JSON.stringify(ast, null, 4)) const lowerer = new Lowerer(); lowerer.lower(ast); +lowerer.printProgram(); const program = lowerer.finish(); console.log(JSON.stringify(program, null, 4));