>'BOOK1P1 - uSEFUL sUBROUTINES
 
     nOW THAT WE HAVE DISCUSSED THE INTERRUPT SERVICES IN DETAIL, LET'S TAKE A BREAK AN COVER SOME SIMPLER ROUTINES.  a VERY SIMPLE, YET VERY POWERFUL SUBROUTINE IS ONE THAT WILL LOOKUP VALUES IN A TABLE AND EXECUTE THEIR ASSOCIATED ADDRESS.  i USE THIS SUBROUTINE IN ALMOST EVERY PROGRAM i WRITE BECAUSE IT IS SO VERSATILE.  iT WILL TAKE A SINGLE CHARACTER IN THE a REGISTER, AND LOOK UP ITS CORRESPONDING VECTOR IN A TABLE POINTED TO BY hl AND TERMINATED WITH A ZERO.  iT IS VERY USEFUL IN VECTORING TO MENU SELECTIONS.
 
start    ld        hl,messg   ;MENU STRING
         call      @disply    ;DISPLAY TO THE VIDEO
         ld        b,1        ;ONE CHARACTER TO INPUT
         call      @getstr    ;GET ONE KEY FROM KEYBOARD
         jr        c,exit     ;<break> KEY PRESSED
         jr        z,start    ;<enter> ONLY PRESSED
         ld        hl,table   ;LOOKUP TABLE FOR RESPONSES
         call      @gotabl    ;VECTOR IF VALID RESPONSE
         jr        start      ;NOT VALID
table    db        '1'        ;IF THIS KEY WAS PRESSED
         dw        menu1      ;THEN BRANCH TO THIS ADDRESS
         db        '2'        ;LIKEWISE
         dw        menu2
         db        'x'        ;ETC., ETC.
         dw        menux
         db        0          ;THIS TERMINATES THE TABLE
 
    tHE ABOVE IS A POSSIBLE USE OF THE @gotabl SUBROUTINE TO BRANCH TO A CORRECT MENU SELECTION.  nOW HERE IS THE CODE.
 
@gotabl  inc       (hl)       ;CHECK FOR 0 TERMINATOR
         dec       (hl)       ;DON'T NEED REGISTERS THIS WAY
         ret       z          ;NOT IN THE TABLE
         cp        (hl)       ;IS THIS THE ONE WE WANT?
         jr        z,havtbl   ;HAVE THE ONE, GET THE ADDRESS
         inc       hl         ;ELSE POINT TO THE NEXT ENTRY
         inc       hl         ;WHICH IS 3 BYTES AWAY
         inc       hl
         jr        @gotabl    ;CHECK THE NEXT ONE
havtbl   inc       hl         ;POINT TO THE lsb
         ld        a,(hl)     ;GET IT
         inc       hl         ;POINT TO THE msb
         ld        h,(hl)     ;GET IT
         ld        l,a        ;hl = ADDRESS FROM TABLE
         ex        (sp),hl    ;REMOVE CALLER ADDRESS FROM STACK
         ret                  ;AND INSTEAD, GO TO TABLE VECTOR
 
    tHIS COVERS A TABLE VECTOR USING A SINGLE CHARACTER AS A LOOKUP BYTE.  aLL THE REGISTERS ARE PRESERVED EXCEPT THE hl PAIR.  tHIS ALLOWS SEVERAL PARAMETERS TO BE PASSED TO THE SUBROUTINE IN REGISTERS, AND THEREBY NO REQUIRING HARD MEMORY FOR THEM.  tHE FOLLOWING SUBROUTINES ARE OTHERS THAT USE A TABLE TO LOCATE A VECTOR ADDRESS, BUT THEY CAN LOOK UP AN ENTIRE STRING OF CHARACTERS TO CHECK FOR MATCHES.  iN THE @getstr SUBROUTINE MENTIONED DESCRIBED ELSEWHERE IN THIS BOOK, WE HAVE DEFINED THAT THE hl REGISTERS WILL BE POINTING TO THE INPUT STRING ENTERED FROM THE KEYBOARD, TERMINATED WITH A c/r (0dh).  lET'S USE THE de REGISTER TO POINT TO THE TABLE OF COMMAND WORDS AND VECTORS.
    iN TECHNIQUE #1, WE WILL ASSUME THAT ALL OF THE WORDS IN THE TABLE ARE EXACTLY 6 CHARACTERS LONG.  tHIS WILL ALLOW US TO COMBINE THE VECTORS INTO THE SAME TABLE AS THE WORDS.
 
start    ld        hl,mesg    ;PROMPT MESSAGE
         call      @disply    ;TO THE VIDEO
         ld        b,6        ;6 CHARS MAX INPUT
         call      @getstr    ;GET FROM KEYBOARD
         jr        c,exit     ;<break>
         jr        z,start    ;<enter> (NO OTHER KEYS)
         ld        de,table   ;TABLE OF COMMANDS
         call      @gotabl1   ;LOOKUP RESPONSE IN TABLE
         jr        start      ;NOT FOUND, TRY AGAIN
