Subleq is an example of a One-Instruction Set Computer (OISC).

It is named after its only instruction, which is SUbtract and Branch if Less than or EQual to zero.

Task
Your task is to create an interpreter which emulates a SUBLEQ machine.

The machine's memory consists of an array of signed integers.   These integers may be interpreted in three ways:

   simple numeric values 
   memory addresses 
   characters for input or output
Any reasonable word size that accommodates all three of the above uses is fine.

The program should load the initial contents of the emulated machine's memory, set the instruction pointer to the first address (which is defined to be address 0), and begin emulating the machine, which works as follows:

   Let A be the value in the memory location identified by the instruction pointer;   let B and C be the values stored in the next two consecutive addresses in memory.
   Advance the instruction pointer three words, to point at the address after the address containing C.
   If A is   -1   (negative unity),   then a character is read from the machine's input and its numeric value stored in the address given by B.   C is unused.
   If B is   -1   (negative unity),   then the number contained in the address given by A is interpreted as a character and written to the machine's output.   C is unused.
   Otherwise, both A and B are treated as addresses.   The number contained in address A is subtracted from the number in address B (and the difference left in address B).   If the result is positive, execution continues uninterrupted; if the result is zero or negative, the number in C becomes the new instruction pointer.
   If the instruction pointer becomes negative, execution halts.
Your solution may initialize the emulated machine's memory in any convenient manner, but if you accept it as input, it should be a separate input stream from the one fed to the emulated machine once it is running. And if fed as text input, it should be in the form of raw subleq "machine code" - whitespace-separated decimal numbers, with no symbolic names or other assembly-level extensions, to be loaded into memory starting at address   0   (zero).

For purposes of this task, show the output of your solution when fed the below   "Hello, world!"   program.

As written, this example assumes ASCII or a superset of it, such as any of the Latin-N character sets or Unicode;   you may translate the numbers representing characters (starting with 72=ASCII 'H') into another character set if your implementation runs in a non-ASCII-compatible environment. If 0 is not an appropriate terminator in your character set, the program logic will need some adjustment as well.

15 17 -1 17 -1 -1 16 1 -1 16 3 -1 15 15 0 0 -1 72 101 108 108 111 44 32 119 111 114 108 100 33 10 0
The above "machine code" corresponds to something like this in a hypothetical assembler language for a signed 8-bit version of the machine:

start:
    0f 11 ff subleq (zero), (message), -1
    11 ff ff subleq (message), -1, -1    ; output character at message
    10 01 ff subleq (neg1), (start+1), -1
    10 03 ff subleq (neg1), (start+3), -1
    0f 0f 00 subleq (zero), (zero), start
; useful constants
zero: 
    00      .data 0  
neg1: 
    ff      .data -1
; the message to print
message: .data "Hello, world!\n\0"
    48 65 6c 6c 6f 2c 20 77 6f 72 6c 64 21 0a 00


	;;;	-------------------------------------------------------------
	;;;	SUBLEQ interpreter that runs under MS-DOS.
	;;;	The word size is 16 bits, and the SUBLEQ program gets a 64KB
	;;;	(that is, 32K Subleq words) address space. 
	;;;	The SUBLEQ program is read from a text file given on the
	;;;	command line, I/O is done via the console. 
	;;;	Console I/O is normally raw, but with the /T parameter,
	;;;	line ending translation is done (CRLF <> LF). 
	;;;	-------------------------------------------------------------
	bits	16
	cpu	8086
	;;;	MS-DOS system calls
getch:	equ	1h	; Get character
putch:	equ	2h	; Print character
puts:	equ	9h	; Print string
fopen:	equ	3Dh	; Open file
fclose:	equ	3Eh	; Close file
fread:	equ	3Fh	; Read from file
alloc:	equ	48h	; Allocate memory block
resize:	equ	4Ah	; Change size of memory block
exit:	equ	4Ch	; Exit to DOS
	;;;	Constants
RBUFSZ:	equ	1024	; 1K read buffer 
CR:	equ	13	; CR and LF 
LF:	equ	10
	;;;	RAM locations
cmdlen:	equ	80h	; Length of command line
cmdlin:	equ	81h	; Contents of command line
	org	100h
