
**********************************************************************
files: Validation.TXT, Validation.TAP
       Validation.BAS, Validation.ASM
**********************************************************************


** Input Validation
   ================

A function of this form can be used:

     GO SUB FN a(row,col,length,type,reqd,"string",gosub,table)

where the parameters specify:
	input position, maximum length, data type (see below),
    optional/mandatory, prompt text, start line of validation routine,
    start of validation table

The data type could be handled with reference to a 96-byte string holding a code for each regular character indicating into which validation sets it falls. Eight bits per byte allows for eight distinct sets plus 248 (mostly redundant) combinations, which could be:

[code]
bit 7 = 128 = any digit
bit 6 =  64 = lower case
bit 5 =  32 = upper case
bit 4 =  16 = punctuation symbols
bit 3 =   8 = arithmetic symbols
bit 2 =   4 = currency symbols
bit 1 =   2 = "."
bit 0 =   1 = "-","+"
[/code]

Digits and letters each occur in only one set; some symbols occur in more than one set. So, for example:

* "." is in sets 4,3,1, thus v$(CODE "."-31)=CHR$ (16+8+2).

* "any number" comprises sets 7,1,0, thus type=128+2+1=131.

To verify a character, the 'type' code is ANDed with the character's entry in v$ and if the result is non-zero then it's OK. A few lines of machine code would deal the AND, and so the key-press validation for any key/type combination could be reduced to simply:

1001 LET k$=INKEY$: IF NOT (CODE k$+USR valkey) THEN GO TO 1001
1002 IF INKEY$<>"" THEN GO TO 1002

which would return 0 for an incorrect key or no key, or else the key value. Line 1002 waits for the accepted keypress to be released before proceeding.

To avoid the validation table taking up more space than necessary (ie. by assigning it from DATA statements to an array, for example) it could be referenced in situ, using a separate RESTORE in the initialisation routine to locate it, thus:

[code]
 100 DEF FN a(y,x,l,t,r,i$,g,v)=g: DEF FN p(a)=PEEK a+256*PEEK (a+1):
     RESTORE 100: LET da=FN p(23639)+8:
     POKE 23563,da-INT (da/256)*256: POKE 23564,INT (da/256):
     RESTORE 999: LET vt=FN p(23639)+7:
     RETURN
 999 DATA "...96 bytes validation table..."
....
     GO SUB FN a(row,col,length,type,reqd,"string",gosub,vt)
[/code]

   ================

Here's the few lines of machine code:

Validation.BAS [code]
        org  23296
STKEND: equ  23653
VALKEY: ld   bc,(STKEND)
        dec  bc
        dec  bc
        dec  bc
        ld   a,(bc)          ;A =key code
        sub  32              ;A =validation table key code offset
        ld   ix,(23563)      ;IX=(DEFADD)
        ld   l,(ix+61)
        ld   h,(ix+62)       ;HL=validation table base address
        ld   d,0
        ld   e,a
        add  hl,de           ;HL=validation table key code address
        ld   a,(ix+28)       ;A =type
        and  (hl)
        jr   nz,KEY_OK
        ld   (bc),a          ;reset key code, indicating error
KEY_OK: ld   b,0
        ld   c,b
        ret
[/code]

the structure of the FN a() variables; those referenced by m/c marked []:

[code]
offset: 00 01 02 03 04 05 06 07
  data   y 0e 00 00 nn 00 00 ,
        08 09 10 11 12 13 14 15
         x 0e 00 00 nn 00 00 ,
        16 17 18 19 20 21 22 23
         l 0e 00 00 nn 00 00 ,
        24 25 26 27 28 29 30 31
         t 0e 00 00[nn]00 00 ,
        32 33 34 35 36 37 38 39
         r 0e 00 00 nn 00 00 ,
        40 41 42 43 44 45 46 47 48
         i  $ 0e 00 al ah ll lh ,
        49 50 51 52 53 54 55 56
         g 0e 00 00 gl gh 00 ,
        57 58 59 60 61 62 63 64
         v 0e 00 00[vl vh]00 )
[/code]

and a routine to generate the validation table + input demo:

