$title('8086 disassembler')
$date(5/14/81)
$compact
$optimize(2)

disem86: do;

declare
	cr literally '0dh',
	lf literally '0ah',
	true literally '1',
	false literally '0';

$include(optab.dat)

declare
	tab$ptrs (5) address public initial (.ops2, .ops3, .ops4, .ops5, .ops6);

declare
	left$bracket byte data ('['),
	right$bracket byte data (']');

declare
	alt$table$base address,
	alt$table based alt$table$base (16) byte,
	alt$table$ptrs (8) address external;

declare
	mod$bits byte,
	reg$bits byte,
	rm$bits byte,
	byte1$reg$bits byte,
	d$bit byte,
	s$bit byte,
	v$bit byte,
	w$bit byte,
	z$bit byte;

declare
	mnemonic$index byte,    /* index into opcodes */
	instr$type byte,
	table$ptr address,
	table$char based table$ptr byte,
	disem$ptr pointer,
	disem$offset address at (.disem$ptr),
	disem$end address,
	disem$byte based disem$ptr (1) byte,
	disem$word based disem$ptr (1) address,
	b$or$w$flag byte,
	error$flag byte;

declare instr$table (512) byte external;

declare
	ax$reg literally '0',
	cx$reg literally '1',
	dx$reg literally '2',
	bx$reg literally '3',
	sp$reg literally '4',
	bp$reg literally '5',
	si$reg literally '6',
	di$reg literally '7';

declare
	al$reg literally '0',
	cl$reg literally '1',
	dl$reg literally '2',
	bl$reg literally '3',
	ah$reg literally '4',
	ch$reg literally '5',
	dh$reg literally '6',
	bh$reg literally '7';
	
declare
	es$reg literally '0',
	cs$reg literally '1',
	ss$reg literally '2',
	ds$reg literally '3';

declare
	reg16 (*) byte public initial ('AX', 'CX', 'DX', 'BX', 'SP', 'BP', 'SI', 'DI'),
	reg8 (*) byte public initial ('AL', 'CL', 'DL', 'BL', 'AH', 'CH', 'DH', 'BH'),
	segreg (*) byte public initial ('ES', 'CS', 'SS', 'DS');

conout: procedure (c) external;
	declare c byte;
	end conout;

comma: procedure;
	call conout (',');
	end comma;

printm: procedure (a) PUBLIC;
	declare a address;
	declare b based a byte;
	do while b <> '$';
		call conout (b);
		a = a + 1;
	end;
	end printm;

print$nibble: procedure (b);
	declare b byte;
	if b > 9 then call conout (b - 10 + 'A');
	else call conout (b + '0');
	end print$nibble;

print$byte: procedure (b);
	declare b byte;
	call print$nibble (shr (b, 4));
	call print$nibble (b and 0fh);
	end print$byte;

print$word: procedure (a) public;
	declare a address;
	call print$byte (high (a));
	call print$byte (low (a));
	end print$word;

error: procedure;
	call printm (.('??=    $'));
	call print$byte (disem$byte (0));
	disem$offset = disem$offset + 1;
	end error;

set$bits: procedure;
	byte1$reg$bits = disem$byte (0) and 7;
	mod$bits = shr (disem$byte (1), 6);
	reg$bits = shr (disem$byte (1), 3) and 7;
	rm$bits = disem$byte (1) and 7;
	w$bit, z$bit = disem$byte (0) and 1;
	d$bit, s$bit, v$bit = shr (disem$byte (0), 1) and 1;
	end set$bits;

print$b$or$w: procedure;
	if w$bit then call printm (.('WORD $'));
	else call printm (.('BYTE $'));
	end print$b$or$w;

print$reg: procedure (reg$add, reg);
	declare reg$add address, reg byte;
	table$ptr = reg$add + shl (reg, 1);
	call conout (table$char);
	table$ptr = table$ptr + 1;
	call conout (table$char);
	end print$reg;

print$reg8: procedure (reg);
	declare reg byte;
	call print$reg (.reg8, reg);
	end print$reg8;

print$reg16: procedure (reg);
	declare reg byte;
	call print$reg (.reg16, reg);
	end print$reg16;

print$reg$8$or$16: procedure (reg$num);
	declare reg$num byte;
	if w$bit then call print$reg$16 (reg$num);
	else call print$reg$8 (reg$num);
	end print$reg$8$or$16;

print$2$reg$16: procedure (r1, r2);
	declare (r1, r2) byte;
	call print$reg$16 (r1);
	call conout ('+');
	call print$reg$16 (r2);
	end print$2$reg$16;

print$A$reg: procedure;
	if w$bit then call print$reg$16 (ax$reg);
	else call print$reg$8 (al$reg);
	end print$A$reg;

print$seg$reg: procedure (reg);
	declare reg byte;
	call print$reg (.seg$reg, reg);
	end print$seg$reg;