section	.text
	clc			; Make sure string instructions go forward
	;;;	-- Memory initialization ------------------------------------
	;;;	This is a .COM file. This means MS-DOS gives us all available
	;;;	memory starting at CS:0, and CS=DS=ES=SS. This means in order
	;;;	to allocate a separate 64k segment for the SUBLEQ memory
	;;;	space, we will first need to free all memory we're not using. 
	;;;	-------------------------------------------------------------
memini:	mov	sp,memtop	; Point SP into memory we will be keeping
	mov	dx,emem		; Set up a pointer to the memory error msg
	mov	ah,resize	; Reallocate current block
	mov	bx,sp		; Size is in paragraphs (16 bytes), and the 
	mov	cl,4		; assembler will not let me shift a label at
	shr	bx,cl		; compile time, so we'll do it at runtime.
	inc	bx		; BX=(memtop>>4)+1; memtop in last paragraph.	
	int	21h
	jnc	.alloc		; Carry not set = allocate memory
	jmp	die		; Otherwise, error (jump > 128 bytes)
	;;;	Allocate a 64K block for the SUBLEQ program's address space
.alloc:	mov	ah,alloc	; Allocate 64K (4096 paragraphs) for the
	mov	bx,4096		; SUBLEQ program. Because that is the size of
	int	21h		; an 8086 segment, we get free wraparound,
	jnc	.zero		; and we don't have to worry about bounds
	jmp	die		; checking.
	;;;	Zero out the memory we're given
.zero:	push	ax		; Keep SUBLEQ segment on stack.
	mov	es,ax		; Let ES point into our SUBLEQ segment.
	mov	cx,32768	; 32K words = 64K bytes to set to zero.
	xor	ax,ax		; We don't have to care about where DI is,
	rep	stosw		; since we're doing all of ES anyway.
	;;;	-- Parse the command line and open the file -----------------
	;;;	A filename should be given on the command line, which should
	;;;	be a text file containing (possibly negative) integers
	;;;	written in base 10. For "efficiency", we read the file 1K
	;;;	at a time into a buffer, rather than character by character.
	;;;	We also handle the '/T' parameter here. 
	;;;	-------------------------------------------------------------
rfile:	mov	dx,usage	; Print 'usage' message if no argument 
	mov	di,cmdlin	; 0-terminate command line for use with fopen 
	xor	bh,bh		; We'll use BX to index into the command line
	mov	bl,[cmdlen]	; Length of command line
	test	bl,bl		; If it's zero, no argument was given
	jnz	.term		; If not zero, go ahead
	jmp 	die		; Otherwise, error (again, jump > 128 bytes)
.term:	mov	[di+bx],bh	; Otherwise, 0-terminate
	mov	ax,ds		; Let ES point into our data segment
	mov	es,ax		; (in order to use SCASB).
.skp:	mov	al,' '		; Skip any preceding spaces
	mov	cx,128		; Max. command line length
	repe	scasb
	dec	di		; As usual, SCASB goes one byte too far
	mov	al,[di]		; If we're at zero now, we don't have an
	test	al,al		; argument either, so same error. 
	jnz	.parm		; (Again, jump > 128 bytes)
	jmp	die
.parm	cmp	al,'/'		; Input parameter?
	jne	.open		; If not, this is the filename, open it
	inc	di		; If so, is it 'T' or 't'?
	mov	al,[di]
	inc	di		; Skip past it
	mov	dl,[di]		; And is the next one a space again?
	cmp	dl,' '
	je	.testp		; If so, it's potentially valid
.perr:	mov	dx,eparm	; If not, print error message
	jmp	die
.testp:	or	al,32		; Make lowercase
	cmp	al,'t'		; 'T'?
	jne	.perr		; If not, print error message
	inc	byte [trans]	; If so, turn translation on 
	jmp	.skp		; And then get the filename
