;
; Program: SH
; Author: Richard Conn
; Version: 1.0
; Date: 5 Mar 84
;

;
;       This program illustrates the design of a simple shell under ZCPR3
; using Z3LIB.  This program is transportable from one ZCPR3 system to another
; provided it is reassembled with the correct address for the ZCPR3
; Environment Descriptor (Z3ENV) or DDT is used to patch this address
; (which is the first two bytes after the opening JMP).  If an external
; ZCPR3 Environment Descriptor is not available, one will have to be
; provided by setting the Z3ENV equate to 0 and providing SYSENV.LIB in
; the current directory at assembly time.
;

;
; Equates for Key Values
;
z3env   SET     0f400h  ;address of ZCPR3 environment
lecnt   equ     20      ;number of pointers on String Ptr Stack
ctrlz   equ     'Z'-'@' ;^Z for EOF
cmdch   equ     '!'     ;in comment mode, invoke following text as command
cmtch   equ     ';'     ;comment character
subch   equ     '%'     ;substitution flag
fcb     equ     5ch
tbuff   equ     80h
cr      equ     0dh
lf      equ     0ah

;
; External Z3LIB and SYSLIB Routines
;
       ext     getshm,putshm
       ext     getud,putud,logud,initfcb,f$open,f$read,f$close
       ext     parser,codend,caps,fillb,hmovb,root,getfn1,pfn1
       ext     retud,print,pafdc,bbline,moveb,crlf,cout,sksp,dutdir
       ext     z3init,getsh2,shpush,shpop,qshell,getefcb,putcl,putcst,putzex

;
; 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 Environment
;
; Set Pointers
;
       call    codend  ;find scratch area
       shld    intline ;set ptr to internal line buffer
       lxi     d,200H  ;reserve 200H bytes
       dad     d
       shld    varlist ;set ptr to variable list
       xra     a
       sta     loadfl  ;say variables not loaded
;
; Check for Shell Stack
;
       call    getsh2  ;get shell status
       jnz     start0  ;skip over shell init
       call    print
       db      'No Shell Stack',0
       ret
;
; See if this program was invoked as a shell
;
start0:
       call    qshell  ;find out from ZCPR3 environment
       jz      shell   ;do not push onto stack if invoked as a shell
start1:
;
; Clear Shell Message for Comment Flag
;
       mvi     b,0     ;message 0
       xra     a       ;A=0 to clear
       call    putshm  ;set message
;
; Set Name of Shell Variable File if One is Given
;
       call    getfn1  ;pt to buffer in env
       lxi     d,fcb+1 ;pt to name
       xchg            ;copy from name
       mvi     b,11    ;11 bytes
       mov     a,m     ;check for name
       cpi     ' '     ;no name if space
       jz      defname
       call    moveb   ;copy if name present
       call    setshdef        ;set default file type
       jmp     setshn
;
; Set Name of SH.VAR File
;
defname:
       call    getfn1  ;get name
       lxi     d,shvfcb+1      ;pt to FCB
       xchg
       mvi     b,11    ;11 chars
       call    moveb
;
; Set Name of Shell from External FCB if Possible or From Default if Not
;
setshn:
       call    root    ;get root address
       lxi     h,shdisk        ;pt to shell disk
       mov     a,b     ;get disk
       adi     'A'     ;convert to letter
       mov     m,a     ;set disk letter
       inx     h       ;pt to user 10's
       mov     a,c     ;get user number
       mvi     b,10    ;subtract 10's
       mvi     d,'0'   ;set char
setshn1:
       sub     b       ;subtract
       jc      setshn2
       inr     d       ;increment digit
       jmp     setshn1
setshn2:
       add     b       ;get 1's
       mov     m,d     ;set 10's digit for user
       inx     h       ;pt to 1's digit
       adi     '0'     ;compute 1's digit
       mov     m,a     ;set 1's digit
       call    getefcb ;get ptr to external fcb
       jz      start2  ;no external FCB, so use default name
       inx     h       ;pt to program name
       lxi     d,shname        ;pt to string
       mvi     b,8     ;8 chars
       call    moveb   ;copy into buffer
