import { opToString } from "./arch.ts"; export type Line = { labels?: string[]; ins: Ins }; export type Ins = Lit[]; export type Label = { label: string }; export type Lit = number | string | boolean | Label; export type Locs = { [key: string]: number }; export type Refs = { [key: number]: string }; export class Assembler { private lines: Line[] = []; private addedLabels: string[] = []; private constructor(private labelCounter: number) {} public static newRoot(): Assembler { return new Assembler(0); } public fork(): Assembler { return new Assembler(this.labelCounter); } public join(assembler: Assembler) { this.labelCounter = assembler.labelCounter; if (assembler.lines.length < 0) { return; } if (assembler.lines[0].labels !== undefined) { this.addedLabels.push(...assembler.lines[0].labels); } this.add(...assembler.lines[0].ins); this.lines.push(...assembler.lines.slice(1)); } public add(...ins: Ins): Assembler { 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: `.L${(this.labelCounter++).toString()}` }; } public setLabel({ label }: Label) { this.addedLabels.push(label); } public assemble(): { program: number[]; locs: Locs } { let ip = 0; const program: number[] = []; const locs: Locs = {}; const refs: Refs = {}; let selectedLabel = ""; for (const line of this.lines) { for (const label of line.labels ?? []) { const isAbsLabel = !label.startsWith("."); if (isAbsLabel) { selectedLabel = label; locs[label] = ip; } else { locs[`${selectedLabel}${label}`] = ip; } } for (const lit of line.ins as Lit[]) { if (typeof lit === "number") { program.push(lit); ip += 1; } else if (typeof lit === "boolean") { program.push(lit ? 1 : 0); ip += 1; } else if (typeof lit === "string") { program.push(lit.length); ip += 1; for (let i = 0; i < lit.length; ++i) { program.push(lit.charCodeAt(i)); ip += 1; } } else { program.push(0); refs[ip] = lit.label.startsWith(".") ? `${selectedLabel}${lit.label}` : refs[ip] = lit.label; ip += 1; } } } for (let i = 0; i < program.length; ++i) { if (!(i in refs)) { continue; } if (!(refs[i] in locs)) { console.error( `Assembler: label '${refs[i]}' used at ${i} not defined`, ); continue; } program[i] = locs[refs[i]]; } return { program, locs }; } public printProgram() { let ip = 0; for (const line of this.lines) { for (const label of line.labels ?? []) { console.log(` ${label}:`); } const op = opToString(line.ins[0] 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 lit.label; } }).join(", "); console.log(`${ip.toString().padStart(8, " ")}: ${op} ${args}`); ip += line.ins.map((lit) => typeof lit === "string" ? lit.length + 1 : 1 ).reduce((acc, curr) => acc + curr, 0); } } }