;
;  PROGRAM:  VMENUCK
;  AUTHOR:  RICHARD CONN
;  VERSION:  1.0
;  DATE:  22 July 84
;  PREVIOUS VERSIONS:  None
;  DERIVATION:  MENUCK 1.0 (18 May 84)
;
VERS    EQU     10      ;VERSION NUMBER
z3env   SET     0f400h

;
;       VMENUCK is used to check the syntax of a MENU.VMN file for the ZCPR3
; menu processor, VMENU.  VMENU was optimized for size and runtime speed, and
; I tried to keep the size under 2K (and succeeded, for that matter).  In
; keeping VMENU small, the error diagnostics it gives are quite limited, with
; a variety of errors producing the message "Str Err" for MENU.VMN
; structure error.
;
;       VMENUCK is intended to be used to check the syntax and other features
; of a user's MENU.VMN before allowing VMENU to run with it.  In this way,
; many errors may be caught before the MENU.VMN file comes into common use,
; and there is plenty of space for informative diagnostics.
;

;
;  MENU Constants
;
MCMD    EQU     ':'     ;Menu Jump Command
MINDIC  EQU     '#'     ;Menu Indic
GOPTION EQU     '-'     ;Global Option Indic
XOPTION EQU     'X'
VARFLAG EQU     '$'     ;Variable Flag

;
;  CP/M Constants
;
bentry  equ     5       ;BDOS Entry
fcb     equ     5ch     ;FCB
tbuff   equ     80h     ;Temp I/O Buffer
cr      equ     0dh
lf      equ     0ah
EOF     equ     'Z'-'@' ;^Z=EOF

;
;  Externals
;
       ext     z3init,zfname,z3log

       ext     caps,crlf,eval10,retud
       ext     f$open,f$close,f$read
       ext     print,cout
       ext     moveb
       ext     phldc,padc,pfn2,pafdc
       ext     codend

;
; Environment Definition
;
       if      z3env ne 0
;
; External ZCPR3 Environment Descriptor
;
       jmp     start
       db      'Z3ENV' ;This is a ZCPR3 Utility
       db      1       ;External Environment Descriptor
z3eadr:
       dw      z3env
start:
       lhld    z3eadr  ;pt to ZCPR3 environment
;
       else
;
; Internal ZCPR3 Environment Descriptor
;
       MACLIB  Z3BASE.LIB
       MACLIB  SYSENV.LIB
z3eadr:
       jmp     start
       SYSENV
start:
       lxi     h,z3eadr        ;pt to ZCPR3 environment
       endif

;
; Start of Program -- Initialize ZCPR3 Environment
;
       call    z3init  ;initialize the ZCPR3 Env and the VLIB Env

       call    print
       db      'VMENUCK  Version '
       db      (vers/10)+'0','.',(vers mod 10)+'0',0

       lda     fcb+1   ;get first char
       cpi     ' '     ;no file name?
       jz      help
       cpi     '/'     ;option?
       jnz     start1
;
;  Print Help Message
;
help:
       call    print
       db      cr,lf,'Syntax:'
       db      cr,lf,'  VMENUCK dir:filename.typ <-- Check File'
       db      cr,lf,'  VMENUCK dir:filename     <-- Check filename.VMN'
       db      0
       ret

;
;  Begin serious processing -- locate the file pted to by HL
;
start1:
       lxi     d,fcb           ;pt to FCB
       call    z3log           ;log into indicated FCB

;
;  Set File Type to MNU if not specified
;
start2:
       lxi     h,fcb+9         ;pt to file type
       mov     a,m             ;get first char
       cpi     ' '             ;set type if <SP>
       jnz     start3
       push    b               ;save BC
       lxi     d,mnutyp        ;set type to MNU
       xchg
       mvi     b,3             ;3 bytes
       call    moveb
       pop     b               ;get BC
