A "high-level" language for the Gameboy
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

118 lines
3.0 KiB

  1. import { BasicBlock } from "./ir/block"
  2. import { convertIR } from "./ir/convert"
  3. import { prettyPrintBlock } from "./ir/pretty"
  4. import { parse } from "./parser"
  5. import { allocateRegisters } from "./regalloc"
  6. import { realizeBlock } from "./sm83/realize"
  7. import { R8 } from "./sm83/cpu"
  8. import { allocate } from "./codegen/allocate"
  9. import type { Attr, Decl, Stmt, Type, VarDecl } from "./ast"
  10. type SymbolDefn = {
  11. attrs: Array<Attr>,
  12. name: string,
  13. type: Type
  14. }
  15. type SymbolMap = {
  16. [name: string]: SymbolDefn
  17. }
  18. // TODO: Support more than one TU
  19. export const compile = (fileName: string, source: string): string => {
  20. // 1. Parse
  21. const ast = parse(source)
  22. // 2. Partition declarations and statements
  23. const decls = ast.filter((x): x is Decl => x.type == "decl")
  24. const stmts = ast.filter((x): x is Stmt => x.type == "stmt")
  25. // 3. Create top-level symbol map
  26. const symbols = processDecls(decls)
  27. // TODO: Support declaring types other than U8/S8
  28. const asmDecls = Object.values(symbols).map(symbol => `${symbol.name}:: DB`)
  29. // TODO: Some form of type-checking
  30. // 4. Generate IR
  31. const ir = new BasicBlock(convertIR(stmts))
  32. console.log("=== IR === ")
  33. console.log(prettyPrintBlock(ir))
  34. // 5. Generate code
  35. const alloc = allocateRegisters(ir, Object.values(R8))
  36. const insns = realizeBlock(allocate(alloc, ir))
  37. let output = ''
  38. if (asmDecls.length > 0) {
  39. output += `SECTION "${fileName} Data", WRAM0\n\n`
  40. output += asmDecls.join("\n")
  41. output += "\n\n"
  42. }
  43. if (insns.length > 0) {
  44. output += `SECTION "${fileName} Code", ROM0\n\n`
  45. output += insns.join("\n")
  46. }
  47. console.log("=== ASM ===")
  48. return output
  49. }
  50. export const processDecls = (decls: Array<Decl>): SymbolMap => decls.reduce(processDecl, {})
  51. export const processDecl = (symbols: SymbolMap, decl: Decl): SymbolMap => {
  52. const symbol = processVarDecl(symbols, decl)
  53. // Don't declare extern symbols
  54. if (hasAttr(symbol, { name: 'extern', args: [] })) {
  55. return symbols
  56. }
  57. return {
  58. ...symbols,
  59. [symbol.name]: symbol,
  60. }
  61. }
  62. export const processVarDecl = (symbols: SymbolMap, decl: VarDecl): SymbolDefn => {
  63. if (typeof symbols[decl.name] !== 'undefined') {
  64. throw new Error(`a variable named \`${decl.name}' is already defined`)
  65. }
  66. return {
  67. attrs: decl.attrs,
  68. name: decl.name,
  69. type: decl.args.type,
  70. }
  71. }
  72. export const hasAttr = (symbol: SymbolDefn, attr: Attr) => {
  73. let has = false
  74. for (let i = 0; i < symbol.attrs.length; ++i) {
  75. has = attrEquals(symbol.attrs[i], attr)
  76. if (has) {
  77. break
  78. }
  79. }
  80. return has
  81. }
  82. const attrEquals = (attr1: Attr, attr2: Attr): boolean => {
  83. if (attr1.name !== attr2.name || attr1.args.length !== attr2.args.length) {
  84. return false
  85. }
  86. for (let i = 0; i < attr1.args.length; ++i) {
  87. if (attr1.args[i] !== attr2.args[i]) {
  88. return false
  89. }
  90. }
  91. return true
  92. }