import dedent from "npm:dedent"; import { PC } from "./pc.ts"; const pc = new PC() const labels: Record = {} const code = new TextDecoder() .decode(Deno.readFileSync(Deno.args[0] ?? 'code.a')) const aliases = Object.fromEntries(new TextDecoder() .decode(Deno.readFileSync('aliases.txt')).split('\n').map(a=>a.split('='))) const macros: Record string> = {} interface DataAddr { line: number, offset: number } interface ObjectFile { // number is the ammount of bytes to skip code: (string | number)[], offset: number, // line number, data data: [number, number[]][] } const object: ObjectFile = { code: [], data: [], offset: 2**16 / 2 } const advAliases: Record = { 'mov,reg,addr': dedent`\ mov d @1 ld @0 d`, 'mov,addr,reg': dedent`\ mov d @0 str d @1`, 'str,reg,addr': dedent`\ mov d @1 str d @0`, 'ld,reg,addr': dedent`\ mov d @1 ld @0 d`, 'add,': `add c a b`, 'sub,': `sub c a b`, 'mul,': `mul c a b`, 'div,': `div c a b`, } function processCode(rcode: string, offset: number = 0): (string | number)[] { const code: (string | number)[] = rcode .split('\n') .map(l => l.trim()) .map(l => l.replace(/\s*(? l.replace(/(? char.charCodeAt(0).toString())) .map(l => l.replace(/0x([0-9A-Fa-f]+)/g, (_, hex: string) => ''+parseInt(hex, 16))) .filter(l => l) .reduce((acc, l) => { if (acc[acc.length - 1] && acc[acc.length - 1].endsWith('\\')) { acc[acc.length - 1] = acc[acc.length - 1].slice(0, -1) + '\n' acc[acc.length - 1] += l return acc } acc.push(l) return acc }, [] as string[]); const result: (string | number)[] = [] let i = offset; let li = 0; //parse macros while (li < code.length) { const el = code[li]; if (typeof el == 'number') { li++; continue; } const sel = el.split(' '); li++; if (sel[0] == '.macro') { sel.shift(); let tx = sel.join(' '); const pattern = /([A-z\-_0-9]+)\((.*?)\)\s*/g const match = [...tx.matchAll(pattern)][0] tx = tx.replace(pattern, '').replaceAll('\\n', '\n'); if (!match) throw 'knives at you' const args = (match[2] ?? '').split(/,\s*/g) macros[match[1]] = (args_: string[]) => { let s = tx; let i = 0 for (const a of args_) { s = s.replaceAll('@'+args[i], a) i++ } return s } continue; } i++ } // parse other i = offset; li = 0; while (li < code.length) { const el = code[li]; if (typeof el == 'number') { li++; continue; } const sel = el.split(' '); li++; if (el.endsWith(":")) { console.log(sel[0], i) labels[sel[0].replace(/:$/g,'')] = '$'+i; labels[`[${sel[0].replace(/:$/g,'')}]`] = `[${i}]`; continue; } if (sel[0] == '.label') { labels[sel[1]] = sel[2]; continue; } if (macros[sel[0]]) { const macro = macros[sel[0]] sel.shift() const r = macro(sel).split('\n') i+=r.length continue; } if (sel[0] == '.macro') { continue; } if (sel[0] == '.offset') { continue; } if (sel[0] == '#using') { const newCode = processCode(new TextDecoder().decode(Deno.readFileSync(sel[1])), i + 1) i += newCode.length + 1 continue; } i++ } i = offset; li = 0; while (li < code.length) { let el = code[li]; if (typeof el == 'number') { result.push(el); li++; continue; } let sel = el.split(' '); if (aliases[sel[0]]) el = el.replace(sel[0], aliases[sel[0]]); li++; if (macros[sel[0]]) { for (const label of Object.keys(labels).sort((a, b) => b.length - a.length)) { el = el.split(' ').map(a => a == label ? labels[label] : a).join(' ') } sel = el.split(' ') const macro = macros[sel[0]] sel.shift() let rr = macro(sel) for (const label of Object.keys(labels).sort((a, b) => b.length - a.length)) { rr = rr.replace(label, labels[label]) } const r = rr.split('\n') result.push(...r) i+=r.length continue; } if (el.endsWith(":")) { continue; } if (sel[0] == '.label') { continue; } if (sel[0] == '.macro') { continue; } if (sel[0] == '.offset') { object.offset = +sel[1] continue; } if (sel[0] == '#using') { const newCode = processCode(new TextDecoder().decode(Deno.readFileSync(sel[1])), i + 1) result.push(`jmp $${i+newCode.length+1}`) // skip over included code result.push(...newCode) i += newCode.length + 1 continue; } if (sel[0] == '.hex') { object.data.push([ i, [parseInt(sel[1], 16)] ]) result.push(1) i++; continue; } if (sel[0] == '.str') { const str = [...el.matchAll(/"(.*?)(? str.charCodeAt(i)) ]) result.push(str.length) i++; continue; } for (const label of Object.keys(labels).sort((a, b) => b.length - a.length)) { el = el.split(' ').map(a => a == label ? labels[label] : a).join(' ') } const [cmd, ...args] = el.split(' '); const argtypes = args.map((a: string) => { if (pc.regNames.split('').includes(a)) return 'reg'; if (a.match(/^\$[0-9A-Fa-f]+$/g)) return 'addr'; return 'val'; }) if (advAliases[`${cmd},${argtypes.join(',')}`]) { el = advAliases[`${cmd},${argtypes.join(',')}`].replace(/@([0-9]+)/g, (_m, arg) => { if (argtypes[+arg] == 'addr') { return args[+arg].replace('$', '') } return args[+arg] }) result.push(...el.split('\n')); i += el.split('\n').length; continue; } result.push(el.replace(/\$[0-9A-Fa-f]+/g, (_, addr) => addr)) i++ } return result } object.code = processCode(code+'\nend') // console.log(labels) // for (const label of Object.keys(labels)) { // result.push(`ld a ${labels[label]}`); // result.push(`mov b ${labels[label]}`); // result.push(`mov c 0`); // result.push(`dbg ${label}`) // } Deno.writeTextFileSync('code.o', JSON.stringify(object))