table    db        'word1 '   ;MUST BE 6 CHARS, PAD WITH SPACES
         dw        addr1      ;ADDRESS TO BRANCH TO IF MATCHED
         db        'word2 '   ;NEXT WORD
         dw        addr2      ;ADDRESS TO BRANCH
         db        'xx    '   ;ETC., ETC.
         dw        xx         ;ADDRESS OF ABOVE WORD
         db        0          ;TERMINATOR FOR TABLE
 
@gotabl1 ld        a,(de)     ;GET TABLE BYTE
         or        a          ;SET FLAGS FOR THE BYTE
         ret       z          ;TERMINATOR BYTE
         push      bc         ;NEED TO USE THIS
         ld        b,6        ;6 BYTES TO COMPARE
         call      @icomp     ;SEE IF THEY'RE THE SAME
         push      af         ;SAVE THE FLAG REGISTER
         ld        bc,8       ;# OF BYTES IN EACH ENTRY
         ex        de,hl      ;hl => TABLE
         add       hl,bc      ;NEXT ENTRY
         ex        de,hl      ;de => NEXT TABLE ENTRY
         pop       af         ;RESTORE FLAGS
         pop       bc         ;RESTORE THIS TOO
         jr        nz,@gotabl1;NOT THIS ENTRY
         ex        de,hl      ;hl => NEXT ENTRY
         dec       hl         ;msb OF ADDRESS
         ld        a,(hl)     ;GET IT
         dec       hl         ;POINT TO lsb
         ld        l,(hl)     ;GET IT
         ld        h,a        ;hl = ADDRESS
         ex        (sp),hl    ;REPLACE CALLER ADDRESS WITH VECTOR
         ex        de,hl      ;hl STILL POINTS TO THE STRING
         ret                  ;GO TO VECTOR, bc UNTOUCHED
 
    iN TECHNIQUE # 2, WE WILL USE THE EXACT SAME SAMPLE ROUTINE GIVEN ABOVE, BUT THIS TIME WE WILL ASSUME THAT THE WORDS ARE OF VARIABLE LENGTH.  fOR THIS, WE WILL NEED TWO TABLES, ONE FOR THE WORDS, AND ONE FOR THE VECTORS.  bECAUSE NORMAL ASCII CHARACTERS DO NOT USE BIT 7, WE WILL USE IT TO INDICATE THE START CHARACTER IN A WORD.  sINCE WE ONLY WANT TO PASS A SINGLE TABLE POINTER TO THE SUBROUTINE, WE WILL PLACE THE ADDRESS OF THE SECOND TABLE IN THE FIRST 2 BYTES OF THE FIRST TABLE.  lET'S FIRST RE-DEFINE OUR LOOKUP TABLE TO BE USED.
 
table    dw        table2     ;ADDRESS OF SECOND TABLE
         db        'w'+80H,'ORD1'
; THIS DEFINES THE FIRST WORD IN THE TABLE, WITH THE HIGH BIT SET
         db        'w'+80H,'ORD2'  ;DO NOT NEED GAPS BETWEEN
         db        'x'+80H,'XX'    ;ETC., ETC.
         db        80H             ;HIGH BIT only FOR TERMINATOR
table2   dw        addr1      ;ADDRESS FOR WORD 1
         dw        addr2      ;WORD 2
         dw        addrx      ;ETC., ETC. FOR EACH ENTRY
 
    aND THE ACTUAL SUBROUTINE TO PARSE THE TABLES:
 
@gotabl2 push      bc         ;WE NEED THESE REGISTERS
         push      de         ;SAVE TABLE START
         inc       de         ;de = START OF WORDS -1
         ld        c,0        ;USE AS WORD COUNTER (POSITION)
loop1    inc       de         ;BUMP POINTER TO LOCATE WORD START
         ld        a,(de)     ;GET THE BYTE
         bit       7,a        ;HIGH BIT SET?
         jr        z,loop1    ;GO TILL START FOUND
         and       7fh        ;SEE IF TERMINATOR BYTE
         jr        z,notit    ;YES, RETURN, NO MATCHES HERE
         cp        (hl)       ;FIRST BYTE MATCH?
         call      z,scomp    ;COMPARE REST IF YES
         jr        z,gotit    ;GOT THE RIGHT ONE, LOOKUP ADDRESS
         inc       c          ;BUMP WORD COUNTER
         jr        loop1      ;BUMP POSITION TO NEXT WORD
notit    pop       de         ;RESTORE STACK
         pop       bc
         ret                  ;NOT FOUND IN TABLE
