;
;  PROGRAM:  MCHECK
;  AUTHOR:  RICHARD CONN
;  VERSION:  1.1
;  DATE:  6 Jan 83
;  PREVIOUS VERSIONS:  1.0 (12 Dec 82)
;
VERS    EQU     11      ;VERSION NUMBER

;
;       This program is Copyright (c) 1982, 1983 by Richard Conn
;       All Rights Reserved
;
;       ZCPR2 and its utilities, including this one, are released
; to the public domain.  Anyone who wishes to USE them may do so with
; no strings attached.  The author assumes no responsibility or
; liability for the use of ZCPR2 and its utilities.
;
;       The author, Richard Conn, has sole rights to this program.
; ZCPR2 and its utilities may not be sold without the express,
; written permission of the author.
;


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

;
;  MENU Constants
;
MCMD    EQU     ':'     ;Menu Jump Command
RSM     EQU     '$'     ;System Menu Indic
MINDIC  EQU     '#'     ;Menu Indic
GOPTION EQU     '-'     ;Global Option Indic
COPTION EQU     'C'     ;Option chars
DOPTION EQU     'D'
POPTION EQU     'P'
XOPTION EQU     'X'

;
;  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

;
;  SYSLIB Externals
;
       ext     caps
       ext     crlf
       ext     eval10
       ext     f$open
       ext     f$close
       ext     f$read
       ext     print
       ext     zgpins
       ext     zfname
       ext     retud
       ext     logud
       ext     moveb
       ext     cout
       ext     phldc
       ext     padc
       ext     codend

;
;  Branch to Start of Program
;
       jmp     start

;
;******************************************************************
;
;  SINSFORM -- ZCPR2 Utility Standard General Purpose Initialization Format
;
;       This data block precisely defines the data format for
; initial features of a ZCPR2 system which are required for proper
; initialization of the ZCPR2-Specific Routines in SYSLIB.
;

;
;  EXTERNAL PATH DATA
;
EPAVAIL:
       DB      0FFH    ; IS EXTERNAL PATH AVAILABLE? (0=NO, 0FFH=YES)
EPADR:
       DW      40H     ; ADDRESS OF EXTERNAL PATH IF AVAILABLE

;
;  INTERNAL PATH DATA
;
INTPATH:
       DB      0,0     ; DISK, USER FOR FIRST PATH ELEMENT
                       ; DISK = 1 FOR A, '$' FOR CURRENT
                       ; USER = NUMBER, '$' FOR CURRENT
       DB      0,0
       DB      0,0
       DB      0,0
       DB      0,0
       DB      0,0
       DB      0,0
       DB      0,0     ; DISK, USER FOR 8TH PATH ELEMENT
       DB      0       ; END OF PATH

;
;  MULTIPLE COMMAND LINE BUFFER DATA
;
MCAVAIL:
       DB      0FFH    ; IS MULTIPLE COMMAND LINE BUFFER AVAILABLE?
MCADR:
       DW      0FF00H  ; ADDRESS OF MULTIPLE COMMAND LINE BUFFER IF AVAILABLE

;
;  DISK/USER LIMITS
;
MDISK:
       DB      4       ; MAXIMUM NUMBER OF DISKS
MUSER:
       DB      31      ; MAXIMUM USER NUMBER

;
;  FLAGS TO PERMIT LOG IN FOR DIFFERENT USER AREA OR DISK
;
DOK:
       DB      0FFH    ; ALLOW DISK CHANGE? (0=NO, 0FFH=YES)
UOK:
       DB      0FFH    ; ALLOW USER CHANGE? (0=NO, 0FFH=YES)

;
;  PRIVILEGED USER DATA
;
PUSER:
       DB      10      ; BEGINNING OF PRIVILEGED USER AREAS
PPASS:
       DB      'chdir',0       ; PASSWORD FOR MOVING INTO PRIV USER AREAS
       DS      41-($-PPASS)    ; 40 CHARS MAX IN BUFFER + 1 for ending NULL

;
;  CURRENT USER/DISK INDICATOR
;
CINDIC:
       DB      '$'     ; USUAL VALUE (FOR PATH EXPRESSIONS)

;
;  DMA ADDRESS FOR DISK TRANSFERS
;
DMADR:
       DW      80H     ; TBUFF AREA

;
;  NAMED DIRECTORY INFORMATION
;
NDRADR:
       DW      00000H  ; ADDRESS OF MEMORY-RESIDENT NAMED DIRECTORY
NDNAMES:
       DB      64      ; MAX NUMBER OF DIRECTORY NAMES
DNFILE:
       DB      'NAMES   '      ; NAME OF DISK NAME FILE
       DB      'DIR'           ; TYPE OF DISK NAME FILE

;
;  REQUIREMENTS FLAGS
;
EPREQD:
       DB      0FFH    ; EXTERNAL PATH?
MCREQD:
       DB      000H    ; MULTIPLE COMMAND LINE?
MXREQD:
       DB      000H    ; MAX USER/DISK?
UDREQD:
       DB      000H    ; ALLOW USER/DISK CHANGE?