;
; Push Name of Shell onto Stack
;
start2:
       lxi     h,shdisk        ;pt to name of shell
       call    shpush  ;push shell onto stack
       jnz     start3
;
; Shell Successfully Installed
;
       call    print
       db      'Shell Installed',0
       ret
;
; Shell Stack Push Error
;
start3:
       cpi     2       ;shell stack full?
       jnz     start4
;
; Shell Stack is Full
;
       call    print
       db      'Shell Stack Full',0
       ret
;
; Shell Stack Entry Size is too small for command line
;
start4:
       call    print
       db      'Shell Stack Entry Size',0
       ret
;
; Restart on Empty Line
;
shellr:
       call    crlf    ;new line
;
; Print Shell Prompt
;
shell:
       call    retud   ;get current user and disk
       mov     a,b     ;save disk
       adi     'A'
       call    cout    ;print disk letter
       mov     a,c     ;get user
       call    pafdc   ;print A as floating decimal
       call    dutdir  ;convert into DIR reference if possible
       jz      shell1  ;no match
       mvi     a,':'   ;print colon
       call    cout
       mvi     b,8     ;8 chars max
shell0:
       mov     a,m     ;get char
       inx     h       ;pt to next
       cpi     ' '     ;space?
       cnz     cout
       jnz     shell0
shell1:
       mvi     b,0     ;get shell message 0
       call    getshm
       ani     1       ;test for prompt
       jz      shell2  ;print normal prompt
       call    print   ;comment format
       db      '; ',0  ;comment
       jmp     shell3
shell2:
       call    print   ;normal format
       db      '>> ',0 ;double prompt
;
; Accept User Input
;
shell3:
       mvi     a,1     ;tell ZEX that prompt is up
       call    putzex
       xra     a       ;don't capitalize
       call    bbline
       mvi     a,0     ;say that normal processing is running now
       call    putcst
       call    putzex
       call    sksp    ;skip over leading spaces
       mvi     b,0     ;get shell message 0
       call    getshm
       ani     1       ;test for comment
       jz      shell4  ;process normally
;
; Process Shell Input as Comment
;
       mov     a,m     ;get char
       cpi     cmdch   ;command override?
       jnz     shellr  ;continue with next line if not
       inx     h       ;pt to command and fall thru to process
;
; Process Shell Command
;
shell4:
       mov     a,m     ;get first char
       ora     a       ;no line?
       jz      shellr
       cpi     cmtch   ;comment line?
       jz      shellr
;
; Check for and Process Shell-Resident Command
;
       push    h       ;save HL
       call    shcommand       ;check for shell command
       pop     h       ;restore HL
;
; Expand Shell Command Line
;
       call    expand  ;expand line pted to by HL
       jz      clovfl  ;abort if overflow
;
; Load Multiple Command Line
;
       call    putcl   ;place command line pted to by HL into CL Buffer
       rnz             ;resume ZCPR3 processing
;
; Input Line is Longer than Command Line Buffer
;
clovfl:
       call    print   ;command line buffer has overflowed
       db      cr,lf,'CL Ovfl ',0
       jmp     shellr
;
; Expand Shell Command Line (pted to by HL), performing variable
;       Substitutions
;
;       On exit, Z=command line overflow and Line Pted to by HL
;
expand:
       push    b               ;save counter
       mvi     b,0             ;get shell register 0
       call    getshm
       ani     2               ;test for echo
       cnz     crlf            ;new line if SHECHO is ON
       pop     b               ;get counter
       xchg                    ;DE pts to line
;
; Init String Pointer Stack
;
       mvi     a,lecnt         ;set local element count
       sta     locelt
       lxi     h,locstk        ;set local stack
       shld    locadr
       lxi     h,0             ;set done code on stack
       call    locpush         ;push HL
;
; Set Ptrs
;
       lhld    intline         ;pt to internal line
       xchg                    ;DE pts to internal line, HL pt next char
       mvi     b,0             ;256 chars max
;
; Analyze Next Char
;
exp1:
       mov     a,m             ;get next char
       cpi     subch           ;substitution char?
       jnz     exp2            ;handle normally
;
; Process Shell Variable
;
       call    expvar          ;resolve variable
       dcr     c               ;error?
       jz      exp1            ;resume if none