print$data$8: procedure;
	call print$byte (disem$byte (0));
	disem$offset = disem$offset + 1;
	end print$data$8;

print$data$16: procedure;
	call print$word (disem$word (0));
	disem$offset = disem$offset + 2;
	end print$data$16;

print$data$8$or$16: procedure;
	if w$bit then call print$data$16;
	else call print$data$8;
	end print$data$8$or$16;

print$data$sw: procedure;
	if rol (disem$byte (0), 1) then call print$word (0ff00h or disem$byte (0));
	else call print$word (disem$byte (0));
	disem$offset = disem$offset + 1;
	end print$data$sw;

print$signed$8: procedure;
	declare a address;
	a = disem$byte (0);
	if low (a) >= 80h then a = a or 0ff00h;  /* sign extend to 16 bits */
	call print$word (disem$offset + a + 1);
	disem$offset = disem$offset + 1;
	end print$signed$8;

print$signed$16: procedure;
	call print$word (disem$offset + disem$word (0) + 2);
	disem$offset = disem$offset + 2;
	end print$signed$16;

print$direct$addr: procedure;
	call conout (left$bracket);
	call print$word (disem$word (0));
	call conout (right$bracket);
	disem$offset = disem$offset + 2;
	end print$direct$addr;

print$mod$rm: procedure;
	disem$offset = disem$offset + 1;    /* point past mod/reg/rm byte */
	if mod$bits = 3 then
		do;
			call print$reg$8$or$16 (rm$bits);
			return;
		end;
	if b$or$w$flag then call print$b$or$w;
	if rm$bits = 6 and mod$bits = 0 then
		do;
			call print$direct$addr;
			return;
		end;
	if mod$bits = 1 then
		do;
			if (rm$bits <> 6) or (disem$byte (0) <> 0)
			  then call print$byte (disem$byte (0));
			disem$offset = disem$offset + 1;
		end;
	else if mod$bits = 2 then
		do;
			call print$word (disem$word (0));
			disem$offset = disem$offset + 2;
		end;
	call conout (left$bracket);
	do case rm$bits;
		call print$2$reg$16 (3, 6);
		call print$2$reg$16 (3, 7);
		call print$2$reg$16 (5, 6);
		call print$2$reg$16 (5, 7);
		call print$reg$16 (6);
		call print$reg$16 (7);
		call print$reg$16 (5);
		call print$reg$16 (3);
	end;
	call conout (right$bracket);
	end print$mod$rm;

print$mod$reg$rm: procedure;
	if d$bit then
		do;
			call print$reg$8$or$16 (reg$bits);
			call conout (',');
			call print$mod$rm;
		end;
	else
		do;
			call print$mod$rm;
			call conout (',');
			call print$reg$8$or$16 (reg$bits);
		end;
	end print$mod$reg$rm;

print$mnemonic: procedure;
	declare (len, i) byte;
	len = 2;
	do while mnemonic$index >= opn$in (len - 1);
		len = len + 1;
	end;
	table$ptr = tab$ptrs (len - 2) + (mnemonic$index - opn$in (len - 2))
	  * len;
	do i = 1 to 7;
		if i <= len then
			do;
				call conout (table$char);
				table$ptr = table$ptr + 1;
			end;
		else call conout (' ');
	end;
	disem$offset = disem$offset + 1;
	end print$mnemonic;

type1: procedure;
	call print$mnemonic;
	end type1;

type2: procedure;
	if disem$byte (1) = 0ah then
		do;
			call print$mnemonic;
			disem$offset = disem$offset + 1;
		end;
	else error$flag = true;
	end type2;

type3: procedure;
	call print$mnemonic;
	call print$reg$16 (byte1$reg$bits);
	end type3;

type4: procedure;
	declare temp byte;
	temp = shr (disem$byte (0), 3) and 3;
	call print$mnemonic;
	call print$segreg (temp);
	end type4;

type5: procedure;
	call print$mnemonic;
	call print$signed$8;
	end type5;

type6: procedure;
	call print$mnemonic;
	call print$signed$16;
	end type6;

type8: procedure;   /* 7, 9 */
	call print$mnemonic;
	call print$mod$rm;
	end type8;

type10: procedure;
	call print$mnemonic;
	call print$data$8;
	end type10;

type11: procedure;
	call print$mnemonic;
	call print$data$16;
	end type11;

type12: procedure;
	call print$mnemonic;
	call conout ('3');
	end type12;

type13: procedure;
	declare temp address;
	call print$mnemonic;
	temp = disem$word (0);
	disem$offset = disem$offset + 2;
	call print$data$16;
	call conout (':');
	call print$word (temp);
	end type13;

type14: procedure;    /* 15, 16, 17 */
	call print$mnemonic;
	call print$mod$reg$rm;
	end type14;

type18: procedure;    /* 19, 20, 21 */
	call print$mnemonic;
	if d$bit then
		do;
			call print$direct$addr;
			call comma;
			call print$A$reg;
		end;
	else
		do;
			call print$A$reg;
			call comma;
			call print$direct$addr;
		end;
	end type18;