PUREQD:
       DB      000H    ; PRIVILEGED USER?
CDREQD:
       DB      000H    ; CURRENT INDIC AND DMA?
NDREQD:
       DB      0FFH    ; NAMED DIRECTORIES?
Z2CLASS:
       DB      0       ; CLASS 0
       DB      'ZCPR2'
       DS      10      ; RESERVED

;
;  END OF SINSFORM -- STANDARD DEFAULT PARAMETER DATA
;
;******************************************************************
;

;
;  Start of Program
;
start:
       call    zgpins  ;init ZCPR2 buffers
       lxi     h,tbuff ;place zero at end of input buffer
       mov     a,m     ;get char count
       inx     h       ;pt to first char
       push    h       ;save ptr
       add     l
       mov     l,a
       mov     a,h
       aci     0
       mov     h,a
       mvi     m,0     ;store ending zero
       pop     h       ;pt to first char
       call    sblank  ;skip over blanks
       lxi     d,inbuf ;copy into input line buffer
       push    d       ;save ptr to first char
start0:
       mov     a,m     ;get byte
       stax    d       ;put it
       inx     h       ;pt to next
       inx     d
       ora     a       ;end of string?
       jnz     start0

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

       pop     h       ;HL pts to first non-blank char
       mov     a,m     ;get it
       ora     a       ;EOL?
       jz      help
       cpi     '/'     ;option?
       jnz     start1
;
;  Print Help Message
;
help:
       call    print
       db      cr,lf,' MCHECK is used to perform a syntax check on a MENU.CPR'
       db      cr,lf,'file.  It is invoked with the following forms:'
       db      cr,lf,'         MCHECK or MCHECK //     <-- Print this Help'
       db      cr,lf,'         MCHECK dir:filename.typ <-- Check File'
       db      cr,lf,'         MCHECK dir:filename     <-- Check filename.CPR'
       db      cr,lf,0
       ret

;
;  Begin serious processing -- locate the file pted to by HL
;
start1:
       call    retud           ;get current user/disk
       mov     a,b             ;save disk
       sta     cdisk
       mov     a,c             ;save user
       sta     cuser
       lxi     d,mfcb          ;pt to FCB
       call    zfname          ;look for file
       jnz     start2
       call    print
       db      cr,lf,'Error in Disk or User Number -- Aborting',0
       ret
;
;  Set File Type to CPR if not specified
;
start2:
       lxi     h,mtyp          ;pt to file type
       mov     a,m             ;get first char
       cpi     ' '             ;set type if <SP>
       jnz     start3
       push    b               ;save BC
       lxi     d,cprtyp        ;set type to CPR
       xchg
       mvi     b,3             ;3 bytes
       call    moveb
       pop     b               ;get BC
;
;  Set User and Disk in C and B
;
start3:
       mvi     a,0ffh          ;get current disk indicator
       cmp     b               ;B=indicator?
       jnz     start4
       lda     cdisk           ;select current disk
       mov     b,a             ;... in B
       inr     b               ;in range 1-16
start4:
       dcr     b               ;adjust disk to 0-15
       mvi     a,0ffh          ;get current user indicator
       cmp     c               ;C=indicator?
       jz      start5
       mvi     a,'?'           ;if C=All Users, default to current
       cmp     c
       jnz     start6
start5:
       lda     cuser           ;select current user
       mov     c,a
start6:
       call    logud           ;log in user and disk
       lxi     d,mfcb          ;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,'Error in Opening File -- Aborting',0
       ret
;
;  Read in File
;
readfile:
       call    codend          ;get address of first block
readloop:
       lxi     d,mfcb          ;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 -- MENU.CPR File is Too Big -- Aborting',0
       ret
;
;  Read is Done -- Store Ending ^Z and Set Initial Values
;
readdone:
       mvi     m,EOF   ;Store ^Z to ensure EOF
       lxi     d,mfcb          ;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           ;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      'Error -- 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      'Error -- Premature EOF Encountered -- Aborting',0
       jmp     errxit
nm2a:
       cpi     MINDIC          ;Menu Indicator?
       jnz     nm2             ;Continue
;
;  Move Thru Menu Commands
;
nm3:
       call    nxtline         ;skip to next 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
;
;  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
       lda     gopt            ;global option?
       ora     a               ;0=no
       mov     a,b             ;get char
       jnz     optcl1          ;skip RSM test if it is global
       cpi     RSM             ;System Menu?
       jz      optclp
optcl1:
       cpi     COPTION         ;check options
       jz      optclp
       cpi     DOPTION
       jz      optclp
       cpi     POPTION
       jz      optclp
       cpi     XOPTION
       jz      optclp
       call    newerr          ;increment error count
       call    lprint
       db      'Error -- 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      'Error -- 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

inbuf:
       ds      250     ;input line buffer
cdisk:
       ds      1       ;current disk
cuser:
       ds      1       ;current user
mfcb:
       db      0
       ds      8       ;file name
mtyp:
       ds      3       ;file type
       ds      4
       ds      16
       ds      4
cprtyp:
       db      'CPR'
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