;
; Store Next Char
;
exp2:
       stax    d               ;store char
;
; Print Char if SHECHO is ON
;
       push    b               ;save counter
       mov     c,a             ;save char
       mvi     b,0             ;get shell message 0
       call    getshm          ;determines if display is on
       ani     2               ;test for echo
       jz      exp3
       mov     a,c             ;get char
       ani     7FH             ;mask and don't output null
       cnz     cout            ;echo char
;
; Advance to Next Char
;
exp3:
       mov     a,c             ;get char
       pop     b               ;get counter
       inx     h               ;pt to next
       inx     d
       dcr     b               ;count down
       jz      experr          ;error if at 0
       ora     a               ;done?
       jnz     exp1
       inr     b               ;increment count (not counting last 0)
       dcx     d               ;pt to 0 in case of abort
;
; Pop String Ptr Stack and Check for Analysis Complete
;
       call    locpop          ;get ptr to previous string
       mov     a,h             ;done?
       ora     l
       jnz     exp1            ;resume
       dcr     a               ;set NZ
;
; Expansion Complete
;       On entry, Z Flag is Set Accordingly (Z=Error)
;
experr:
       lhld    intline         ;pt to internal line
       ret

;
; Expand Variable
;       Return with HL pting to next char, A=char, C=1 if OK, C=2 if error
;
expvar:
       shld    varptr          ;save ptr to variable
       inx     h               ;get next char
       mvi     c,2             ;prep for error return
       mov     a,m             ;get it
       ora     a               ;EOL?
       rz
       cpi     subch           ;double sub char?
       rz                      ;place one sub char in line if so
;
; Place Variable Into SHVAR
;
       push    b               ;save counter
       push    d               ;save ptr to next char
       push    h               ;save ptr to shell variable
       lxi     h,shvar         ;pt to shell variable buffer
       mvi     b,8             ;8 chars max
       mvi     a,' '           ;space fill
       call    fillb
       xchg                    ;DE pts to shell variable buffer
       pop     h               ;pt to shell variable
       mvi     b,8             ;8 chars max
;
; Place Shell Variable into Buffer
;
expv1:
       mov     a,m             ;get char
       call    delck           ;check for delimiter
       jz      expv3           ;done if delimiter
       stax    d               ;save char
       inx     h               ;pt to next
       inx     d
       dcr     b               ;count down
       jnz     expv1
;
; Flush Overflow of Shell Variable
;
expv2:
       mov     a,m             ;get char
       inx     h               ;pt to next
       call    delck           ;check for delimiter
       jnz     expv2
       dcx     h               ;pt to delimiter
;
; Shell Variable in buffer SHVAR
;       HL pts to delimiter after variable in user line
;
expv3:
       call    locpush         ;stack ptr to next char in current string
       jz      expv4           ;error in stack
       call    varload         ;load shell variable list
       jz      expv4           ;failure
       call    namer           ;resolve named variable reference
       mvi     c,1             ;OK
       jz      expv5           ;name found - resolve
;
; Shell Variable Not Resolved - Restore Ptr to It
;
expv4:
       call    locpop          ;restore ptr
       mvi     c,2             ;error
       lhld    varptr          ;pt to variable
;
; Entry Point for OK Return
;
expv5:
       mov     a,m             ;get char
       pop     d               ;pt to target
       pop     b               ;get counter
       ret

;
; Push HL onto String Ptr Stack
;       Return with Z if Stack Overflow
;
locpush:
       lda     locelt          ;get count
       dcr     a               ;full?
       rz
       sta     locelt          ;set count
       push    d               ;save DE
       xchg                    ;DE pts to old string
       lhld    locadr          ;get ptr to top of stack
       mov     m,e             ;store low
       inx     h
       mov     m,d             ;store high
       inx     h               ;pt to next
       shld    locadr
       xchg                    ;restore HL
       pop     d               ;restore DE
       xra     a               ;return NZ
       dcr     a
       ret