gotit    ex        de,hl      ;de => INPUT STRING
         pop       hl         ;GET START OF TABLE
         ld        a,(hl)     ;GET lsb OF TABLE 2
         inc       hl         ;POINT TO msb
         ld        h,(hl)     ;GET IT
         ld        l,a        ;hl => TABLE 2 (VECTORS)
         push      hl         ;PUT IT BACK
         ld        l,c        ;c = WORD NUMBER
         ld        h,0        ;hl = WORD NUMBER
         add       hl,hl      ;* 2 FOR TABLE
         pop       bc         ;GET START OF TABLE 2
         add       hl,bc      ;POINT TO THE ENTRY
         pop       bc         ;RESTORE STACK TO ORIGINAL
         ld        a,(hl)     ;GET THE lsb OF THE VECTOR
         inc       hl         ;POINT TO THE msb
         ld        h,(hl)     ;GET IT
         ld        l,a        ;hl = VECTOR
         ex        (sp),hl    ;REMOVE CALLER ADDRESS, PUT VECTOR
         ex        de,hl      ;hl STILL POINTS TO THE STRING
         ret                  ;GO TO CALLER, hl bc INTACT
scomp    push      hl         ;SAVE START OF STRING
         push      de         ;SAVE CURRENT TABLE
scplp    inc       hl         ;POINT TO NEXT ENTRY
         inc       de         ;THE FIRST ONE ALREADY MATCHED
         ld        a,(de)     ;GET A BYTE
         bit       7,a        ;START OF A WORD?
         jr        nz,scpret  ;ALL BYTES MATCHED, RETURN ok
         cp        (hl)       ;SAME AS STRING
         jr        z,scplp    ;DO MORE IF YES
         jr        scpret+1   ;RETURN WITH nz FLAG 
scpret   xor                  ;SET z FLAG FOR MATCH
         pop       de         ;RESTORE REGISTERS
         pop       hl         ;DONE, z = MATCH, nz = NO MATCH
         ret
 
    wE HAVE NOW DEFINED VARIOUS WAY ON HOW TO IMPLEMENT TABLES FOR BRANCHING PURPOSES.  lETS LOOK AT VARIOUS WAYS ON COMPARING TWO STRINGS TO SEE IF THEY ARE EQUAL.
    iF WE HAVE TWO STRINGS OF A KNOWN LENGTH WHICH WE WILL SUPPLY TO THE SUBROUTINE IN THE b REGISTER, AND WE WANT TO SEE THAT THE TWO STRINGS ARE exactly EQUAL INCLUDING THE case (UPPER/LOWER), THEN WE MAY USE THE FOLLOWING TYPE OF COMPARE.  tHIS IS TERMED A "CASE DEPENDENT" COMPARE.
 
@dcomp   push      hl         ;SAVE START OF STRINGS
         push      de
dcplp    ld        a,(de)     ;GET A BYTE
         cp        (hl)       ;SAME?
         jr        nz,dcpret  ;NOPE, RETURN WITH nz FLAG
         inc       hl         ;BUMP STRING
         inc       de         ;BUMP TABLE
         djnz      dcplp      ;DO FOR LENGTH IN b REGISTER
dcpret   pop       de         ;RESTORE REGISTERS
         pop       hl         ;FLAGS NOT AFFECTED BY THIS
         ret                  ;z = MATCH, nz = NOT MATCH
 
    sOMETIMES, WE MAY WANT TO SEE IF STRINGS MATCH REGARDLESS WHETHER OR NOT EITHER STRING CONTAINS UPPER OR LOWER CASE CHARACTERS.  tHIS IS CALLED A "CASE INDEPENDENT" COMPARE, AND A CAPITAL a IS TREATED EQUAL TO A LOWERCASE A, ETC.  tO DO THIS, WE WILL CONVERT EACH BYTE TO UPPER CASE (SO THEY ARE THE SAME) BEFORE ACTUALLY COMPARING THE BYTES.  wE WILL USE THE c REGISTER TO HOLD THE TEMPORARY VALUES AS WE COMPUTE THEM.
 
@icomp   push      hl         ;SAVE REGISTERS AGAIN
         push      de
icplp    ld        a,(de)     ;GET TABLE BYTE
         call      @ucase     ;CONVERT TO UPPER CASE
         ld        c,a        ;SAVE IT HERE
         ld        a,(hl)     ;GET STRING BYTE
         call      @ucase     ;MAKE THIS UPPER CASE TOO
         cp        c          ;SAME?
         jr        nz,icpret  ;NOPE, RETURN WITH nz FLAG
         inc       hl         ;BUMP STRING POINTER
         inc       de         ;BUMP TABLE POINTER
         djnz      icplp      ;GO FOR b REGISTER BYTES
icpret   pop       de         ;RESTORE STACK
         pop       hl
         ret                  ;z = MATCH, nz = NO MATCH
 
    aLSO, SOMETIMES WE MAY WANT TO ALLOW THE USER TO INSERT A CHARACTER (USUALLY ?) INTO THE STRING TO INDICATE THAT A CHARACTER FOUND AT THIS POSITION WILL always MATCH.  tHIS SYMBOL WILL REPRESENT AN "i DON'T CARE" POSITION IN THE STRING.  aGAIN, THE b REGISTER WILL CONTAIN THE LENGTH.  wE WILL ALSO CONVERT THE CHARACTERS TO UPPER CASE FOR AN INDEPENDENT TYPE COMPARE.
 
