import { convertIR } from "./ir/convert"
|
|
import { prettyPrintBlock } from "./ir/pretty"
|
|
import { parse } from "./parser"
|
|
import { allocateRegisters } from "./regalloc"
|
|
import { generateBlock } from "./sm83/codegen"
|
|
import { R8 } from "./sm83/cpu"
|
|
|
|
import type { Attr, Decl, Stmt, Type, VarDecl } from "./ast"
|
|
|
|
type SymbolDefn = {
|
|
attrs: Array<Attr>,
|
|
name: string,
|
|
type: Type
|
|
}
|
|
|
|
type SymbolMap = {
|
|
[name: string]: SymbolDefn
|
|
}
|
|
|
|
// TODO: Support more than one TU
|
|
export const compile = (fileName: string, source: string): string => {
|
|
// 1. Parse
|
|
const ast = parse(source)
|
|
|
|
// 2. Partition declarations and statements
|
|
const decls = ast.filter((x): x is Decl => x.type == "decl")
|
|
const stmts = ast.filter((x): x is Stmt => x.type == "stmt")
|
|
|
|
// 3. Create top-level symbol map
|
|
const symbols = processDecls(decls)
|
|
|
|
// TODO: Support declaring types other than U8/S8
|
|
const asmDecls = Object.values(symbols).map(symbol => `${symbol.name}:: DB`)
|
|
|
|
// TODO: Some form of type-checking
|
|
|
|
// 4. Generate IR
|
|
const ir = convertIR(stmts)
|
|
|
|
console.log("=== IR === ")
|
|
console.log(prettyPrintBlock(ir))
|
|
|
|
// 5. Generate code
|
|
const alloc = allocateRegisters(ir, Object.values(R8))
|
|
const insns = generateBlock(alloc, ir)
|
|
|
|
let output = ''
|
|
if (asmDecls.length > 0) {
|
|
output += `SECTION "${fileName} Data", WRAM0\n\n`
|
|
output += asmDecls.join("\n")
|
|
output += "\n\n"
|
|
}
|
|
|
|
if (insns.length > 0) {
|
|
output += `SECTION "${fileName} Code", ROM0\n\n`
|
|
output += insns.join("\n")
|
|
}
|
|
|
|
console.log("=== ASM ===")
|
|
return output
|
|
}
|
|
|
|
export const processDecls = (decls: Array<Decl>): SymbolMap => decls.reduce(processDecl, {})
|
|
|
|
export const processDecl = (symbols: SymbolMap, decl: Decl): SymbolMap => {
|
|
const symbol = processVarDecl(symbols, decl)
|
|
|
|
// Don't declare extern symbols
|
|
if (hasAttr(symbol, { name: 'extern', args: [] })) {
|
|
return symbols
|
|
}
|
|
|
|
return {
|
|
...symbols,
|
|
[symbol.name]: symbol,
|
|
}
|
|
}
|
|
|
|
export const processVarDecl = (symbols: SymbolMap, decl: VarDecl): SymbolDefn => {
|
|
if (typeof symbols[decl.name] !== 'undefined') {
|
|
throw new Error(`a variable named \`${decl.name}' is already defined`)
|
|
}
|
|
|
|
return {
|
|
attrs: decl.attrs,
|
|
name: decl.name,
|
|
type: decl.args.type,
|
|
}
|
|
}
|
|
|
|
export const hasAttr = (symbol: SymbolDefn, attr: Attr) => {
|
|
let has = false
|
|
|
|
for (let i = 0; i < symbol.attrs.length; ++i) {
|
|
has = attrEquals(symbol.attrs[i], attr)
|
|
if (has) {
|
|
break
|
|
}
|
|
}
|
|
|
|
return has
|
|
}
|
|
|
|
const attrEquals = (attr1: Attr, attr2: Attr): boolean => {
|
|
if (attr1.name !== attr2.name || attr1.args.length !== attr2.args.length) {
|
|
return false
|
|
}
|
|
|
|
for (let i = 0; i < attr1.args.length; ++i) {
|
|
if (attr1.args[i] !== attr2.args[i]) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|