;
;  Try to Open the File
;
start3:
       lxi     d,fcb           ;prepare to open file
       xra     a               ;A=0 to select current disk
       stax    d
       call    f$open          ;open file
       jz      readfile        ;read in file if OK
       call    print
       db      cr,lf,' File Not Found',0
       ret
;
;  Read in File
;
readfile:
       call    codend          ;get address of first block
readloop:
       lxi     d,fcb           ;read block
       call    f$read          ;do it
       ora     a               ;check for error
       jnz     readdone
       lxi     d,tbuff         ;pt to block just read in
       mvi     b,128           ;128 bytes
readmove:
       ldax    d               ;get byte
       ani     7fh             ;mask MSB
       mov     m,a             ;put byte
       inx     h               ;pt to next
       inx     d
       dcr     b               ;count down
       jnz     readmove
       xchg                    ;DE pts to next block
       lhld    bentry+1        ;get address of BDOS
       mov     a,h             ;check for possible overflow
       sui     10              ;10 pages below BDOS is limit
       cmp     d               ;within range?
       xchg                    ;HL pts to next block
       jnc     readloop        ;continue read if within range
       call    print
       db      cr,lf,' TPA Overflow -- VMENU File is Too Big',0
       ret
;
;  Read is Done -- Store Ending ^Z and Set Initial Values
;
readdone:
       mvi     m,EOF   ;Store ^Z to ensure EOF
       lxi     d,fcb           ;Close File
       call    f$close
       mvi     a,0ffh          ;A = -1
       sta     menunum         ;set menu number
       sta     maxnum          ;set max number of all menus
       lxi     h,0             ;HL=0
       shld    errors          ;Set Error Count to 0
       inx     h               ;HL=1
       shld    linenum         ;Set Line Number to 1
;
;  Count Number of Menus
;
       call    codend          ;Pt to First Byte
       mov     a,m             ;get first byte
;
;  Skip to Beginning of Menu Display
;
mdskip:
       cpi     EOF             ;EOF?
       jz      mdone
       cpi     MINDIC          ;beginning of display?
       jz      mcgo            ;now go skip commands
       call    lskip           ;skip to next line
       jmp     mdskip
mcgo:
       inx     h               ;pt to char after MINDIC
       mov     a,m             ;another MINDIC?
       cpi     MINDIC
       jz      mdone           ;done if 2 in a row
       lda     maxnum          ;get menu number count
       inr     a               ;found another one
       sta     maxnum
mcskip:
       call    lskip           ;skip to next line
       jz      mdone           ;done if premature EOF
       cpi     MINDIC          ;end of display?
       jnz     mcskip
       inx     h               ;pt to char after MINDIC
       mov     a,m             ;get it
       jmp     mdskip
;
;  Check for Valid First Character
;
mdone:
       call    print
       db      cr,lf,'VMenu Syntax Check on ',0
       call    retud           ;get dir
       mov     a,b             ;get disk
       adi     'A'
       call    cout
       mov     a,c             ;get user
       call    pafdc
       mvi     a,':'
       call    cout
       lxi     d,fcb+1         ;pt to FCB
       call    pfn2
       call    print           ;Print Header
       db      cr,lf
       db      cr,lf,' Line Comment/Error Message'
       db      cr,lf,' ---- ---------------------',0

       xra     a               ;set no global option
       sta     gopt
       call    codend          ;get address of first byte
       mov     a,m             ;get first char
       cpi     GOPTION         ;global options?
       jnz     newmenu         ;process globals
       mvi     a,0ffh          ;set global option
       sta     gopt
       call    lprint
       db      '** Global Options Detected **',0
       call    optchk          ;check options
       xra     a               ;set no global option
       sta     gopt
       call    nxtline         ;advance to next line
;
;  This is the main entry point for processing a menu
;
newmenu:
       mov     a,m             ;get Menu Indicator
       cpi     MINDIC          ;must be MINDIC
       jz      nm1
       call    newerr          ;add to error count
       call    lprint
       db      ' New Menu Expected, But ',MINDIC,' NOT Found -- '
       db      'Aborting',0
       jmp     errxit