.open:	mov	ax,fopen<<8	; Open file for reading (AL=0=O_RDONLY)
	mov	dx,di		; 0-terminated path on the command line
	int	21h
	jnc	.read		; Carry not set = file opened
	mov	dx,efile	; Otherwise, file error (we don't much care
	jmp	die		; which one, that's too much work.)
.read:	pop	es 		; Let ES be the SUBLEQ segment (which we
	xor	di,di		; pushed earlier), and DI point to 1st word.
	mov	bp,ax		; Keep the file handle in BP.
	xor	cx,cx		; We have read no bytes yet.
	;;;	-- Read and parse the file ----------------------------------
	;;;	We need to read 16-bit signed integers from the file,
	;;;	in decimal. The integers are separated by whitespace, which
	;;;	for simplicity's sake we'll say is ASCII space and _all_
	;;;	control characters. BP, CX and SI are used as state to 
	;;;	emulate character-based I/O, and so must be preserved;
	;;;	furthermore, DI is used as a pointer into the SUBLEQ memory.
	;;;	-------------------------------------------------------------
skipws:	call	fgetc		; Get next character
	jc	fdone		; If we get EOF, we're done.
	cmp	al,' '		; Is it whitespace? (0 upto ' ' inclusive)
	jbe	skipws		; Then keep skipping
rdnum:	xor	dl,dl		; DL is set if number is negative
	xor	bx,bx		; BX will keep the number
	cmp	al,'-'		; Is first character a '-'?
	jne	.dgt		; If not, it's positive 
	inc	dx		; Otherwise, set DL,
	call	fgetc		; and get next character.
	jc	fdone
.dgt:	mov	dh,al		; Store character in DH
	sub	dh,'0'		; Subtract '0'
	cmp	dh,9		; Digit is [0..9]?
	jbe	.dgtok		; Then it is OK
	jmp	fmterr		; Otherwise, format error (jump > 128)
.dgtok:	mov	ax,bx		; BX *= 10 (without using MUL or SHL BX,CL;
	shl	bx,1		; since we can't spare the registers).
	shl	bx,1
	add	bx,ax
	shl 	bx,1
	mov	al,dh		; Load digit into AL
	cbw			; Sign extend (in practice just sets AH=0)
	add	bx,ax		; Add it into BX
	call	fgetc		; Get next character
	jc	dgteof		; EOF while reading num is special
	cmp	al,' '		; If it isn't whitespace, 
	ja	.dgt		; then it's the next digit.
	test	dl,dl		; Otherwise, number is done. Was it negative?
	jz 	.wrnum		; If not, write it to SUBLEQ memory
	neg	bx		; Otherwise, negate it
.wrnum:	mov	ax,bx		; ...and _then_ write it.
	stosw
	jmp	skipws		; Skip any other wspace and get next number
dgteof:	test	dl,dl		; If we reached EOF while reading a number,
	jz	.wrnum		; we need to do the same conditional negation
	neg	bx		; and write out the number that was still in
.wrnum:	mov	ax,bx		; BX.
	stosw
fdone:	mov	ah,fclose	; When we're done, close the file.
	mov	bx,bp		; (Not strictly necessary since we've only
	int	21h		; read, so we don't care about errors.)
	;;;	-- Run the SUBLEQ code --------------------------------------
	;;;	SI = instruction pointer. An instruction A B C is loaded into
	;;;	BX DI AX respectively. Note that SUBLEQ addresses words,
	;;;	whereas the 8086 addresses bytes, so the addresses all need
	;;;	to be shifted left once before being used. 
	;;;	-------------------------------------------------------------
subleq:	xor	si,si		; Start with IP=0
	mov	cl,[trans]	; CL = \r\n translation on or off
	mov	ax,es		; Set DS=ES=SUBLEQ segment
	mov	ds,ax
	;;;	Load instruction
.step:	lodsw			; Load A
	mov	bx,ax		; BP = A
	lodsw			; Load B
	mov	di,ax		; DI = B
	lodsw			; Load C (AX=C)
	;;;	Check for special cases
	inc	bx		; BX=-1 = read byte	
	jz	.in		; If ++BP==0, then read character  
	dec	bx		; Restore BX
	inc	di		; If ++DI==0, then write character
	jz	.out		
	dec	di		; Restore DI
	;;;	Do the SUBLEQ instruction
	shl	di,1		; Addresses must be doubled since SUBLEQ
	shl	bx,1		; addresses words and we're addressing bytes
	mov	dx,[di]		; Retrieve [B]
	sub	dx,[bx]		; DX = [B] - [A]
	mov	[di],dx		; [B] = DX
	jg	.step		; If [B]>[A], (i.e. [B]-[A]>=0), do next step
	shl	ax,1		; Otherwise, AX*2 (C) becomes the new IP
	mov	si,ax
	jnc	.step		; If high bit was 0, next step
	mov	ax,exit<<8	; But otherwise, it was negative, so we stop
	int	21h
	;;;	Read a character from standard input
.in:	mov	ah,getch	; Input: read character into AL
	int	21h		
	cmp	al,CR		; Is it CR? 
	je	.crin		; If not, just store the character
.sto:	xor	ah,ah		; Character goes in low byte of word
	shl	di,1		; Word address to byte address
	mov	[di],ax		; Store character in memory at B
	jmp	.step		; And do next step
	;;;	Pressing enter only returns CR; not CR LF on two reads, 
	;;;	therefore on CR we give LF instead when translation is on.
.crin:	test	cl,cl		; Do we even want translation?
	jz	.sto		; If not, just store the CR and leave it
	mov	al,LF		; But if so, use LF instead
	jmp	.sto
	;;;	Write a character to standard output
.out: 	shl	bx,1		; Load character from [A]
	mov	dl,[bx]		; We only need the low byte
	mov 	ah,putch	; Set AH to print the character
	cmp	dl,LF		; Is it LF?
	je	.lfo		; Then handle it separately
.wr:	int	21h
	jmp	.step		; Do next step
	;;;	LF needs to be translated into CR LF, so we need to print the
	;;;	CR first and then the LF, if translation is on.
.lfo:	test	cl,cl		; Do we even want translation?
	jz	.wr		; If not, just print the LF
	mov	dl,CR		; If so, print a CL first
	int	21h
	mov	dl,LF		; And then a LF
	jmp	.wr
	;;;	-- Subroutine: get byte from file buffer. --------------------
	;;;	If the buffer is empty, fill with more bytes from file.
	;;;	On EOF, return with carry set. 
	;;;	Input:  BP = file handle, CX = bytes left in buffer,
	;;;	        SI = current pointer into buffer.
	;;;	Output: AL = byte, CX and SI moved, other registers preserved
	;;;	-------------------------------------------------------------
fgetc:	test	cx,cx		; Bytes left?
	jz	.read		; If not, read from file
.buf:	lodsb			; Otherwise, get byte from buffer
	dec	cx		; One fewer byte left
	ret			; And we're done. (TEST clears carry, LODSB
				; and DEC don't touch it, so it's clear.)
.read:	push	ax		; Keep AX, BX, DX
	push	bx
	push	dx
	mov	ah,fread	; Read from file,
	mov	bx,bp		; BP = file handle,
	mov	cx,RBUFSZ	; Fill up entire buffer if possible,
	mov	dx,fbuf		; Starting at the start of buffer,
	mov	si,dx		; Also start returning bytes from there.
	int	21h
	jc	.err		; Carry set = read error
	mov	cx,ax		; CX = amount of bytes read
	pop	dx		; Restore AX, BX, DX
	pop	bx
	pop 	ax
	test	cx,cx		; If CX not zero, we now have data in buffer
	jnz	.buf		; So get first byte from buffer
	stc			; But if not, EOF, so set carry and return
	ret
.err:	mov	dx,efile	; On error, print the file error message
	jmp	die		; and stop
	;;;	Parse error (invalid digit) ---------------------------------
	;;;	Invalid character is in AL. BP, CX, SI still set to read from
	;;;	file. 
fmterr:	mov	dx,ds		; Set ES=DS
	mov	es,dx 
	mov	dl,5		; Max. 5 characters
	mov	di,eparse.dat	; DI = empty space in error message
.wrch:	stosb			; Store character in error message
	call	fgetc		; Get next character 
	jc	.done		; No more chars = stop
	dec	dl		; If room left, 
	jnz	.wrch		; write next character
.done:	mov	dx,eparse	; Use error message with offender written in
				; And fall through to stop the program
	;;;	Print the error message in [DS:DX] and terminate with
	;;;	errorlevel 2. 
die:	mov	ah,puts
	int	21h
	mov	ax,exit<<8 | 2
	int	21h
section	.data
usage:	db	'SUBLEQ [/T] <file> - Run the SUBLEQ program in <file>.$'
efile:	db	'Error reading file.$'
eparm:	db	'Invalid parameter.$'
emem:	db	'Memory allocation failure.$'
eparse:	db	'Invalid integer at: '
.dat:	db	'     $'	; Spaces to be filled in by error routine
trans:	db	0	; Will be set if CRLF translation is on
section	.bss	
fbuf:	resb	RBUFSZ	; File buffer
stack:	resw	128	; 128 words for main stack (should be enough)
memtop:	equ	$