Having followed the "debate" on input validation in the pages of this
magazine for some time, I though it about time I did my bit. The
starting point for my VALID keyword below is: let QDOS do the work!
Obviously, QDOS accepts inputs that we may not always find logical eg
x%='1e700'
is a valid integer (x%=1!) in QDOS' eyes though it won't
do the same for x (overflow), however the main point is often just to
check whether a user input conforms with certain restrictions so that
our carefully crafted programs don't ignominously fall over due some
unintended input. VALID, together with other useful routines can be
found in my programming toolkit, PTOOL, available to members from the
library.
VALID: It's task in life is merely to check a number or variable to see whether is conforms to the internal QDOS format.
RETurn VALID(expected_type,variable)
where expected_type is:
0 = long integer, 1 = string, 2 = floating point, 3 = integer.
A further type exists: -1 = query
If the variable, literal or expression does not conform to the expected type the function returns zero, otherwise it returns non-zero. An additional bonus of this function is that it returns zero if the variable is unset ie, has not been given any value. (This is not true for SBASIC though. But then it doesn't fall over when it hits an unset variable either). This makes it possible to write procedures in SuperBasic with optional parameters, eg
DEFine PROCedure Test(a$,b,c%) IF NOT VALID(3,c%):c%=default% PRINT a$,b,c% END DEFine
Set default% to whatever you like and try Test '1',2 or Test 1,2,3 - or Test '1',2,'rubbish'. (SBASIC requires a slight modification).
A variation on VALID is query:
RETurn VALID(-1,variable)
returns the type of the parameter variable as an integer. It does not tell you whether the contents of a variable are valid or not eg whether a$='.-3' is a valid number, as you can using the other type specifications above, but it will tell you the basic type of the variable in question. These are the types that can be tested (values in hex and decimal):
$0001 (1) unset str $0201 (513) set str $0301 (769) str array $0002 (2) unset fp $0202 (514) set fp $0302 (770) fp array $0003 (3) unset int $0203 (515) set int $0303 (771) int array
also:
$0602 (1538) REPeat loop index $0702 (1794) FOR loop index $0300 (0768) Sub string
You can also tell which separator (ie , ; ! \ TO) variable is followed by, or whether it is preceded by a hash -
PRINT VALID(-1,#2;)
but what's the point?
* SuperBasic VALID function * * V0.01, © PWITTE, 1992 * V0.02, pjw, June 23rd 2000, bug fix * V0.03, pjw, June 21st 2019, All platforms (two formats) section code filetype 0 lea.l fns,a1 ;point to keyword definitions move.w $110,a2 ;bp.init jmp (a2) ;link to SuperBasic * fns dc.w 0,0 ;no procs, endfor procs dc.w 1 ;one function dc.w valid-* ;offset to routine dc.b 6,'VALID% ' ;name of keyword dc.w 0 ;endfor fns * v_err_bp moveq #-15,d0 ;error "bad parameter" v_err rts valid lea.l 8*2(a3),a4 ;check for two parameters cmpa.l a4,a5 bne.s v_err_bp ; if <> 2 return bad parameter error lea.l 8(a3),a5 ;hide last parameter to get first only move.w $112,a2 ;ca.gint jsr (a2) ;get an integer bne.s v_err ;something wrong here, quit with error move.w 0(a6,a1.l),d0 ;d0 contains first parameter bpl.s v_test ;if negative then it is a query * v_query move.w 8(a6,a3.l),d7 ;return next parameter information bra.s v_return v_test subq.w #3,d0 ;max possible number is 3 bgt.s v_err_bp ; number too big, quit move.l $58(a6),a4 ;a4 = stack top (bv_rip), rel a6 suba.l $5c(a6),a4 ;a4 = stack top rel base (bv_ribas) move.l a5,a3 ;point to next parameter addq.l #8,a5 ; which is the final parameter neg.w d0 add.w d0,d0 ;change to ca... and convert to addr offset move.w d0,a2 ;a2 = offset to fetch routine vector moveq #1,d7 ;assume valid movea.w $112(a2),a2 jsr (a2) ;attempt to fetch parameter beq.s v_ret ;it worked! Use assumed TRUE moveq #0,d7 ;it failed! Switch to FALSE v_ret move.l a4,a1 ;restore offset off old stack pointer adda.l $5c(a6),a1 ;make it rel to (poss new) base pointer move.l a1,$58(a6) ;this is new stack top, w room for one int * v_return move.w d7,0(a6,a1.l) ;insert return value moveq #3,d4 ;inform SB of integer return moveq #0,d0 ;no errors rts ;all done * end
A (very) simple input validation routine could then look something like this:
DEFine FuNction get_integer% REPeat input_loop INPUT "Enter integer"!n$ IF VALID(3,n$):EXIT input_loop PRINT "Not an integer!" END REPeat input_loop RETurn n$ END DEFine get_integer%
You can download the ready-to-run toolkit from here