@compar  push      hl         ;SAVE STRING START
         push      de         ;SAVE TABLE START
cmplp    ld        a,(hl)     ;GET STRING BYTE
         cp        '?'        ;DON'T CARE CHARACTER?
         jr        z,cmpnx    ;FORCE A MATCH IF YES
         call      @ucase     ;ELSE MAKE IT UPPER CASE
         ld        c,a        ;SAVE IT HERE
         ld        a,(de)     ;GET TABLE BYTE
         call      @ucase     ;CONVERT IT TO UPPER CASE
         cp        c          ;SAME?
         jr        nz,cmpret  ;NOPE, RETURN WITH nz FLAG
cmpnx    inc       hl         ;NEXT STRING BYTE
         inc       de         ;NEXT TABLE BYTE
         djnz      cmplp      ;DO FOR b # BYTES
cmpret   pop       de         ;RESTORE STACK
         pop       hl
         ret                  ;z = MATCH, nz = NO MATCH
 
    sINCE WE HAVE BEEN MAKING CALLS TO THE ROUTINE THAT CONVERTS A CHARACTER IN THE a REGISTER TO UPPER CASE, LET'S SEE JUST EXACTLY HOW TO DO THAT.  iF WE LOOK AT AN ASCII CHART, WE WILL SEE THAT THE LOWERCASE CHARACTERS HAVE BIT 6 SET, WHILE THE UPPER CASE CHARACTERS HAVE IT RESET, OTHERWISE THE VALUES ARE THE SAME.  fIRST WE WILL DETERMINE IF THE BYTE IS IN THE LOWERCASE RANGE (WE DON'T WANT TO CHANGE NUMBERS ETC. WHICH ALSO HAVE BIT 6 SET), AND NOT AFFECT IT IF NOT.
 
@ucase   cp        60h        ;UPPER CASE OR NON-ALPHA?
         ret       c          ;DON'T CHANGE IF YES
         cp        80h        ;IN THE GRAPHIC CHARACTER RANGE?
         ret       nc         ;DON'T CHANGE IF YES
         and       5fh        ;ELSE MAKE IT UPPER CASE
         ret
 
    wHEN WE HAVE RECEIVED A STRING OF INPUT FROM THE KEYBOARD, WE MAY WANT TO POSITION THE POINTER TO THE FIRST VALID CHARACTER.  uSUALLY, A SPACE OR COMMA IS USED TO SEPARATE ENTRIES ON AN INPUT LINE, BUT THESE CHARACTERS WE WANT TO IGNORE.  wE NEED SOME WAY TO PARSE THROUGH THIS INPUT STRING TO SKIP OVER THESE CHARACTERS.  iN THE FOLLOWING SUBROUTINE, WE WILL ASSUME THAT THE hl REGISTER POINTS TO AN INPUT STRING.  wHEN WE RETURN FROM THE ROUTINE, WE WILL SET THE CARRY FLAG IF THERE IS NO VALID CHARACTERS ON THE LINE.  aS YOU WILL SEE LATER, WE WILL USE THIS SUBROUTINE EXTENSIVELY TO PARSE SEVERAL COMMANDS ON A SINGLE LINE.  a CALL IS MADE TO @poshl.
 
poslp    inc       hl         ;POINT TO NEXT CHARACTER
@poshl   ld        a,(hl)     ;GET A CHARACTER
         cp        13         ;c/r STRING TERMINATOR?
         scf                  ;SET CARRY TO INDICATE THIS
         ret       z          ;RETURN WITH c FLAG IF YES
         cp        ','        ;A COMMA SEPARATOR?
         jr        z,poslp    ;SKIP IT IF YES
         cp        ' '        ;A SPACE SEPARATOR?
         jr        z,poslp    ;SKIP IF YES
         ret                  ;ELSE RETURN, hl=> FIRST CHAR
 
    oTHER USEFUL SUBROUTINES INVOLVE CONVERTING A NUMBER FROM ASCII TO BINARY (COMPUTING THE VALUE SPECIFIED IN AN INPUT STRING), AND BINARY TO ASCII (GETTING A NUMBER INTO DISPLAYABLE FORMAT).  tHERE ARE MANY NUMBER BASES THAT CAN BE USED IN THIS PROCESS, BUT THE MOST USED IN THE trs80 IS hex AND decimal.  fIRST, WE WILL DEFINE A SIMPLE BINARY TO ASCII CONVERSION OF A SINGLE BYTE TO BOTH hex AND decimal ASCII.  tHE BYTE WILL BE SUPPLIED IN THE a REGISTER, AND WILL BE RETURN IN THE acb OR cb REGISTERS (DEPENDING ON THE ROUTINE USED).
 