;
;  Print that we have a new menu
;
nm1:
       call    lprint
       db      '** Menu Number ',0
       lda     menunum         ;increment menu number
       inr     a
       sta     menunum
       call    padc
       call    optchk          ;check options
;
;  Skip Thru Display
;
nm2:
       call    nxtline         ;skip to next line
       jnz     nm2a            ;continue if no EOF
earlyeof:
       call    newerr          ;add to error count
       call    lprint
       db      ' Premature EOF Encountered',0
       jmp     errxit
nm2a:
       cpi     MINDIC          ;Menu Indicator?
       jnz     nm2             ;Continue
;
;  Move Thru Menu Commands
;
nm3:
       call    mcmd1           ;check Menu Command Line
       jz      earlyeof
       call    lcheck          ;check line
       cpi     MINDIC          ;check for menu indicator
       jnz     nm3             ;continue until menu indicator encountered
       inx     h               ;check for 2 indicators in a row for end
       mov     a,m             ;get 2nd char
       dcx     h               ;back up in case it is not
       cpi     MINDIC          ;2 in a row?
       jnz     newmenu         ;process as new menu if not
errxit:
       call    lprint
       db      '** End of Menu Check **',cr,lf,'       ',0
       lhld    errors          ;check error count
       mov     a,h             ;check for Zero
       ora     l
       jnz     err1
       call    print
       db      'No',0
       jmp     err2
err1:
       call    phldc           ;print as decimal
err2:
       call    print
       db      ' Errors Detected',0
       ret

;
;  Utilities
;

;
;  LPRINT -- Print "Line # "+text
;
lprint:
       call    crlf            ;new line
       push    h               ;save HL
       lhld    linenum         ;get line number
       call    phldc           ;print as decimal
       pop     h               ;restore HL
       mvi     a,' '           ;print <sp>
       call    cout
       jmp     print           ;print text
;
;  NXTLINE -- Advance to next line, check for EOF, and increment Line Number
;  LSKIP -- Advance to next line and check for EOF
;       Return with HL pting to first char of next line and Z Set if EOF
;
nxtline:
       push    h               ;increment line count
       lhld    linenum         ;add 1
       inx     h
       shld    linenum
       pop     h               ;fall thru to skipping
lskip:
       mov     a,m             ;get char
       cpi     EOF             ;EOF?
       rz
       inx     h               ;pt to next
       cpi     lf              ;line feed?
       jnz     lskip           ;continue if not
       mov     a,m             ;get first char of next line
       cpi     EOF             ;check for EOF
       ret
;
;  MCMD1 -- Check Menu Line, check for EOF, and increment Line Number
;       Return with HL pting to first char of next line and Z Set if EOF
;
mcmd1:
       mov     a,m             ;get char
       cpi     EOF             ;EOF?
       jz      mcmdx
       inx     h               ;pt to next
       cpi     VARFLAG         ;variable?
       jz      mcmd2
       cpi     lf              ;line feed?
       jnz     mcmd1           ;continue if not
mcmdx:
       push    h               ;increment line count
       lhld    linenum         ;add 1
       inx     h
       shld    linenum
       pop     h               ;fall thru to skipping
       mov     a,m             ;get first char of next line
       cpi     EOF             ;check for EOF
       ret
;
; Check Variable
;
mcmd2:
       mov     a,m             ;get char
       ani     7fh             ;mask
       call    caps            ;capitalize
       inx     h               ;pt to next
       cpi     VARFLAG         ;OK if double VARFLAG
       jz      mcmd1
       cpi     'D'             ;OK if D
       jz      mcmd1
       cpi     'U'             ;OK if U
       jz      mcmd1
       cpi     'F'             ;filename.typ?
       jz      mcmd3
       cpi     'N'             ;filename?
       jz      mcmd3
       cpi     'T'             ;filetype?
       jz      mcmd3
       cpi     'P'             ;pointed-to file?
       jz      mcmd6
