diff options
| author | Uri Shaked | 2020-01-30 22:56:38 +0200 |
|---|---|---|
| committer | Uri Shaked | 2020-01-30 22:56:38 +0200 |
| commit | a9f485fc74887cc364f1d59a32648677182832f1 (patch) | |
| tree | 82a049766a33efd9aeef1ecc94b6aa1b5ad21fb2 /src | |
| parent | feat(twi): partial TWI master implementation #10 (diff) | |
| download | avr8js-a9f485fc74887cc364f1d59a32648677182832f1.tar.gz avr8js-a9f485fc74887cc364f1d59a32648677182832f1.tar.bz2 avr8js-a9f485fc74887cc364f1d59a32648677182832f1.zip | |
feat: add a simple AVR assembler for use in tests
Diffstat (limited to 'src')
| -rw-r--r-- | src/utils/assembler.spec.ts | 49 | ||||
| -rw-r--r-- | src/utils/assembler.ts | 934 |
2 files changed, 983 insertions, 0 deletions
diff --git a/src/utils/assembler.spec.ts b/src/utils/assembler.spec.ts new file mode 100644 index 0000000..29453eb --- /dev/null +++ b/src/utils/assembler.spec.ts @@ -0,0 +1,49 @@ +import { assemble } from './assembler'; + +function bytes(hex: string) { + const result = new Uint8Array(hex.length / 2); + for (let i = 0; i < hex.length; i += 2) { + result[i / 2] = parseInt(hex.substr(i, 2), 16); + } + return result; +} + +describe('AVR assembler', () => { + it('should assemble ADD instruction', () => { + expect(assemble('ADD r16, r11')).toEqual({ + bytes: bytes('0b0d'), + errors: [], + lines: [{ byteOffset: 0, bytes: '0d0b', line: 1, text: 'ADD r16, r11' }] + }); + }); + + it('should support labels', () => { + expect(assemble('loop: JMP loop').bytes).toEqual(bytes('0c940000')); + }); + + it('should support mutli-line programs', () => { + const input = ` + start: + LDI r16, 15 + EOR r16, r0 + BREQ start + `; + expect(assemble(input).bytes).toEqual(bytes('0fe00025e9f3')); + }); + + it('should successfully assemble an empty program', () => { + expect(assemble('')).toEqual({ + bytes: new Uint8Array(0), + errors: [], + lines: [] + }); + }); + + it('should return an empty byte array in case of program error', () => { + expect(assemble('LDI r15, 20')).toEqual({ + bytes: new Uint8Array(0), + errors: ['Line 0: Rd out of range: 16<>31'], + lines: [] + }); + }); +}); diff --git a/src/utils/assembler.ts b/src/utils/assembler.ts new file mode 100644 index 0000000..a06063a --- /dev/null +++ b/src/utils/assembler.ts @@ -0,0 +1,934 @@ +/** + * Assemble AVR programs into a list of bytes. + * Based on code from https://github.com/tadpol/Avrian-Jump, + * refactored to TypeScript by Uri Shaked. + * + * This was written with http://www.atmel.com/atmel/acrobat/doc0856.pdf + * It is a bit short of features often found in an assembler, but there is enough here to build + * simple programs and run them. + * + * It would be nice someday to add device support, just to give errors on unsupported + * instructions. Macros would be nice too. + * + * Copyright (C) 2020, Uri Shaked + * Copyright (c) 2012 Michael Conrad Tadpol Tilstra + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING + * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +interface LabelTable { + [key: string]: number; +} + +type Pass1Bytes = + | string + | ((l: LabelTable) => string) + | string[] + | [(l: LabelTable) => [string, string], string]; + +interface LineTablePass1 { + line: number; + bytes: Pass1Bytes; + text: string; + byteOffset: number; +} + +interface LineTable extends LineTablePass1 { + bytes: string; +} + +/** + * Get a destination register index from a string and shift it to where it + * is most commonly found. + * Also, make sure it is within the valid range. + */ +function destRindex(r: string, min = 0, max = 31) { + const match = r.match(/[Rr](\d{1,2})/); + if (!match) { + throw 'Not a register: ' + r; + } + const d = parseInt(match[1]); + if (d < min || d > max) { + throw 'Rd out of range: ' + min + '<>' + max; + } + return (d & 0x1f) << 4; +} + +/** + * Get a source register index from a string and shift it to where it is + * most commonly found. + * Also, make sure it is within the valid range. + */ +function srcRindex(r: string, min = 0, max = 31) { + const match = r.match(/[Rr](\d{1,2})/); + if (!match) { + throw 'Not a register: ' + r; + } + const d = parseInt(match[1]); + if (d < min || d > max) { + throw 'Rd out of range: ' + min + '<>' + max; + } + let s = d & 0xf; + s |= ((d >> 4) & 1) << 9; + return s; +} + +/** + * Get a constant value and check that it is in range. + */ +function constValue(r: string | number, min = 0, max = 255) { + const d = typeof r === 'string' ? parseInt(r) : r; + if (isNaN(d)) { + throw 'constant is not a number.'; + } + if (d < min || d > max) { + throw '[Ks] out of range: ' + min + '<>' + max; + } + return d; +} + +/* + * Fit a twos-complement number into the specific bit count + */ +function fitTwoC(r: number, bits: number) { + if (bits < 2) { + throw 'Need atleast 2 bits to be signed.'; + } + if (bits > 16) { + throw 'fitTwoC only works on 16bit numbers for now.'; + } + if (Math.abs(r) > Math.pow(2, bits - 1)) + throw 'Not enough bits for number. (' + r + ', ' + bits + ')'; + if (r < 0) { + r = 0xffff + r + 1; + } + const mask = 0xffff >> (16 - bits); + return r & mask; +} + +/** + * Determin if input is an address or label and lookup if required. + * If label that doesn't exist, return NaN. + * If offset is not 0, convert from absolute address to relative. + */ +function constOrLabel(c: string | number, labels: LabelTable, offset = 0) { + if (typeof c == 'string') { + let d = parseInt(c); + if (isNaN(d)) { + if (c in labels) { + d = labels[c] - offset; + d = d >> 1; /* convert bytes into words. */ + } else { + return NaN; + } + } + c = d; + } + return c; +} + +/** + * Convert number to hex and left pad it + * @param len default to words. + */ +function zeroPad(r: number | string, len = 4) { + r = Number(r).toString(16); + const base = Array(len + 1).join('0'); + const t = base.substr(0, len - r.length) + r; + return t; +} + +/** + * Get an Indirect Address Register and shift it to where it is commonly found. + */ +function stldXYZ(xyz: string) { + switch (xyz) { + case 'X': + return 0x900c; + case 'X+': + return 0x900d; + case '-X': + return 0x900e; + case 'Y': + return 0x8008; + case 'Y+': + return 0x9009; + case '-Y': + return 0x900a; + case 'Z': + return 0x8000; + case 'Z+': + return 0x9001; + case '-Z': + return 0x9002; + default: + throw 'Not -?[XYZ]\\+?'; + } +} + +/** + * Get an Indirect Address Register with displacement and shift it to where it is commonly found. + */ +function stldYZq(yzq: string) { + const d = yzq.match(/([YZ])\+(\d+)/); + let r = 0x8000; + switch (d[1]) { + case 'Y': + r |= 0x8; + break; + case 'Z': + /* r|= 0; */ + break; + default: + throw 'Not Y or Z with q'; + } + const q = parseInt(d[2]); + if (q < 0 || q > 64) throw 'q is out of range'; + r |= ((q & 0x20) << 8) | ((q & 0x18) << 7) | (q & 0x7); + return r; +} + +type opcodeHandler = (a: string, b: string, byteLoc: number, labels: LabelTable) => Pass1Bytes; + +const SEflag = (a: number) => zeroPad(0x9408 | (constValue(a, 0, 7) << 4)); + +/** + * Table of Mnemonics that can be assembled. + */ +const OPTABLE: { [key: string]: opcodeHandler } = { + /* Mnemonic: linecompiler */ + ADD(a, b) { + const r = 0x0c00 | destRindex(a) | srcRindex(b); + return zeroPad(r); + }, + ADC(a, b) { + const r = 0x1c00 | destRindex(a) | srcRindex(b); + return zeroPad(r); + }, + ADIW(a, b) { + let r = 0x9600; + const dm = a.match(/[Rr](24|26|28|30)/); + if (!dm) throw 'Rd must be 24, 26, 28, or 30'; + let d = parseInt(dm[1]); + d = (d - 24) / 2; + r |= (d & 0x3) << 4; + const k = constValue(b, 0, 63); + r |= ((k & 0x30) << 2) | (k & 0x0f); + return zeroPad(r); + }, + AND(a, b) { + const r = 0x2000 | destRindex(a) | srcRindex(b); + return zeroPad(r); + }, + ANDI(a, b) { + let r = 0x7000 | (destRindex(a, 16, 31) & 0xf0); + const k = constValue(b); + r |= ((k & 0xf0) << 4) | (k & 0xf); + return zeroPad(r); + }, + ASR(a) { + const r = 0x9405 | destRindex(a); + return zeroPad(r); + }, + BCLR(a) { + let r = 0x9488; + const s = constValue(a, 0, 7); + r |= (s & 0x7) << 4; + return zeroPad(r); + }, + BLD(a, b) { + const r = 0xf800 | destRindex(a) | (constValue(b, 0, 7) & 0x7); + return zeroPad(r); + }, + BRBC(a, b, byteLoc, labels) { + const k = constOrLabel(b, labels, byteLoc + 2); + if (isNaN(k)) { + return (l) => OPTABLE['BRBC']('a', b, byteLoc, l) as string; + } + let r = 0xf400 | constValue(a, 0, 7); + r |= fitTwoC(constValue(k, -64, 63), 7) << 3; + return zeroPad(r); + }, + BRBS(a, b, byteLoc, labels) { + const k = constOrLabel(b, labels, byteLoc + 2); + if (isNaN(k)) { + return (l) => OPTABLE['BRBS']('a', b, byteLoc, l) as string; + } + let r = 0xf000 | constValue(a, 0, 7); + r |= fitTwoC(constValue(k, -64, 63), 7) << 3; + return zeroPad(r); + }, + BRCC(a, _, byteLoc, labels) { + return OPTABLE['BRBC']('0', a, byteLoc, labels); + }, + BRCS(a, _, byteLoc, labels) { + return OPTABLE['BRBS']('0', a, byteLoc, labels); + }, + BREAK() { + return '9598'; + }, + BREQ(a, _, byteLoc, labels) { + return OPTABLE['BRBS']('1', a, byteLoc, labels); + }, + BRGE(a, _, byteLoc, labels) { + return OPTABLE['BRBC']('4', a, byteLoc, labels); + }, + BRHC(a, _, byteLoc, labels) { + return OPTABLE['BRBC']('5', a, byteLoc, labels); + }, + BRHS(a, _, byteLoc, labels) { + return OPTABLE['BRBS']('5', a, byteLoc, labels); + }, + BRID(a, _, byteLoc, labels) { + return OPTABLE['BRBC']('7', a, byteLoc, labels); + }, + BRIE(a, _, byteLoc, labels) { + return OPTABLE['BRBS']('7', a, byteLoc, labels); + }, + BRLO(a, _, byteLoc, labels) { + return OPTABLE['BRBS']('0', a, byteLoc, labels); + }, + BRLT(a, _, byteLoc, labels) { + return OPTABLE['BRBS']('4', a, byteLoc, labels); + }, + BRMI(a, _, byteLoc, labels) { + return OPTABLE['BRBS']('2', a, byteLoc, labels); + }, + BRNE(a, _, byteLoc, labels) { + return OPTABLE['BRBC']('1', a, byteLoc, labels); + }, + BRPL(a, _, byteLoc, labels) { + return OPTABLE['BRBC']('2', a, byteLoc, labels); + }, + BRSH(a, _, byteLoc, labels) { + return OPTABLE['BRBC']('0', a, byteLoc, labels); + }, + BRTC(a, _, byteLoc, labels) { + return OPTABLE['BRBC']('6', a, byteLoc, labels); + }, + BRTS(a, _, byteLoc, labels) { + return OPTABLE['BRBS']('6', a, byteLoc, labels); + }, + BRVC(a, _, byteLoc, labels) { + return OPTABLE['BRBC']('3', a, byteLoc, labels); + }, + BRVS(a, _, byteLoc, labels) { + return OPTABLE['BRBS']('3', a, byteLoc, labels); + }, + BSET(a) { + let r = 0x9408; + const s = constValue(a, 0, 7); + r |= (s & 0x7) << 4; + return zeroPad(r); + }, + BST(a, b) { + const r = 0xfa00 | destRindex(a) | constValue(b, 0, 7); + return zeroPad(r); + }, + CALL(a, b, byteLoc, labels) { + let k = constOrLabel(a, labels); + if (isNaN(k)) { + return [(l) => OPTABLE['CALL'](a, b, byteLoc, l) as [string, string], 'xxxx']; + } + let r = 0x940e; + k = constValue(k, 0, 0x400000); + const lk = k & 0xffff; + const hk = (k >> 16) & 0x3f; + r |= ((hk & 0x3e) << 3) | (hk & 1); + return [zeroPad(r), zeroPad(lk)]; + }, + CBI(a, b) { + const r = 0x9800 | (constValue(a, 0, 31) << 3) | constValue(b, 0, 7); + return zeroPad(r); + }, + CRB(a, b, byteLoc, l) { + const k = constValue(b); + return OPTABLE['ANDI'](a.toString(), (~k & 0xff).toString(), byteLoc, l); + }, + CLC() { + return '9488'; + }, + CLH() { + return '94d8'; + }, + CLI() { + return '94f8'; + }, + CLN() { + return '94a8'; + }, + CLR(a, _, byteLoc, l) { + return OPTABLE['EOR'](a, a, byteLoc, l); + }, + CLS() { + return '94c8'; + }, + CLT() { + return '94e8'; + }, + CLV() { + return '94b8'; + }, + CLZ() { + return '9498'; + }, + COM(a) { + const r = 0x9400 | destRindex(a); + return zeroPad(r); + }, + CP(a, b) { + const r = 0x1400 | destRindex(a) | srcRindex(b); + return zeroPad(r); + }, + CPC(a, b) { + const r = 0x0400 | destRindex(a) | srcRindex(b); + return zeroPad(r); + }, + CPI(a, b) { + let r = 0x3000 | (destRindex(a, 16, 31) & 0xf0); + const k = constValue(b); + r |= ((k & 0xf0) << 4) | (k & 0xf); + return zeroPad(r); + }, + CPSE(a, b) { + const r = 0x1000 | destRindex(a) | srcRindex(b); + return zeroPad(r); + }, + DEC(a) { + const r = 0x940a | destRindex(a); + return zeroPad(r); + }, + DES(a) { + const r = 0x940b | (constValue(a, 0, 15) << 4); + return zeroPad(r); + }, + EICALL() { + return '9519'; + }, + EIJMP() { + return '9419'; + }, + ELPM(a, b) { + if (typeof a == 'undefined' || a == '') { + return '95d8'; + } else { + let r = 0x9000 | destRindex(a); + switch (b) { + case 'Z': + r |= 6; + break; + case 'Z+': + r |= 7; + break; + default: + throw 'Bad operand'; + } + return zeroPad(r); + } + }, + EOR(a, b) { + const r = 0x2400 | destRindex(a) | srcRindex(b); + return zeroPad(r); + }, + FMUL(a, b) { + const r = 0x0308 | (destRindex(a, 16, 23) & 0x70) | (srcRindex(b, 16, 23) & 0x7); + return zeroPad(r); + }, + FMULS(a, b) { + const r = 0x0380 | (destRindex(a, 16, 23) & 0x70) | (srcRindex(b, 16, 23) & 0x7); + return zeroPad(r); + }, + FMULSU(a, b) { + const r = 0x0388 | (destRindex(a, 16, 23) & 0x70) | (srcRindex(b, 16, 23) & 0x7); + return zeroPad(r); + }, + ICALL() { + return '9509'; + }, + IJMP() { + return '9409'; + }, + IN(a, b) { + let r = 0xb000 | destRindex(a); + const A = constValue(b, 0, 63); + r |= ((A & 0x30) << 5) | (A & 0x0f); + return zeroPad(r); + }, + INC(a) { + const r = 0x9403 | destRindex(a); + return zeroPad(r); + }, + JMP(a, b, byteLoc, labels) { + let k = constOrLabel(a, labels); + if (isNaN(k)) { + return [(l) => OPTABLE['JMP'](a, b, byteLoc, l) as [string, string], 'xxxx']; + } + let r = 0x940c; + k = constValue(k, 0, 0x400000); + const lk = k & 0xffff; + const hk = (k >> 16) & 0x3f; + r |= ((hk & 0x3e) << 3) | (hk & 1); + return [zeroPad(r), zeroPad(lk)]; + }, + LAC(a, b) { + if (a != 'Z') throw 'First Operand is not Z'; + const r = 0x9206 | destRindex(b); + return zeroPad(r); + }, + LAS(a, b) { + if (a != 'Z') throw 'First Operand is not Z'; + const r = 0x9205 | destRindex(b); + return zeroPad(r); + }, + LAT(a, b) { + if (a != 'Z') throw 'First Operand is not Z'; + const r = 0x9207 | destRindex(b); + return zeroPad(r); + }, + LD(a, b) { + const r = 0x0000 | destRindex(a) | stldXYZ(b); + return zeroPad(r); + }, + LDD(a, b) { + const r = 0x0000 | destRindex(a) | stldYZq(b); + return zeroPad(r); + }, + LDI(a, b) { + let r = 0xe000 | (destRindex(a, 16, 31) & 0xf0); + const k = constValue(b); + r |= ((k & 0xf0) << 4) | (k & 0xf); + return zeroPad(r); + }, + LDS(a, b) { + const k = constValue(b, 0, 65535); + const r = 0x9000 | destRindex(a); + return [zeroPad(r), zeroPad(k)]; + }, + LPM(a, b) { + if (typeof a == 'undefined' || a == '') { + return '95c8'; + } else { + let r = 0x9000 | destRindex(a); + switch (b) { + case 'Z': + r |= 4; + break; + case 'Z+': + r |= 5; + break; + default: + throw 'Bad operand'; + } + return zeroPad(r); + } + }, + LSL(a, _, byteLoc, l) { + return OPTABLE['ADD'](a, a, byteLoc, l); + }, + LSR(a) { + const r = 0x9406 | destRindex(a); + return zeroPad(r); + }, + MOV(a, b) { + const r = 0x2c00 | destRindex(a) | srcRindex(b); + return zeroPad(r); + }, + MOVW(a, b) { + /* use destRindex on both here for simpler shifting */ + const r = 0x0100 | ((destRindex(a) >> 1) & 0xf0) | ((destRindex(b) >> 5) & 0xf); + return zeroPad(r); + }, + MUL(a, b) { + const r = 0x9c00 | destRindex(a) | srcRindex(b); + return zeroPad(r); + }, + MULS(a, b) { + const r = 0x0200 | (destRindex(a, 16, 31) & 0xf0) | (srcRindex(b, 16, 31) & 0xf); + return zeroPad(r); + }, + MULSU(a, b) { + const r = 0x0300 | (destRindex(a, 16, 23) & 0x70) | (srcRindex(b, 16, 23) & 0x7); + return zeroPad(r); + }, + NEG(a) { + const r = 0x9401 | destRindex(a); + return zeroPad(r); + }, + NOP() { + return '0000'; + }, + OR(a, b) { + const r = 0x2800 | destRindex(a) | srcRindex(b); + return zeroPad(r); + }, + ORI(a, b) { + let r = 0x6000 | (destRindex(a, 16, 31) & 0xf0); + const k = constValue(b); + r |= ((k & 0xf0) << 4) | (k & 0xf); + return zeroPad(r); + }, + OUT(a, b) { + let r = 0xb800 | destRindex(b); + const A = constValue(a, 0, 63); + r |= ((A & 0x30) << 5) | (A & 0x0f); + return zeroPad(r); + }, + POP(a) { + const r = 0x900f | destRindex(a); + return zeroPad(r); + }, + PUSH(a) { + const r = 0x920f | destRindex(a); + return zeroPad(r); + }, + RCALL(a, b, byteLoc, labels) { + const k = constOrLabel(a, labels, byteLoc + 2); + if (isNaN(k)) { + return (l) => OPTABLE['RCALL'](a, b, byteLoc, l) as string; + } + const r = 0xd000 | fitTwoC(constValue(k, -2048, 2048), 12); + return zeroPad(r); + }, + RET() { + return '9508'; + }, + RETI() { + return '9518'; + }, + RJMP(a, b, byteLoc, labels) { + const k = constOrLabel(a, labels, byteLoc + 2); + if (isNaN(k)) { + return (l) => OPTABLE['RJMP'](a, b, byteLoc, l) as string; + } + const r = 0xc000 | fitTwoC(constValue(k, -2048, 2048), 12); + return zeroPad(r); + }, + ROL(a, _, byteLoc, l) { + return OPTABLE['ADC'](a, a, byteLoc, l); + }, + ROR(a) { + const r = 0x9407 | destRindex(a); + return zeroPad(r); + }, + SBC(a, b) { + const r = 0x0800 | destRindex(a) | srcRindex(b); + return zeroPad(r); + }, + SBCI(a, b) { + let r = 0x4000 | (destRindex(a, 16, 31) & 0xf0); + const k = constValue(b); + r |= ((k & 0xf0) << 4) | (k & 0x0f); + return zeroPad(r); + }, + SBI(a, b) { + const r = 0x9a00 | (constValue(a, 0, 31) << 3) | constValue(b, 0, 7); + return zeroPad(r); + }, + SBIC(a, b) { + const r = 0x9900 | (constValue(a, 0, 31) << 3) | constValue(b, 0, 7); + return zeroPad(r); + }, + SBIS(a, b) { + const r = 0x9b00 | (constValue(a, 0, 31) << 3) | constValue(b, 0, 7); + return zeroPad(r); + }, + SBIW(a, b) { + let r = 0x9700; + const dm = a.match(/[Rr](24|26|28|30)/); + if (!dm) throw 'Rd must be 24, 26, 28, or 30'; + let d = parseInt(dm[1]); + d = (d - 24) / 2; + r |= (d & 0x3) << 4; + const k = constValue(b, 0, 63); + r |= ((k & 0x30) << 2) | (k & 0x0f); + return zeroPad(r); + }, + SBR(a, b) { + let r = 0x6000 | (destRindex(a, 16, 31) & 0xf0); + const k = constValue(b); + r |= ((k & 0xf0) << 4) | (k & 0x0f); + return zeroPad(r); + }, + SBRC(a, b) { + const r = 0xfc00 | destRindex(a) | constValue(b, 0, 7); + return zeroPad(r); + }, + SBRS(a, b) { + const r = 0xfe00 | destRindex(a) | constValue(b, 0, 7); + return zeroPad(r); + }, + SEC() { + return SEflag(0); + }, + SEH() { + return SEflag(5); + }, + SEI() { + return SEflag(7); + }, + SEN() { + return SEflag(2); + }, + SER(a) { + const r = 0xef0f | (destRindex(a, 16, 31) & 0xf0); + return zeroPad(r); + }, + SES() { + return SEflag(4); + }, + SET() { + return SEflag(6); + }, + SEV() { + return SEflag(3); + }, + SEZ() { + return SEflag(1); + }, + SLEEP() { + return '9588'; + }, + SPM(a) { + if (typeof a == 'undefined' || a == '') { + return '95e8'; + } else { + if (a != 'Z+') throw 'Bad param to SPM'; + return '95f8'; + } + }, + ST(a, b) { + const r = 0x0200 | destRindex(b) | stldXYZ(a); + return zeroPad(r); + }, + STD(a, b) { + const r = 0x0200 | destRindex(b) | stldYZq(a); + return zeroPad(r); + }, + STS(a, b) { + const k = constValue(a, 0, 65535); + const r = 0x9200 | destRindex(b); + return [zeroPad(r), zeroPad(k)]; + }, + SUB(a, b) { + const r = 0x1800 | destRindex(a) | srcRindex(b); + return zeroPad(r); + }, + SUBI(a, b) { + let r = 0x5000 | (destRindex(a, 16, 31) & 0xf0); + const k = constValue(b); + r |= ((k & 0xf0) << 4) | (k & 0xf); + return zeroPad(r); + }, + SWAP(a) { + const r = 0x9402 | destRindex(a); + return zeroPad(r); + }, + TST(a, _, byteLoc, l) { + return OPTABLE['AND'](a, a, byteLoc, l); + }, + WDR() { + return '95a8'; + }, + XCH(a, b) { + const r = 0x9204 | destRindex(b); + if (a != 'Z') throw 'Bad param, not Z'; + return zeroPad(r); + } +}; + +function passone(inputdata: string) { + const lines = inputdata.split('\n'); + const commentReg = /[#;].*$/; + const labelReg = /^(\w+):/; + const codeReg = /^\s*(\w+)(?:\s+([^,]+)(?:,\s*(\S+))?)?\s*$/; + let lt: LineTablePass1; + let res; + let rets; + let mnemonic; + + let byteOffset = 0; + const lableTable: LabelTable = {}; + const replacements: { [key: string]: string } = {}; + const errorTable = []; + const lineTable = []; + + for (let idx = 0; idx < lines.length; idx++) { + res = lines[idx].trim(); + if (res.length == 0) continue; + lt = { line: idx + 1, text: res, bytes: [], byteOffset: 0 }; + res = res.replace(commentReg, '').trim(); /* strip off comments. */ + if (res.length == 0) continue; + /* check for a label */ + rets = res.match(labelReg); + if (rets) { + lableTable[rets[1]] = byteOffset; + res = res.replace(labelReg, '').trim(); /* strip out label. */ + } + if (res.length == 0) continue; + /* Check for a mnemonic line */ + res = res.match(codeReg); + try { + if (res == null) { + throw "doesn't match as code!"; + } + + if (!res[1]) { + throw 'Empty mnemonic field!'; + } + + /* do opcode */ + mnemonic = res[1].toUpperCase().trim(); + /* This switch is ok for just these three. + * If ever to add more, then need to figure out how to merge all of the + * mnemonics into the OPTABLE. (or build a seperate internal op table) + */ + switch (mnemonic) { + case '_REPLACE': + replacements[res[2]] = res[3]; + continue; + case '_LOC': { + const num = parseInt(res[2]); + if (isNaN(num)) { + throw 'Location is not a number.'; + } + if (num & 0x1) { + throw 'Location is odd'; + } + byteOffset = num; + continue; + } + case '_IW': { + const num = parseInt(res[2]); + if (isNaN(num)) { + throw 'Immeadiate Word is not a number.'; + } + lt.bytes = zeroPad(num); + lt.byteOffset = byteOffset; + byteOffset += 2; + continue; + } + } + + if (!(mnemonic in OPTABLE)) { + throw 'No such mnemonic: ' + mnemonic; + } + + /* do replacements on parameters. */ + if (res[2] in replacements) { + res[2] = replacements[res[2]]; + } + if (res[3] in replacements) { + res[3] = replacements[res[3]]; + } + + rets = OPTABLE[mnemonic](res[2], res[3], byteOffset, lableTable); + lt.byteOffset = byteOffset; + switch (typeof rets) { + case 'function': + case 'string': + byteOffset += 2; + break; + case 'object' /* assumed as an array. */: + byteOffset += rets.length * 2; + break; + default: + throw 'unknown return type from optable.'; + } + lt.bytes = rets; + lineTable.push(lt); + } catch (err) { + errorTable.push('Line ' + idx + ': ' + err); + } + } + + return { + labels: lableTable, + errors: errorTable, + lines: lineTable + }; +} + +function elementSize(lt: LineTablePass1) { + return typeof lt.bytes === 'string' ? lt.bytes.length / 2 : lt.bytes.length * 2; +} + +/** + * Handle any forward referenced labels that were deferred in passone. + */ +function passtwo(lineTable: LineTablePass1[], labels: LabelTable) { + const errorTable = []; + const lastElement = lineTable[lineTable.length - 1]; + const byteSize = lastElement ? lastElement.byteOffset + elementSize(lastElement) : 0; + const resultTable = new Uint8Array(byteSize); + for (const ltEntry of lineTable) { + try { + /* Look for functions left over from passone. */ + if (typeof ltEntry.bytes == 'function') { + ltEntry.bytes = ltEntry.bytes(labels); + } + if ( + ltEntry.bytes instanceof Array && + ltEntry.bytes.length >= 1 && + typeof ltEntry.bytes[0] == 'function' + ) { + /* a bit gross. FIXME */ + ltEntry.bytes = ltEntry.bytes[0](labels); + } + + /* copy bytes out of linetable into the results. */ + switch (typeof ltEntry.bytes) { + case 'string': + resultTable[ltEntry.byteOffset + 1] = parseInt(ltEntry.bytes.substr(0, 2), 16); + resultTable[ltEntry.byteOffset] = parseInt(ltEntry.bytes.substr(2, 4), 16); + break; + case 'object' /* also array. */: + if (ltEntry.bytes.length < 1) { + throw 'Empty array in lineTable.'; + } + for (let j = 0, bi = ltEntry.byteOffset; j < ltEntry.bytes.length; j++, bi += 2) { + const value = ltEntry.bytes[j]; + if (typeof value !== 'string') { + throw 'Not an array of strings.'; + } + resultTable[bi + 1] = parseInt(value.substr(0, 2), 16); + resultTable[bi] = parseInt(value.substr(2, 4), 16); + } + break; + default: + throw 'unknown return type from optable.'; + } + } catch (err) { + errorTable.push('Line: ' + ltEntry.line + ': ' + err); + } + } + + return { errors: errorTable, bytes: resultTable, lines: lineTable as LineTable[] }; +} + +/** + * The assembler. + */ +export function assemble(input: string) { + const mid = passone(input); + if (mid.errors.length > 0) { + return { + bytes: new Uint8Array(0), + errors: mid.errors, + lines: [] + }; + } + return passtwo(mid.lines, mid.labels); +} |