b2hex    ld        b,a        ;SAVE BYTE HERE
         srl       a          ;MOVE TOP 4 BITS TO LOW 4 BITS
         srl       a          ;BY SHIFTING THE BYTE RIGHT
         srl       a          ;4 TIMES WITH NO CARRY
         srl       a
         call      b2hex1     ;CONVERT THIS DIGIT
         ld        c,a        ;SAVE msb DIGIT HERE
         ld        a,b        ;GET STARTING NUMBER AGAIN
         and       0fh        ;NOW HAVE LOWER 4 BITS ONLY
         call      b2hex1     ;CONVERT THIS DIGIT TO ASCII
         ld        b,a        ;SAVE HERE, bc = HEX ASCII
         ret                  ;DONE
b2hex1   add       a,30h      ;MAKE IT ASCII
         cp        3ah        ;IN RANGE OF 0-9?
         ret       m          ;DONE IF YES
         add       a,7        ;ELSE ADJUST FOR a-f
         ret                  ;DONE
 
b2dec    ld        b,'0'      ;START DIGIT
b2d1     sub       100        ;GET 100'S PLACE
         jr        c,b2d2     ;HAVE IT
         inc       b          ;BUMP THE DIGIT
         jr        b2d1       ;DO MORE
b2d2     push      bc         ;SAVE b ON STACK FOR a LATER
         add       a,100      ;GET NUMBER mod 100
         ld        c,'0'      ;START DIGIT
b2d3     sub       10         ;GET 10'S PLACE
         jr        c,b2d4     ;HAVE IT
         inc       c          ;BUMP ASCII
         jr        b2d3       ;DO MORE
b2d4     add       a,3ah      ;ADD ASCII + 10 WE SUBTRACTED
         ld        b,a        ;cb = LOW 2 DIGITS
         pop       af         ;a = HUNDREDS DIGIT
         ret
 
    iN THE ABOVE 2 ROUTINES, WE HAVE PLACED THE ASCII VALUES INTO THE bc REGISTER IN A SEEMINGLY "BACKWARD" MANNER.  tHERE IS A GOOD REASON FOR THIS, AND IT IS BECASE THE z80 STORES ADDRESS IN MEMORY IN AN lsb, msb ORDER, OR "BACKWARDS".  bY HAVING OUR CONVERSION ROUTINES SUPPLY THE RESULT IN THIS FASHION, WE HAVE THE PROPER ORDER TO PLACE DIRECTLY INTO MEMORY IN THE CORRECT ORDER.  aS SOME EXAMPLES:
 
         ld        a,10       ;JUST ANY OLD NUMBER
         call      @b2hex     ;CONVERT TO HEX ASCII
         ld        (3c00h+62),bc ;CORRECTLY ORIENTATED ON THE VIDEO
 
         ld        a,10       ;ANYTHING
         call      @b2dec     ;CONVERT TO DECIMAL ASCII
         ld        (3c00h+61),a ;PLACE msb ON VIDEO
         ld        (3c00h+62),bc ;PUT REMAINING DIGITS ON VIDEO
 
         ld        a,10       ;NUMBER
         call      @b2hex     ;ASCII HEX
         ld        (msg1a),bc ;PUT INTO STRING TO BE DISPLAYED
         ld        hl,msg1    ;START OF MESSAGE
         call      @disply    ;DISPLAY IT
msg1     db        'tHERE ARE '
msg1a    db        'XXh NUMBERS HERE.',13
 
         ld        a,10       ;NUMBER
         call      @b2dec     ;DECIMAL ASCII
         ld        (msg1a),a  ;PUT INTO STRING
         ld        (msg1a+1),bc ;PUT REST THERE
         ld        hl,msg1    ;START OF MESSAGE
         call      @disply    ;DISPLAY TO THE VIDEO
 
    tHE ABOVE ROUTINES WILL ALLOW US TO EASILY CONVERT A SINGLE BYTE IN THE RANGE OF 0-255 TO DECIMAL OR HEX ASCII.  wE WILL NOW CONVERT A WORD (2 BYTES) INTO ITS ASCII CONTERPART AND RETURN THE VALUE IN REGISTERS AGAIN, BUT THIS TIME WE NEED MORE REGISTERS, SO WE WILL USE acbed AND cbed DEPENDING ON IF IT IS hex OR decimal WE ARE CONVERTING TO.  tHE hex CONVERSION CAN MAKE EASY USE OF THE ABOVE @b2hex ROUTINE (BYTE TO HEX).  wE WILL SUPPLY THE STARTING NUMBER IN THE bc REGISTER.
 
@w2hex   ld        a,c        ;GET lsb
         push      bc         ;SAVE NUMBER
         call      @b2hex     ;MAKE IT HEX
         pop       af         ;GET msb START NUMBER
         push      bc         ;SAVE lsb ON STACK
         call      @b2hex     ;MAKE IT HEX
         pop       de         ;GET lsb FROM STACK
         ret                  ;cbed = ASCII OF NUMBER
 
