;;
;; 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: