diff --git a/gbuoy.js b/gbuoy.js index 2fa7046..2e3b77b 100755 --- a/gbuoy.js +++ b/gbuoy.js @@ -1,2 +1,2 @@ #!/usr/bin/env node -require("./build/main.js"); +require("./build/main") diff --git a/lib/asm.ts b/lib/asm.ts new file mode 100644 index 0000000..70f30d6 --- /dev/null +++ b/lib/asm.ts @@ -0,0 +1,103 @@ +import type { SSA } from "./ir" + +const reg8 = ["B", "C", "D", "E"] as const + +type R8 = typeof reg8[number] + +class ASMState { + regs: { [r in R8]: string | null } + syms: { [s: string]: R8 } + stack_offsets: { [s: string]: number } + cur_offset: number + insns: Array + + constructor() { + this.regs = { "B": null, "C": null, "D": null, "E": null } + this.syms = {} + this.stack_offsets = {} + this.cur_offset = 0 + this.insns = [] + } + + // TODO: Generalize for u16 + get_sym_loc(sym: string): R8 | number | null { + // return register if already allocated + if (typeof this.syms[sym] !== "undefined") { + return this.syms[sym] + } + + // return stack offset if already allocated + if (typeof this.stack_offsets[sym] !== "undefined") { + return this.cur_offset - this.stack_offsets[sym] + } + + return null + } + + alloc_sym_loc(sym: string): R8 | number { + const existing_loc = this.get_sym_loc(sym) + if (existing_loc !== null) { + return existing_loc + } + + // look for free register + for (const r8 of reg8) { + if (this.regs[r8] === null) { + this.regs[r8] = sym + this.syms[sym] = r8 + return r8 + } + } + + // allocate space on stack + this.cur_offset++ + this.stack_offsets[sym] = this.cur_offset + + this.insns.push("ADD SP, -1") + return this.stack_offsets[sym] + } +} + +export const convertASM = (ir: Array): Array => { + const state = new ASMState() + + ir.forEach(ssa => convertASM_SSA(state, ssa)) + return state.insns +} + +const convertASM_SSA = (state: ASMState, ssa: SSA): void => { + const dest = state.alloc_sym_loc(ssa.dest) + + if ("source1" in ssa) { // Binary + } else if ("op" in ssa) { // Unary + } else { // Copy + let source = "" + if (typeof ssa.source == "number") { + source = ssa.source.toString() + } else { + const loc = state.get_sym_loc(ssa.source) + + if (loc === null) { + state.insns.push(`LD A, (${ssa.source})`) + source = "A" + } else if (typeof loc === "string") { + source = loc + } else { + state.insns.push( + `LD HL, SP + (${loc})`, + `LD A, (HL)` + ) + source = "A" + } + } + + if (typeof dest === "string") { + state.insns.push(`LD ${dest}, ${source}`) + } else { + state.insns.push( + `LD HL, SP + (${dest})`, + `LD (HL), ${source}` + ) + } + } +} diff --git a/lib/compile.ts b/lib/compile.ts index 04c23e7..3232f02 100644 --- a/lib/compile.ts +++ b/lib/compile.ts @@ -1,5 +1,7 @@ +import { convertASM } from "./asm" +import { convertIR, prettyPrintIR } from "./ir" import { parse } from "./parser" -import { convertIR } from "./ir" + import type { Attr, Decl, Stmt, Type, VarDecl } from "./ast" type SymbolDefn = { @@ -21,31 +23,35 @@ export const compile = (source: string): string => { const decls = ast.filter((x): x is Decl => x.type == "decl") const stmts = ast.filter((x): x is Stmt => x.type == "stmt") - console.log("Declarations", decls) - console.log("Statements", stmts) - // 3. Create top-level symbol map const symbols = processDecls(decls) - console.log("Symbols", symbols) - // TODO: Some form of type-checking // 4. Generate IR const ir = convertIR(stmts) - console.log("IR", ir) + console.log("=== IR === ") + console.log(ir.map(prettyPrintIR).join("\n")) + + // 5. Generate code + const insns = convertASM(ir) + const code = insns.join("\n") - return "" + console.log("=== ASM === ") + return code } -export const processDecls = (decls: Array): SymbolMap => decls.reduce((symbols, decl) => { +export const processDecls = (decls: Array): SymbolMap => decls.reduce(processDecl, {}) + +export const processDecl = (symbols: SymbolMap, decl: Decl): SymbolMap => { const symbol = processVarDecl(symbols, decl) + return { ...symbols, [symbol.name]: symbol, } -}, {}) +} export const processVarDecl = (symbols: SymbolMap, decl: VarDecl): SymbolDefn => { if (typeof symbols[decl.name] !== 'undefined') { diff --git a/lib/ir.ts b/lib/ir.ts index 3909b7d..865de33 100644 --- a/lib/ir.ts +++ b/lib/ir.ts @@ -3,11 +3,11 @@ import type { AssignStmt, Expr, Stmt } from "./ast" // dest <- op(x, y) type Operand = string | number -type ASSA = { dest: string } & Data +type ASSA = { dest: string, source: Operand } & Data -type SSA = ASSA<{ source: Operand }> - | ASSA<{ source: Operand, op: string }> - | ASSA<{ source1: Operand, op: string, source2: Operand }> +export type SSA = ASSA<{ source: Operand }> // dest = source + | ASSA<{ op: string }> // dest = op(source) + | ASSA<{ op: string, source1: Operand }> // dest = op(source, source1) type IRState = { nextID: number @@ -44,3 +44,17 @@ export const convertExpr = (state: IRState, expr: Expr): [string, Array] => const name = `temp${state.nextID++}` return [name, [{ dest: name, source: expr }]] } + +export const prettyPrintIR = (ssa: SSA): string => { + let output = "" + + if ("source1" in ssa) { + output = `${ssa.dest} = ${ssa.source} ${ssa.op} ${ssa.source1}` + } else if ("op" in ssa) { + output = `${ssa.dest} = ${ssa.op} ${ssa.source}` + } else { + output = `${ssa.dest} = ${ssa.source}` + } + + return output +} diff --git a/package.json b/package.json index 99e8da1..a477fbb 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "lib": "lib" }, "scripts": { + "build": "mkdir -p build && peggy -o build/parser.js lib/parser.pegjs && tsc", "test": "echo \"Error: no test specified\" && exit 1" }, "author": "Forest Belton ",