@w2dec   ld        de,numtbl  ;LOOKUP TABLE FOR SUBTRACTION
         push      hl         ;SAVE FROM USE
         ld        h,b        ;GIVE NUMBER TO hl
         ld        l,c
w2dec1   ld        a,(de)     ;GET A BYTE
         ld        c,a        ;SAVE HERE
         inc       de         ;NEXT TABLE BYTE
         ld        a,(de)	    ;GET IT
         ld        b,a        ;SAVE HERE
         inc       de         ;NEXT TABLE BYTE
         push      de         ;SAVE TABLE
         ld        a,2fh      ;ASCII '0' -1
w2dec2   inc       a          ;BUMP THE ASCII DIGIT
         ld        d,h        ;MOVE NUMBER HERE
         ld        e,l        ;TO USE FOR MATH
         add       hl,bc      ;ADD TO TABLE NUMBER
         jr        c,w2dec2   ;GO TILL MAX REACHED
         ex        de,hl      ;THIS PLACE COMPUTED, RESTORE
         pop       de         ;RESTORE TABLE POSITION
         push      af         ;SAVE DIGIT ON THE STACK
         inc       c          ;SEE IF -1 TERMINATOR LAST ONE
         jr        nz,w2dec1  ;DO ALL 5 DIGITS
         pop       af         ;GET lsb
         ld        d,a        ;LOAD UP THE REGISTERS
         pop       af
         ld        e,a        ;ed = 2 LSB'S
         pop       af
         ld        b,a
         pop       af
         ld        c,a        ;cb = 2 NEXT MSB'S
         pop       af         ;a = MSB
         ret                  ;acbed = DECIMAL ASCII
numtbl   dw        -10000,-1000,-100,-10,-1
 
    tHE ABOVE ROUTINES ARE VALUABLE FOR CONVERTING BINARY NUMBERS INTO "HUMAN READABLE" FORM, BUT THEY DO HAVE RESTRICTIONS ON THE SIZE OF THE NUMBER THAT CAN BE REPRESENTED.  sOMETIMES, HOWEVER, THESE LIMITS MAY NOT BE ENOUGH TO SATISFY OUR PROGRAM.  mANY TIMES A "LOOP COUNTER" NEEDS TO BE KEPT TO KEEP TRACK OF THE NUMBER OF TIMES THAT AN EVENT HAS OCCURED.  wE DO NOT NEED TO KEEP THE NUMBER IN BINARY, AND THEN CONVERT TO ASCII LATER, BUT WE CAN INSTEAD KEEP THE NUMBER IN PURE ASCII RIGHT FROM THE START, AND INCREMENT THIS VALUE EACH PASS OF OUR LOOPS.  i WILL PRESENT 3 VECTORS INTO THIS SUBROUTINE, ONE TO INITIALIZE THE STRING TO 0, ONE TO INCREMENT THE NUMBER, AND ONE TO DISPLAY THE RESULTING VALUE.  uSING THIS TECHNIQUE, THE STRING AND RESULTING NUMBER MAY BE OF any LENGTH AS LONG AS THE CORRECT AMOUNT OF SPACE IF ALLOCATED TO THE STRING.  fOR THIS EXAMPLE, WE WILL USE A 10 CHARACTER STRING WHICH WILL HOLD VALUES UP TO 9,999,999,999 (IN DECIMAL) OR PROBABLY SUFFICIENT FOR MOST APPLICATIONS.
 
initstr  push      hl         ;INITIALIZE STRING TO ZERO
         push      bc         ;SAVE REGISTERS NEEDED
         ld        b,9        ;LENGTH -1
         ld        hl,strnum  ;START OF STRING NUMBER
initlp   ld        (hl),20H   ;PUT A SPACE HERE
         inc       hl         ;BUMP POINTER
         djnz      initlp     ;DO WHOLE THING EXCEPT LAST
         ld        (hl),'0'   ;USE 0 FOR STARTING NUMBER
         pop       bc         ;RESTORE STACK
         pop       hl
         ret                  ;STRING IS INITIALIZED
addstr   push      hl         ;SAVE THIS
         ld        hl,strnum+9;START WITH MOST lsb DIGIT
addlp    inc       (hl)       ;BUMP THE DIGIT
         ld        a,(hl)     ;GET IT
         cp        3ah        ;PAST 9?
         jr        c,addret   ;NOPE, DONE
         ld        (hl),'0'   ;PUT A ZERO HERE
         dec       hl         ;GO TO NEXT MOST SIGNIFICANT
         ld        a,(hl)     ;CHECK FOR SPACE HERE
         cp        ' '        ;SPACE YES?
         jr        nz,addlp   ;GO IF NOT
         ld        (hl),'0'   ;ELSE INITIALIZE IT
         jr        addlp      ;GO MORE
