Files
homeclaw/web-ui/vendor/avr8js/utils/assembler.js
T
kim 209f3a3aef v3.0.6: RP2040 에뮬레이터 개선 — USB CDC, vendor화, 부팅 감지
- USB CDC 경로 복원: #define Serial Serial1 우회 제거
- rp2040js/avr8js/codemirror/xterm/monaco-editor vendor화
- /vendor 정적 라우팅 추가 (Express 5 catch-all 우회)
- 부팅 완료 타임아웃 감지 (Serial 출력 없어도 8초 후 RAF 전환)
- 리셋 시 Simulator 재생성으로 주변 장치 상태 초기화
- 시리얼 패널의 내부 메시지를 console.log로 이동
2026-06-19 15:49:36 +09:00

916 lines
26 KiB
JavaScript

/**
* 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.
*/
/**
* 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, 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, 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, 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, bits) {
if (bits < 2) {
throw 'Need at least 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, labels, offset = 0) {
if (typeof c === 'string') {
let d = parseInt(c);
if (isNaN(d)) {
if (c in labels) {
d = labels[c] - offset;
}
else {
return NaN;
}
}
c = d;
}
return c;
}
/**
* Convert number to hex and left pad it
* @param len default to words.
*/
function zeroPad(r, 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) {
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) {
const d = yzq.match(/([YZ])\+(\d+)/);
let r = 0x8000;
if (d == null) {
throw 'Invalid arguments';
}
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;
}
const SEflag = (a) => zeroPad(0x9408 | (constValue(a, 0, 7) << 4));
/**
* Table of instructions that can be assembled.
*/
const OPTABLE = {
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);
}
let r = 0xf400 | constValue(a, 0, 7);
r |= fitTwoC(constValue(k >> 1, -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);
}
let r = 0xf000 | constValue(a, 0, 7);
r |= fitTwoC(constValue(k >> 1, -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), 'xxxx'];
}
let r = 0x940e;
k = constValue(k, 0, 0x400000) >> 1;
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), 'xxxx'];
}
let r = 0x940c;
k = constValue(k, 0, 0x400000) >> 1;
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);
}
const r = 0xd000 | fitTwoC(constValue(k >> 1, -2048, 2047), 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);
}
const r = 0xc000 | fitTwoC(constValue(k >> 1, -2048, 2047), 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) {
const lines = inputdata.split('\n');
const commentReg = /[#;].*$/;
const labelReg = /^(\w+):/;
const codeReg = /^\s*(\w+)(?:\s+([^,]+)(?:,\s*(\S+))?)?\s*$/;
let lt;
let res;
let rets;
let instruction;
let byteOffset = 0;
const lableTable = {};
const replacements = {};
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 */
const resMatch = res.match(codeReg);
try {
if (resMatch === null) {
throw "doesn't match as code!";
}
if (!resMatch[1]) {
throw 'Empty mnemonic field!';
}
/* do opcode */
instruction = resMatch[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 (instruction) {
case '_REPLACE':
replacements[resMatch[2]] = resMatch[3];
continue;
case '_LOC': {
const num = parseInt(resMatch[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(resMatch[2]);
if (isNaN(num)) {
throw 'Immeadiate Word is not a number.';
}
lt.bytes = zeroPad(num);
lt.byteOffset = byteOffset;
byteOffset += 2;
continue;
}
}
if (!(instruction in OPTABLE)) {
throw 'No such instruction: ' + instruction;
}
/* do replacements on parameters. */
if (resMatch[2] in replacements) {
resMatch[2] = replacements[resMatch[2]];
}
if (resMatch[3] in replacements) {
resMatch[3] = replacements[resMatch[3]];
}
const bytes = OPTABLE[instruction](resMatch[2], resMatch[3], byteOffset, lableTable);
lt.byteOffset = byteOffset;
switch (typeof bytes) {
case 'function':
case 'string':
byteOffset += 2;
break;
case 'object' /* assumed as an array. */:
byteOffset += bytes.length * 2;
break;
default:
throw 'unknown return type from optable.';
}
lt.bytes = bytes;
lineTable.push(lt);
}
catch (err) {
errorTable.push('Line ' + idx + ': ' + err);
}
}
return {
labels: lableTable,
errors: errorTable,
lines: lineTable,
};
}
function elementSize(lt) {
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, labels) {
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, labels };
}
/**
* The assembler.
*/
export function assemble(input) {
const mid = passOne(input);
if (mid.errors.length > 0) {
return {
bytes: new Uint8Array(0),
errors: mid.errors,
lines: [],
labels: {},
};
}
return passTwo(mid.lines, mid.labels);
}