;
; Invalid Variable
;
       dcx     h               ;pt to previous (bad char)
       push    psw             ;save char
       call    lprint
       db      ' Variable Error (Not $, D, U, F, N, or T) - ',0
       pop     psw             ;get char
       call    cout            ;print it
       call    newerr          ;increment error count
       jmp     mcmd1
;
; Digit from 1 to 4 should follow
;
mcmd3:
       mov     a,m             ;get next char
       inx     h               ;pt to next
       ani     7fh             ;mask and cap
       call    caps
       cpi     '1'             ;must be from 1 to 4
       jc      mcmd4
       cpi     '5'
       jc      mcmd1
;
; Invalid Digit
;
mcmd4:
       push    psw
       call    lprint
       db      ' Invalid Digit for F, N, or T Variable (not 1-4) - ',0
mcmd5:
       pop     psw
       dcx     h               ;pt to invalid char
       call    cout
       call    newerr          ;increment error count
       jmp     mcmd1

;
; Check for Pointed to File
;
mcmd6:
       mov     a,m             ;get next char
       inx     h               ;pt to next
       ani     7fh             ;mask
       call    caps
       cpi     'F'
       jz      mcmd1
       cpi     'N'
       jz      mcmd1
       cpi     'T'
       jz      mcmd1
       push    psw             ;save char
       call    lprint
       db      ' Invalid Pointed-to File Option (not F, N, or T) - ',0
       jmp     mcmd5           ;process error and back up ptr

;
;  OPTCHK -- Check Line Pted to by HL for Valid GOPTION and MINDIC options
;       Do Not Affect HL
;       Print Error Message and Character if Invalid Option Found
;
optchk:
       push    h               ;save HL
       push    b
       inx     h               ;skip indicator
optclp:
       mov     a,m             ;get char
       call    caps            ;capitalize
       inx     h               ;pt to next
       cpi     cr              ;EOL?
       jz      optcdn
       mov     b,a             ;char in B
       cpi     XOPTION         ;only exit option allowed
       jz      optclp
       call    newerr          ;increment error count
       call    lprint
       db      ' Invalid Option: ',0
       mov     a,b             ;get char
       call    cout            ;print char
       jmp     optclp
optcdn:
       pop     b
       pop     h               ;restore ptr
       ret
;
;  Increment Error Count
;
newerr:
       push    h       ;save HL
       lhld    errors  ;increment error count
       inx     h
       shld    errors
       pop     h       ;restore HL
       ret
;
;  Check Line, especially looking for Menu Jump
;
lcheck:
       push    h       ;save ptr to first char
       inx     h       ;pt to 2nd char
       mov     a,m     ;get it
       cpi     MCMD    ;menu jump?
       jnz     lchk1
       inx     h       ;pt to menu number
       call    eval10  ;convert to binary in DE
       mov     a,d     ;D must be 0
       ora     a       ;check
       jz      lchk0
lchker:
       call    newerr  ;increment error count
       call    lprint
       db      ' Menu Number Out of Range',0
       jmp     lchk1
lchk0:
       lda     maxnum  ;get max menu number
       cmp     e       ;check for range
       jc      lchker
lchk1:
       pop     h       ;restore ptr
       mov     a,m     ;get first char in line
       ret
;
;  Skip HL over Blanks
;
sblank:
       mov     a,m     ;get char
       inx     h       ;pt to next
       cpi     ' '     ;blank?
       jz      sblank  ;continue skipping
       dcx     h       ;pt to non-blank
       ret

;
;  Buffers
;
mnutyp:
       db      'VMN'
errors:
       ds      2       ;error count
linenum:
       ds      2       ;current line number
menunum:
       ds      1       ;current menu number
maxnum:
       ds      1       ;max menu number
gopt:
       ds      1       ;global option flag

       end