type22: procedure;
	call print$mnemonic;
	if d$bit then
		do;
			call print$data$8;
			call comma;
			call print$A$reg;
		end;
	else
		do;
			call print$A$reg;
			call comma;
			call print$data$8;
		end;
	end type22;

type23: procedure;    /* 24 */
	call print$mnemonic;
	call print$A$reg;
	call comma;
	call print$data$8$or$16;
	end type23;

type25: procedure;   /* 26 */
	call print$mnemonic;
	if d$bit then
		do;
			call print$reg$16 (dx$reg);
			call comma;
			call print$A$reg;
		end;
	else
		do;
			call print$A$reg;
			call comma;
			call print$reg$16 (dx$reg);
		end;
	end type25;

type27: procedure;   /* 28, 29, 30 */
	call print$mnemonic;
	b$or$w$flag = true;
	call print$mod$rm;
	call comma;
	if v$bit then call print$reg$8 (cl$reg);
	else call conout ('1');
	end type27;

type31: procedure;    /* 32 */
	call setbits;
	reg$bits = byte1$reg$bits;
	w$bit = shr (disem$byte (0), 3) and 1;
	call print$mnemonic;
	call print$reg$8$or$16 (reg$bits);
	call comma;
	call print$data$8$or$16;
	end type31;

type33: procedure;
	call print$mnemonic;
	call print$reg$16 (ax$reg);
	call comma;
	call print$reg$16 (byte1$reg$bits);
	end type33;

type34: procedure;   /* 35 */
	call print$mnemonic;
	b$or$w$flag = true;
	call print$mod$rm;
	call comma;
	call print$data$8$or$16;
	end type34;

type36: procedure;   /* 37 */
	w$bit = true;   /* force 16 bit reg, mem */
	if reg$bits > 3 then
		do;
			error$flag = true;
			return;
		end;
	call print$mnemonic;
	if d$bit then
		do;
			call print$seg$reg (reg$bits);
			call comma;
			call print$mod$rm;
		end;
	else
		do;
			call print$mod$rm;
			call comma;
			call print$seg$reg (reg$bits);
		end;
	end type36;

type38: procedure;
	call print$mnemonic;
	call print$mod$rm;
	call comma;
	call print$data$8;
	end type38;

type39: procedure;
	if mod$bits = 3 then
		do;
			error$flag = true;
			return;
		end;
	call print$mnemonic;
	call print$reg$16 (reg$bits);
	call comma;
	call print$mod$rm;
	end type39;

type40: procedure;   /* 41 */
	if mod$bits = 3 then
		do;
			error$flag = true;
			return;
		end;
	call print$mnemonic;
	b$or$w$flag = true;
	call print$mod$rm;
	call comma;
	call print$data$8$or$16;
	end type40;

type42: procedure;
	call print$mnemonic;
	call print$byte (shl (byte1$reg$bits, 3) or reg$bits);
	call comma;
	call print$mod$rm;
	end type42;

type44: procedure;
	call print$mnemonic;
	b$or$w$flag = true;
	call print$modrm;
	call comma;
	if s$bit = 1 and w$bit = 1 then call print$data$sw;
	else call print$data$8$or$16;
	end type44;

type45: procedure;
	b$or$w$flag = true;
	call type8;
	end type45;

dis: procedure;
	error$flag, b$or$w$flag = false;
	call set$bits;
	if instr$type = 26 then
		do;
			alt$table$base = alt$table$ptrs (mnemonic$index);
			mnemonic$index = alt$table (reg$bits * 2);
			instr$type = alt$table (reg$bits * 2 + 1);
		end;
	if instr$type > 28 then error$flag = true;
	else
	do case instr$type;
		error$flag = true;
		call type1;
		call type2;
		call type3;
		call type4;
		call type5;
		call type6;
		call type8;
		call type10;
		call type11;
		call type12;
		call type13;
		call type14;
		call type18;
		call type22;
		call type23;
		call type25;
		call type27;
		call type31;
		call type33;
		call type34;
		call type36;
		call type38;
		call type39;
		call type40;
		call type42;
		;
		call type44;
		call type45;
	end;
	if error$flag then call error;
	end dis;

disem: procedure (disloc) address public;
	declare disloc pointer;
	declare nprefix byte;
	disem$ptr = disloc;
	nprefix = 0;
	do while true;
		mnemonic$index = instr$table (disem$byte (0) * 2);
		instr$type = instr$table (disem$byte (0) * 2 + 1);
		if instr$type = 0ffh and nprefix < 3 then
			do;
				call print$mnemonic;
				nprefix = nprefix + 1;
			end;
		else
			do;
				if instr$type = 0ffh then instr$type = 1;
				call dis;
				return disem$offset;
			end;
	end;
	end disem;

end disem86;
