Valid

First not submitted for publication in Quanta in March 1992

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

HTML generated by the amazing asm2htm !
2019 Jun 21

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