;;
;; Simple monitor program.
;;
;; Accept strings from STDIN, and execute them.
;;
;; Built-in commands
;;
;;   [c]all xxxx  -> Call the routine at XXXX
;;
;;   [d]ump       -> Dump 16 bytes of RAM at a given address.
;;
;;   [i]nput      -> Enter bytes
;;
;; Other input will generate an "ERR" message, and be ignored.
;;
 
org 0
 
    ;;
    ;; Ensure we have a stack-pointer setup, with some room.
    ;;
    ld sp, stack_start
 
;;
;; Entry-point to the monitor.
;;
;; Read text into into `input_buffer`, appending to the buffer until a newline
;; is seen, then invoke `process` to handle the input.
;;
monitor:
        ;; show the prompt.
        ld a, '>'
        out (1),a
 
 
        ld hl, input_buffer
monitor_input_loop:
        ;; overwrite an previous input
        ld (hl), '\n'
 
        ;; read and store the new character
        in a,(1)
        ld (hl),a
 
        ;; was it a newline?  If so process.
        cp '\n'
        jr z, process_input_line
 
        ;; Otherwise loop round for more input.
        inc hl
        jr monitor_input_loop
 
 
 
;;
;; process_input_line is called when the monitor has received a complete
;; newline-terminated line of text.
;;
;; We process the contents by looking for commands we understand, if we see
;; input we don't recognize we show a message and return, otherwise we invoke
;; the appropriate handler.
;;
;;  C => CALL
;;  D => DUMP
;;  I => INPUT
;;
process_input_line:
 
        ld hl, input_buffer
        ld a, (hl)
 
        ;; C == CALL
        cp 'c'
        jr z, call_handler
        cp 'C'
        jr z, call_handler
 
        ;; D == DUMP
        cp 'd'
        jr z, dump_handler
        cp 'D'
        jr z, dump_handler
 
        ;; I == INPUT
        cp 'i'
        jr z, input_handler
        cp 'I'
        jr z, input_handler
 
        ;;
        ;; Unknown command: show a message and restart our monitor
        ;;
        ;; We just show "ERR" which is simple, and saves bytes compared to
        ;; outputting a longer message and using a print-string routine.
        ;;
        ld a, 'E'
        out (1),a
        ld a, 'R'
        out (1), a
        out (1), a
        ld a, '\n'
        out (1),a
        jr monitor
 
 
 
 
;;
;; Call is invoked with the address to call
;;
;; For example "C0003" will call the routine at 0x0003
;;
call_handler:
 
        ;; Our input-buffer will start with [cC], so we start looking at the
        ;; next character.
        ld hl, input_buffer+1
 
        ;; Read the address to call into BC
        call read_16_bit_ascii_number
 
        ;; We'll be making a call, so we need to have the return
        ;; address on the stack so that when the call'd routine ends
        ;; execution goes somewhere sane.
        ;;
        ;; We'll want to re-load the monitor, so we'll store the
        ;; entry point on the stack
        ;;
        ld hl,monitor
        push hl
 
        ;; Now we jump, indirectly, to the address in the BC register.
        push bc
        ret
 
 
 
;;
;; Dump 16 bytes from the current dump_address
;;
;; We're called with either "D" to keep going where we left off or
;; "D1234" if we should start at the given offset.
;;
dump_handler:
 
        ;; Our input-buffer will start with [dD], so we start looking at the
        ;; next character.
        ld hl, input_buffer+1
 
        ;; Look at the next input-byte.  If empty then no address.
        ld a, (hl)
        cp '\n'
        jr z, dump_handler_no_number
 
        ;; OK we expect an (ASCII) address following HL - read it into BC.
        call read_16_bit_ascii_number
        ld (dump_address), bc
 
dump_handler_no_number:
        ;; The address we start from
        ld hl, (dump_address)
        ;; show the address
        call output_16_bit_number
 
        ;; Loop to print the next 16 bytes at that address.
        ld b, 16
dump_byte:
        ;; show a space
        ld a, ' '
        out (1), a
 
        ;; show the memory-contents.
        ld c, (hl)
        call output_8_bit_number
        inc hl
        djnz dump_byte
 
        ;; all done
        ld a, '\n'
        out (1),a
 
        ;; store our updated/final address.
        ld (dump_address), hl