addret   pop       hl         ;RESTORE STACK
         ret
prtstr   push      hl         ;SAVE THIS
         ld        hl,strnum  ;POINT TO STRING NUMBER
         call      @poshl     ;MOVE TO FIRST NON-SPACE DIGIT
         call      @disply    ;DISPLAY IT TO THE VIDEO
         pop       hl         ;RESTORE STACK
         ret                  ;DONE, NUMBER DISPLAYED
strnum   db        'XXXXXXXXXX',3 ;OUR "STRING NUMBER"
 
    aLSO, EQUALLY VALUABLE, IS EXTRACTING THE VALUE OF AN INPUT STRING IN ASCII, AND CONVERTING THIS TO A BINARY FORM THAT WE CAN EASILY WORK WITH.  iN THIS CALL, WE WILL ASSUME THAT hl POINTS TO THE INPUT STRING, TERMINATED WITH A c/r.  wE WILL RETURN THE COMPUTED VALUE IN THE bc REGISTER.  iF THE STRING CONTAINS NON-NUMERIC CHARACTERS, WE WILL RETURN WITH THE CARRY FLAG SET TO INDICATE AN INVALID NUMERIC ENTRY.  iF THE VALUE IS IN THE RANGE OF 0-255 (A SINGLE BYTE VALUE), WE WILL RETURN WITH THE z FLAG SET, ELSE IF THE NUMBER IS IN THE RANGE OF 0-65535 (DOUBLE BYTE VALUE), WE WILL RETURN WITH THE nz FLAG SET.  tHIS WILL ALLOW OUR CALLING ROUTINE TO EASILY RECOGNIZE THE CONDITION OF THE INTERPRETED VALUE.
    fIRST A SHORT ROUTINE IS PROVIDED THAT WILL INTERPRET A NUMBER IN decimal ONLY, BUT REQUIRES VERY LITTLE MEMORY.
 
@value   ld        bc,0       ;STARTING VALUE
vallp    ld        a,(hl)     ;GET A CHARACTER
         cp        ' '        ;SPACE?
         jr        z,valdn    ;DONE IF YES
         cp        ','        ;COMMA?
         jr        z,valdn    ;DONE IF YES
         cp        13         ;c/r ?
         jr        z,valdn    ;DONE IF YES
         sub       30h        ;REMOVE THE ASCII
         ret       c          ;RETURN WITH ERROR
         cp        10         ;MUST BE 0-9 RANGE FOR DECIMAL
         ccf                  ;REVERSE CARRY FLAG
         ret       c          ;CHARACTER IS OUT OF RANGE
         push      hl         ;SAVE STRING POINTER
         ld        h,b        ;GET CURRENT VALUE
         ld        l,c        ;INTO hl
         add       hl,hl      ;*2
         add       hl,hl      ;*4
         add       hl,bc      ;*5
         add       hl,hl      ;*10
         ld        c,a        ;GIVE NEW NUMBER TO bc
         ld        b,0
         add       hl,bc      ;ADD TO TOTAL
         ld        b,h        ;GIVE BACK TO bc
         ld        c,l
         pop       hl         ;GET STRING POINTER BACK
         inc       hl         ;POINT TO NEXT CHARACTER
         jr        vallp      ;SCAN THIS ONE
valdn    ld        a,b        ;GET msb OF VALUE
         or        a          ;CLEAR CARRY, SET z FLAG IF 0
         ld        a,c        ;BUT RETURN WITH a = lsb
         ret                  ;nc SET, AND z FLAG UPON b REGISTER
 
    iN THE FOLLOWING, MORE COMPREHENSIVE SUBROUTINE, WE WILL ADD THE ABILITY TO COMPUTE THE VALUE OF ADDITIONAL NUMBER BASES, binary, octal, AND hex IN ADDITION TO DECIMAL.  wE WILL AGAIN ASSUME THAT THE hl REGISTER POINTS TO THE STRING TO BE EVALUATED, AND THE NUMBER IF FOLLOWED BY ONE OF b, d, h, OR o TO ESTABLISH A BASE OF BINARY, DECIMAL, HEX OR OCTAL RESPECTIVELY.  iF THE LAST CHARACTER IS NOT ONE OF THE ABOVE, WE WILL DEFAULT TO DECIMAL BASE.
 
@value   push      hl         ;SAVE START OF STRING
         call      valend     ;GET THE LAST CHARACTER
         pop       hl         ;RESTORE STRING POINTER
         call      @ucase     ;MAKE IT UPPER CASE
         cp        'b'        ;BINARY?
         ld        bc,valbin  ;BINARY MULTIPLIER
         jr        z,valbas   ;SET IT UP
         cp        'h'        ;HEX?
         ld        bc,valhex  ;HEX MULTIPLIER
         jr        z,valbas   ;SET IT UP
         cp        'o'        ;OCTAL?
         ld        bc,valoct  ;OCTAL MULTIPLIER
         jr        z,valbas   ;SET IT UP
         cp        'd'        ;DECIMAL?
         ld        bc,valdec  ;DECIMAL MULTIPLIER
         jr        z,valbas   ;SET IT UP IF YES
         xor       a          ;ELSE NO SPECIFIER, USE DECIMAL