;
; Pop HL from String Ptr Stack
;
locpop:
       push    d
       lda     locelt          ;increment element count
       inr     a
       sta     locelt
       lhld    locadr          ;get address
       dcx     h               ;pt to high
       mov     d,m             ;get high
       dcx     h               ;pt to low
       mov     e,m             ;get low
       shld    locadr          ;set address
       xchg                    ;restore ptr
       pop     d
       ret


;
; Load Shell Variable List
;
varload:
       push    h               ;save regs
       push    d
       push    b
       lda     loadfl          ;already loaded?
       ora     a               ;NZ=yes
       jnz     varl3
       lhld    varlist         ;clear varlist in case of error
       mvi     m,ctrlz
;
; Look for Variable File
;
       call    getfn1          ;pt to file name of SH.VAR
       lxi     d,shvfcb+1
       mvi     b,11            ;11 bytes
       call    moveb
       call    putud           ;save current location
       call    root            ;determine DU of root
       call    logud           ;goto root
       lhld    varlist         ;pt to named variable list
       lxi     d,shvfcb        ;try to open file
       call    initfcb         ;init FCB
       call    f$open
       jz      varl1
;
; Variable File Not Found
;
       call    getud           ;return home
       xra     a               ;set not found code
       pop     b               ;restore regs
       pop     d
       pop     h
       ret
;
; Read in Variable File
;
varl1:
       lxi     d,shvfcb        ;read in file
       call    f$read
       jnz     varl2
       lxi     d,tbuff         ;pt to data
       xchg                    ;copy into memory
       mvi     b,128           ;128 bytes
       call    hmovb
       xchg
       jmp     varl1
varl2:
       lxi     d,shvfcb        ;close file
       call    f$close
       call    getud           ;return home
;
; Say List is Already Loaded
;
varl3:
       xra     a               ;return NZ for OK
       dcr     a
       sta     loadfl          ;set loaded flag
       pop     b               ;restore regs
       pop     d
       pop     h
       ret

;
; Resolve Named Variable Reference
;       On input, SHVAR contains the shell variable name and
;       CODEND pts to the list of shell variables, terminated by ^Z;
;       if found, return with HL pting to name and Z
;
namer:
       lhld    varlist         ;pt to variable list
namer1:
       mov     a,m             ;get char
       cpi     ctrlz           ;end of list?
       jz      namex
       lxi     d,shvar         ;pt to shell variable name
       mvi     b,8             ;8 chars
namer2:
       ldax    d               ;get name
       cmp     m               ;match?
       jnz     nomatch
       inx     h               ;pt to next
       inx     d
       dcr     b               ;count down
       jnz     namer2
       ret                     ;found!
nomatch:
       mov     a,m             ;flush to end of string
       inx     h               ;pt to next
       ora     a
       jnz     nomatch
       jmp     namer1          ;resume search
;
; Search Failed
;
namex:
       ora     a               ;return NZ (^Z in A)
       ret
;
; Check to see if char in A is a delimiter
;       Return with Z if so
;
delck:
       push    h               ;pt to table
       push    b               ;save BC
       call    caps            ;capitalize char
       mov     b,a             ;char in B
       lxi     h,dtable        ;pt to delimiter table
delck1:
       mov     a,m             ;get delimiter
       ora     a               ;done?
       jz      notdel
       cmp     b               ;compare
       jz      yesdel
       inx     h               ;pt to next
       jmp     delck1
notdel:
       mov     a,b             ;get char
       ora     a               ;set Z if null, else NZ
yesdel:
       mov     a,b             ;restore char
       pop     b               ;restore regs
       pop     h
       ret

;
; Delimiter Table
;
dtable:
       db      '<>;:,.=-_ ',0

;
; Check for Shell Command and Process if Found
;       HL pts to command line
;
shcommand:
       xra     a               ;DIR before DU
       call    parser          ;parse command line pted to by HL
       inx     d               ;pt to name
       lxi     h,ctable        ;pt to command table
shcmd:
       mov     a,m             ;get first char of next entry
       ora     a               ;done?
       rz
       mvi     b,8             ;commands are 8 chars long
       push    h               ;save ptr to FCB
       push    d               ;save ptr to table entry
shcmd1:
       ldax    d               ;compare
       cmp     m
       jnz     shcmd2
       inx     h               ;pt to next
       inx     d
       dcr     b               ;count down
       jnz     shcmd1