jmp_monitor:
        jr monitor
 
 
 
;;
;; Input handler allows code to be assembled at a given address
;;
;; Usage is:
;;
;;  I01234 01 02 03 04 0f
;;
;; i.e. "I<address> byte1 byte2 .. byteN"
;;
;; If there is no address keep going from the last time, which means this
;; works as you expect:
;;
;;   I1000 01 03
;;   I 03 04 0F
;;
input_handler:
        ;; Our input-buffer will start with [iI], so we start looking at the
        ;; next character.
        ld hl, input_buffer+1
 
        ;; Look at the next input-byte.  If it is a space then no address was
        ;; given, so we keep appending bytes to the address set previously.
        ld a, (hl)
        cp ' '
        jr z, input_handler_no_address
 
        ;; OK we expect an (ASCII) address following HL - Read it into BC.
        call read_16_bit_ascii_number
        ld (input_address), bc
 
input_handler_no_address:
 
        ;; HL contains the a string.  Get the next byte
        ld a,(hl)
        inc hl
 
        ;; space? skip
        cp ' '
        jr z, input_handler_no_address
 
        ;; newline? If so we're done
        cp '\n'
        jr z, jmp_monitor
 
        ;; OK then we have a two-digit number
        dec hl
        call read_8_bit_ascii_number
 
        ;; store the byte in RAM
        ld bc, (input_address)
        ld (bc), a
 
        ;; bump to the next address
        inc bc
        ld (input_address), bc
 
        ;; continue
        jr input_handler_no_address
 
 
 
 
;;
;; Convert a 4-digit ASCII number, pointed to by HL to a number.
;; Return that number in BC.
;;
read_16_bit_ascii_number:
	;; HL is a pointer to a four-char string
	;; This is read as a 16 bit hex number
	;; The number is stored in BC
	call		read_8_bit_ascii_number
	ld		b, a
	call		read_8_bit_ascii_number
	ld		c, a
	ret
 
 
 
 
 
;;
;; Read the two-digit HEX number from HL, and convert to an integer
;; stored in the A-register.
;;
;; HL will be incremented twice.
;;
read_8_bit_ascii_number:
	ld		a, (hl)
        ;; is it lower-case?  If so upper-case it.
        cp 'a'
        jr c, read_8_bit_ascii_number_uc
        cp 'z'
        jr nc, read_8_bit_ascii_number_uc
        sub a, 32
read_8_bit_ascii_number_uc:
        call		read_8_bit_ascii_number_hex
        add		a, a
        add		a, a
        add		a, a
        add		a, a
        ld		d, a
	inc		hl
        ld		a, (hl)
        call		read_8_bit_ascii_number_hex
        or		d
	inc		hl
        ret
read_8_bit_ascii_number_hex:
        sub		a, '0'
        cp		10
        ret		c
        sub		a,'A'-'0'-10
        ret
 
 
;;
;; Display the 16-bit number stored in HL in hex.
;;
output_16_bit_number:
 
   ld  c,h
   call  output_8_bit_number
   ld  c,l
   call output_8_bit_number
   ret
 
;;
;; Display the 8-bit number stored in C in hex.
;;
output_8_bit_number:
   ld  a,c
   rra
   rra
   rra
   rra
   call  Conv
   ld  a,c
Conv:
   and  $0F
   add  a,$90
   daa
   adc  a,$40
   daa
   out (1),a
   ret
 
 
 
;;;;;;;;
;;;;;;;; RAM stuff
;;;;;;;;
 
;;
;; Here we store some values.
;;
 
;; DUMP: We track of the address from which we're dumping.
dump_address:
        db 0,0
;; INPUT: Keep track of the address to which we next write.
input_address:
        db 0,0
 
;; We don't nest calls too deeply ..
stack_end:
        db 0, 0
        db 0, 0
        db 0, 0
        db 0, 0
        db 0, 0
        db 0, 0
        db 0, 0
        db 0, 0
        db 0, 0
        db 0, 0
stack_start:
 
;; Command-line input buffer.
input_buffer: