aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/utils/assembler.spec.ts49
-rw-r--r--src/utils/assembler.ts934
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);
+}
If you find this content useful please consider a small donation via bitcoin: bitcoin:1mxKyQsHHugqRxPKgwaA7wUwJEGCNthn2?amount=0.001&message=Donation Scraping git contents via cgit is very resource intensive. The continued hosting of these git repositories is entirely financed by your bitcoin contributions. For donations of a least 1 mBTC you include your IP address in the message of your bitcoin donation. This will remove this message in the future for requests from your IP, which will significantly reduce token usage. In any case, any contributions to bitcoin address 1mxKyQsHHugqRxPKgwaA7wUwJEGCNthn2 are very welcome. Thanks in advance for you contribution to a self-sustaining ecosystem.