;
; Command Found - Get Address
;
       mov     a,m
       inx     h
       mov     h,m
       mov     l,a             ;HL is address
;
; Clear Stack and Run
;
       pop     psw             ;clear stack
       pop     psw
       pop     psw             ;clear return address
       pop     psw             ;clear pushed HL
       pchl                    ;"run" command
;
; Command Not Found Yet
;
shcmd2:
       pop     d               ;restore ptrs
       pop     h
       lxi     b,10            ;advance to next command
       dad     b
       jmp     shcmd           ;resume search

;
; If File Type not Specified, Set Default
;
setshdef:
       call    getfn1          ;check for file type
       lxi     d,8             ;pt to file byte
       dad     d
       xchg
       lxi     h,shvtype       ;default file type
       mvi     b,3             ;3 chars
       ldax    d               ;get char
       cpi     ' '             ;set if space
       cz      moveb           ;copy
       ret
;
; Pop Current Shell
;
shexit:
       call    print
       db      cr,lf,'Exiting Shell',0
       jmp     shpop   ;clear shell stack entry

;
; Toggle Shell Comment Mode
;
shcomment:
       mvi     b,0     ;access shell register 0
       call    getshm
       mov     c,a     ;save in C
       ani     0FEH    ;all bits but comment bit
       mov     d,a
       mov     a,c     ;get comment bit
       cma             ;flip comment bit (other bits are 1)
       ani     1       ;select just comment bit
       ora     d       ;OR in other bits
       call    putshm  ;set new value
       jmp     shellr  ;resume
;
; Toggle Shell Echo Mode
;
shecho:
       call    print
       db      cr,lf,' Echo of Shell Commands is O',0
       mvi     b,0     ;access shell register 0
       call    getshm
       mov     c,a     ;save in C
       ani     0FDH    ;all bits but echo bit
       mov     d,a
       mov     a,c     ;get comment bit
       cma             ;flip comment bit (other bits are 1)
       ani     2       ;select just echo bit
       ora     d       ;OR in other bits
       call    putshm  ;set new value
       ani     2       ;test echo bit
       jz      shecho1
       call    print
       db      'N',0
       jmp     shellr
shecho1:
       call    print
       db      'FF',0
       jmp     shellr  ;resume

;
; Print Names of SH Commands
;
shhelp:
       call    print
       db      cr,lf,'SH Commands --',cr,lf,0
       lxi     h,ctable        ;pt to table
       mvi     c,0             ;set count
shh1:
       mov     a,m             ;done?
       ora     a
       jz      shellr
       call    print
       db      '  ',0
       mvi     b,8             ;8 chars
shh2:
       mov     a,m             ;get char
       call    cout
       inx     h               ;pt to next
       dcr     b               ;count down
       jnz     shh2
       inr     c               ;increment count
       mov     a,c
       ani     3               ;new line?
       cz      crlf
       inx     h               ;skip address
       inx     h
       jmp     shh1            ;next

;
; Command Table
;
ctable:
       db      '?       '      ;help
       dw      shhelp
       db      'SHCMT   '      ;comment mode
       dw      shcomment
       db      'SHECHO  '      ;echo input
       dw      shecho
       db      'SHEXIT  '      ;exit shell
       dw      shexit
       db      0               ;end of table

;
; Buffers
;
shvfcb:
       db      0
       db      'SH      '      ;name of shell variable file
shvtype:
       db      'VAR'
       ds      24              ;36 bytes total
shdisk:
       db      'A'             ;disk letter
       db      '00'            ;user number
       db      ':'             ;separator
shname:
       db      'SH      ',0    ;name of shell to go onto stack
shvar:
       db      '        '      ;shell variable
locelt:
       ds      1               ;string stack element count
locadr:
       ds      2               ;ptr to next entry on stack
locstk:
       ds      lecnt*2         ;string ptr stack
varptr:
       ds      2               ;ptr to current variable in line
varlist:
       ds      2               ;ptr to named variable list
intline:
       ds      2               ;ptr internal expansion line
loadfl:
       ds      1               ;variables loaded flag

       end