valbas   ld        (valtrm),a ;SAVE TERMINATOR SPECIFIER
         ld        (valcal),bc;SAVE MULTIPLIER ADDRESS
         ld        bc,0       ;START VALUE
vallp    ld        a,(hl)     ;GET A BYTE
         cp        13         ;c/r TERMINATOR?
         jr        z,valdn    ;DONE IF YES
         cp        ' '        ;SPACE?
         jr        z,valdn    ;DONE IF YES
         cp        ','        ;COMMA?
         jr        z,valdn    ;DONE IF YES
         inc       hl         ;POINT TO NEXT CHARACTER
         call      @ucase     ;MAKE IT UPPER CASE
         cp        0          ;IS IT OUR BASE SPECIFIER?
valtrm   equ       $-1        ;DONE IF YES
         jr        z,valdn    ;POINTER ADJUST CORRECTLY
         call      valnum     ;IS IT A VALID NUMBER?
         ret       c          ;NOPE, ABORT WITH CARRY FLAG
         push      hl         ;SAVE POINTER
         ld        h,b        ;GET CURRENT NUMBER
         ld        l,c        ;INTO hl FOR MATH
         call      0          ;CALL THE MULTIPLIER
valcal   equ       $-2        ;LABEL POINTING TO VECTOR
         ld        c,a        ;GIVE NEW VALUE TO bc
         ld        b,0
         add       hl,bc      ;ADD TO TOTAL
         ld        b,h        ;PUT IT BACK IN bc
         ld        c,l
         pop       hl         ;GET POINTER BACK
         jr        vallp      ;DO NEXT CHARACTER
valdn    ld        a,b        ;GET msb
         or        a          ;SET FLAGS FOR THIS BYTE
         ld        a,c        ;RETURN WITH nc AND lsb
         ret
valnum   sub       30h        ;REMOVE THE ASCII
         ret       c          ;INVALID
         cp        10         ;IS IT 0-9
         ccf                  ;REVERSE THE CARRY FLAG
         ret       nc         ;ok, 0-9
         sub       7          ;ADJUST FOR a-f
         ret       c          ;INVALID, RETURN WITH CARRY
         cp        16         ;MUST BE 0-15
         ccf                  ;REVERSE CARRY
         ret                  ;NUMBER IS 0-9 OR a-f (VALID)
valend   ld        a,(hl)     ;GET A CHARACTER
         cp        ' '        ;CHECK FOR TERMINATORS
         jr        z,valen
         cp        ','
         jr        z,valen
         cp        13
         jr        z,valen
         inc       hl         ;ELSE LOOK AT NEXT CHARACTER
         jr        valend     ;PARSE IT
valen    dec       hl         ;GET THE LAST CHARACTER BACK
         ld        a,(hl)     ;GET IT
         ret                  ;a = LAST CHAR BEFORE TERMINATOR
valhex   add       hl,hl      ;MULTIPLY X 16
valoct   add       hl,hl      ;MULTIPLY X 8
         add       hl,hl
valbin   add       hl,hl      ;MULTIPLY X 2
         ret
valdec   add       hl,hl      ;*2
         add       hl,hl      ;*4
         add       hl,bc      ;*5
         jr        valbin     ;*10
 
    hERE IS A SAMPLE ROUTINE MAKING USE OF SOME OF THE ABOVE CALLS TO PROMPT AND PARSE AN INPUT STRING FOR NUMERIC VALUES.
 
getts    ld        hl,mesg    ;PROMPT MESSAGE FOR TRACK, SECTOR
         call      @disply    ;DISPLAY TO THE VIDEO
         ld        b,25       ;25 CHAR INPUT
         call      @getstr    ;GET FROM KEYBOARD
         jp        c,exit     ;<break>, ABORT
         ld        de,0       ;DEFAULT TRACK, SECTOR
         ret       z          ;de = TRACK, SECTOR
         call      @poshl     ;POSITION TO INPUT
         ret       c          ;NOTHING, MUST HAVE BEEN A SPACE
         call      @value     ;GET THE VALUE
         jr        c,getts    ;INVALID, RE-PROMPT
         ld        d,a        ;SAVE TRACK HERE
         call      @poshl     ;POSITION TO NEXT ENTRY
         ret       c          ;NOTHING, USE DEFAULT
         call      @value     ;GET THE VALUE
         jr        c,getts    ;INVALID NUMBER
         ld        e,a        ;PASS SECTOR HERE
         ret                  ;de = INPUT TRACK, SECTOR
mesg     db        'tRACK, sECTOR ? ',3
>*BOOK1P2