Validation.ASM [code]
   3 DEF FN a(y,x,l,t,r,i$,g,v)=g
   4 DEF FN p(a)=PEEK a+256*PEEK (a+1): DEF FN c()=FN p(23621)
   5 RESTORE 3: LET da=FN p(23639)+8:
     POKE 23563,da-INT (da/256)*256: POKE 23564,INT (da/256):
     RESTORE 999: LET vt=FN p(23639)+7

 820 FOR p=0 TO 0 STEP 0
 821   RESTORE 990+INT (RND*8): READ vp,v$
 823   GO SUB FN a(0,0,1,vp,1,v$,830,vt)
 829 NEXT p

 830 PRINT AT y,x;i$;TAB 0;FLASH 1;"?";
 835 LET k$=INKEY$: IF NOT (CODE k$+USR 23296) THEN GO TO FN c()
 836 IF INKEY$<>"" THEN GO TO FN c()
 837 PRINT CHR$ 8;k$: RETURN

 900 RESTORE 999: LET vt=FN p(23639)+7: FOR c=32 TO 127
 902   PRINT CHR$ c;: LET vc=0
 904   FOR t=0 TO 7
 906     RESTORE 990+t: READ vp,v$
 908     FOR k=1 TO LEN v$
 910       IF CHR$ c=v$(k) THEN LET vc=vc+vp
 912     NEXT k
 914   NEXT t
 916   POKE vt+c-32,vc
 918 NEXT c: PRINT

 990 DATA 1,  "-+"
 991 DATA 2,  "."
 992 DATA 4,  "{60}$": REM $
 993 DATA 8,  "%()*+-/<=>^"
 994 DATA 16, " !""#&',.:;?@[\]_{7B}|{7D}~{7F}": REM  !"#&',.:;?@[\]_{|}~
 995 DATA 32, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
 996 DATA 64, "abcdefghijklmnopqrstuvwxyz"
 997 DATA 128,"0123456789"

 999 DATA "01234567890abcdef+01234567890abcdef+01234567890abcdef+01234567890abcdef+01234567890abcdef+01234567890abcdef+"
[/code]

Lines 900-997 can be deleted once the table has been generated, as it only needs to be done once. Do not use LET/READ to assign a value to the FN a() parameter i$; only assign it a value via a function call as in line 823. To run the demo, RUN 900 then RUN.

   ================

The simplest way to restrict the size of a numeric field is ...
[code]
   4 DIM a$(4)
   5 INPUT a$
   6 LET a=VAL a$
[/code]
... which will restrict the output length to 4 without any explicit checking, but will fail if any non-numeric characters are typed (unless they happen to match the name of a variable). The DIM only needs to be done once.

... or, to reject anything other than a maximum of four digits 0-9 ...
[code]
   5 INPUT a$
   6 LET a=0
   7 FOR b=1 TO LEN a$
   8   IF a$(b)>="0" AND a$(b)<="9" AND a<1000
       THEN LET a=a*10+CODE a$(b)-48
   9 NEXT b
[/code]
I've discarded the DIM in favour of truncating the output, as this will return the first four digits regardless of how many characters are typed.

 ... or, in a more confusing way ...
[code]
   3 DEF FN a$(a$,b)
   =("0" AND NOT b)
   +(CHR$ CODE a$ AND FN a(CODE a$))
   +VAL$ (("FN a$("""+a$(2 TO )+""","+STR$ (b+FN a(CODE a$))+")+"
          AND (b<3 OR NOT FN a(CODE a$)) AND LEN a$)
          +""""""
         ):
     DEF FN a(a)=a>=48 AND a<=57
   5 INPUT a$
   6 LET a=VAL FN a$(a$,0)
[/code]
Line 3 is a single line; I just wrapped it for ... errmmm ... clarity.

There are no advantages to using the DEF FN method at all; it takes more BASIC program space, uses more memory at runtime, is slower than the other two methods and more difficult to understand. There is only the illusory advantage that the code appears simpler if line 3 isn't looked at.


Restricting the size on input rather than output can be done by using an INKEY$ loop rather than INPUT. That way each character can be checked and printed or ignored individually and entry finished immediately once the required limit has been reached. Thusly:
[code]
  10 PRINT AT 10,13;"[    ]";AT 10,14;: LET a=0
  11 IF INKEY$<>"" THEN GO TO 11
  12 IF INKEY$="" THEN GO TO 12
  14 LET i$=INKEY$: PRINT i$;: IF i$=CHR$ 13 THEN GO TO 21
  16 IF i$<"0" OR i$>"9"
     THEN PRINT CHR$ 8; OVER 1; PAPER 2; FLASH 1;" ";CHR$ 8;: GO TO 20
  18 LET a=a*10+VAL i$: IF a>=1000 THEN GO TO 21
  20 GO TO 20-(9 AND INKEY$="")
  21 PRINT AT 12,14;a: STOP
[/code]
