        name mssscp
; File mssscp.asm
; Edit History
; Last edit: 1 Jan 1988
; 6 Jan 1988 Fix pointer for out @con calls. [jrd]
; 1 Jan 1988 version 2.30
; 26 Dec 1987 Use no-echo reading of console for OUTPUT @CON, speedup
;  reading of host response for OUTPUT command. [jrd]
; 4 Dec 1987 Update global byte errlev when a Script command Fails
;  (timeout or output failure or manual interruption). [jrd]
; 9 Oct 1987 Allow curly braced strings, trim trailing whitespace too. [jrd]
; 4 Oct 1987 Apply Set Display 7/8 bit mask to bytes rcv'd from serial port
;  after Set Translation Input filter. Log 8-bit chars in Debug mode. [jrd]
; 26 Sept 1987 Add check for Control-C interrupt to chkkbd and ECHO,
;  make Control-C in TRANSMIT command use squit exit. [jrd]
; 18 Aug 1987 Change ESC to escape for MASM 4.5+ [jrd]
; 11 Aug 1987 Add Set Send Pause plus 3 millisec wait before doing OUTPUT.[jrd]
; 31 July 1987 Open port reading to null chars et al. Correct timeofday
;  routine, from Jack Bryans. [jrd]
; 22 July 1987 Rewrite time of day material for no ambiguities. [jrd]
; 15 July 1987 Terminate strings read as @filespec on first carriage return.
;  Change number parsing to use decimal as default and \bddd for other bases.
; 24 May 1987 Add error recovery for outchr calls. [jrd]
; 10 May 1987 Add translation of input characters, rxtable. [jrd]
; 4 April 1987 Clear Echo's old text line, from Eberhard Lisse. [jrd]
; 18 March 1987 Add requests for command confirmation. [jrd]
; 2 March 1987 Remove test of Set Input Echo from OUTPUT command. [jrd]
; 27 Oct 1986 preserve data char in al around call to outchr in Output [jrd]
; 19 Oct 1986 Add "\b" and "\B" to Output procedure as send-a-Break command
;  to serial port comms line. [jrd]
; 1 Oct 1986 Version 2.29a
; 1 Oct 1986 Add 7/8 bit display mask to displayed text. [jrd]
; 12 Sept 1986 Add changes from Frank da Cruz: one second default timeout,
;  echo chars if Local Echo is On, Input command without a pattern should
;  behave like a Pause command.
;
; MS Kermit Script routines, DEC-20 style.
; Extensively rewritten for MS Kermit 2.29a by Joe R. Doupnik 5 July 86
;;
;    Created June, 1986 a.d.    By James Sturdevant
;                                         A. C. Nielsen Co. Mpls.
;                                         8401 Wayzata Blvd.
;                                         Minneapolis, Mn. 55426
;                                         (612)546-0600
;;;;;;;;
; Kermit command level usages and this file's entry points:
; Clear - clears serial port buffers. Procedure scclr.
; Echo text - displays text on local screen. Proc scecho.
; Pause [time] - waits indicated number of seconds (default is Input
;       Default-timeout, 1 second typically). Proc scpau.
; Input [time] text - waits up to time seconds while scanning serial port
;       input for a match with text. Default value for time is Input
;       Default-timeout, 1 second typically). Spaces or tabs are separators
;       between the time and text fields. Proc scinp.
;       A carriage return typed by the local user simulates a match.
; Output text - sends the text to the serial output port. Proc scout.
; Transmit text [prompt] - raw file transfer to host. Proceeds from source
;       line to source line upon receipt of prompt from host or carriage
;       return from us. Default prompt is linefeed. A null prompt (\0)
;       causes the file to be sent with no pausing or handshaking. Note
;       that linefeeds are stripped from outgoing material. Proc scxmit.
; In the above commands "text" may be replaced by "@filespec" to cause the
;       one line of that file to be used instead. @CON obtains one line of
;       text from the keyboard. Such "indirect command files" may be nested
;       to a depth of 100. Control codes are written as decimal numbers
;       in the form "\ddd" where d is a digit between 0 and 9. Carriage
;       return is \13, linefeed is \10, bell is \7; the special code \255
;       is used to match (Input) either cr or lf or both.
; These commands can be given individually by hand or automatically
;       in a Kermit Take file; Take files may be nested.
;;;;;;;;
; These routines expect to be invoked by the Kermit command dispatcher
; and can have their default operations controlled by the Kermit Set Input
; command (implemented in file mssset.asm). They parse their own cmd lines.
; Set Input accepts arguments of
;   Case Ignore or Observe  (default is ignore case when matching strings)
;   Default-timeout seconds (default is 5 seconds)
;   Echo On or Off      controls echoing of Input cmd text (default is Off)
;   Timeout-action Quit or Proceed (default is Proceed)
; These conditions are passed via global variables incasv,indfto,inecho,
;   inactv, respectively, stored here.
;;;;;;;;;

        include mssdef.h
        public  scout, scinp, scpau, scecho, scclr, scxmit

linelen         equ     134             ; length of working buffer line
prtbuflen       equ     128             ; serial port local buffer length
maxtry          equ     5               ; maximum number of output retries
stat_unk        equ     0               ; status return codes.
stat_ok         equ     1               ; have a port character
stat_cc         equ     2               ; control-C typed
stat_tmo        equ     4               ; timeout
stat_cr         equ     8               ; carriage return typed

datas   segment public 'datas'
        public  indfto, inactv, incasv, inecho
        extrn   taklev:byte, takadr:word, portval:word, flags:byte
        extrn   rxtable:byte, spause:byte, errlev:byte

                                        ; global (public) variables
inactv  db      0                       ; input action value (default proceed)
incasv  db      0dfh                    ; input case  (default ignore)
indfto  dw      1                       ; input and pause timeout (def 1 sec)
inecho  db      1                       ; echo Input cmd text (0 = no)
                                        ; local variables
line    db      linelen+1 dup (?)       ; line of output or input + terminator
prtbuf  db      prtbuflen dup (?)       ; serial port storage buffer
bufcnt  dw      0                       ; serial port buf byte cnt, must be 0
bufrdptr dw     prtbuf                  ; serial port buf read ptr
bufwtptr dw     prtbuf                  ; serial port buf write ptr
temptr  dw      ?                       ; temporary pointer
temptr2 dw      ?                       ; ditto, points to end of INPUT string
tempd   dw      ?                       ; temp
tempa   db      ?                       ; another temp
retry   db      0                       ; number of output retries
status  dw      ?                       ; general status word
fhandle dw      ?                       ; file handle storage place
parmsk  db      7fh                     ; 7/8 bit parity mask
lecho   db      ?                       ; local echo of output (0 = no)
timout  dw      ?                       ; work area (seconds before timeout)
timhms  db      4 dup (?)               ; hhmmss.s tod buffer

crlf    db      cr,lf,'$'
xfrfnf  db      cr,lf,'?Transmit file not found$'
xfrrer  db      cr,lf,'?error reading Transmit file$'
xfrcan  db      cr,lf,'?transmission canceled$'
indmis  db      '?Indirect file not found',cr,lf,'$'
inderr  db      '?error reading indirect file',cr,lf,'$'
tmomsg  db      cr,lf,'?Timeout$'
outhlp  db      'line of text to be sent to remote host$'
inphlp  db      'time limit and line of text expected from remote host$'
echhlp  db      'line of text to echo to screen$'
ptshlp  db      'number of seconds to pause$'
xmthlp  db      'File specification with optional path name$'
pmthlp  db      'Prompt character expected as an ACK from host (\0 for none)$'
datas   ends

code    segment public 'code'

        extrn   comnd:near, clrbuf:near, prtchr:near, outchr:near, sendbr:near
        extrn   cptchr:near, serini:near, serrst:near, pcwait:near, katoi:near
        extrn   cnvstr:near
        assume  cs:code, ds:datas

; Clear input buffer(s) of serial port
; Clear command
;
SCCLR   PROC    NEAR
        mov     ah,cmcfm                ; get a confirm
        call    comnd
         jmp r                          ; no confirm
         nop
        call    bufclear                ; clear our serial port circular buf
        call    clrbuf                  ; clear system serial port buffer too
        jmp     rskp                    ; return success
SCCLR   ENDP
;
; Echo a line of text to our screen
; Echo text
;
SCECHO  PROC    NEAR
        mov     ah,cmtxt                ; get a whole line of asciiz text
        mov     bx,offset line          ; where to store in
        mov     word ptr [bx],0         ; clear line
        mov     dx,offset echhlp        ; help
        call    comnd
         jmp    rskp                    ; ignore parse error if no text
        mov     si,offset line          ; start of line
        mov     di,si                   ; convert to the same place
        mov     ah,incasv               ; save current case state
        push    ax
        mov     incasv,0ffh             ; say no case conversion
        call    cnvlin                  ; convert \numbers to binary
        pop     ax
        mov     incasv,ah               ; recover case state
        jc      echo3                   ; carry set means error
        mov     al,lf                   ; start with a linefeed
        call    scdisp                  ; show it
        jcxz    echo2                   ; z = nothing to show
echo1:  push    cx                      ; save loop counter
        cld
        lodsb                           ; get a source char into al
        push    si
        call    scdisp                  ; display the char
        pop     si
        pop     cx
        loop    echo1                   ; get another
echo2:  jmp     rskp                    ; return success
echo3:  ret                             ; error
SCECHO  ENDP

; Input from port command, match input with text pattern
; Input [timeout] text
;
SCINP   PROC    NEAR
        mov     ah,cmtxt                ; get a whole line of asciiz text
        mov     bx,offset line          ; place to put text
        mov     dx,offset inphlp        ; help message
        call    comnd                   ; get the pattern text
         jmp    r                       ; nothing, complain
        mov     ah,cmcfm                ; get a confirm
        call    comnd
         jmp r                          ; no confirm
         nop
        cmp     taklev,0                ; are we in a Take file?
        je      input0                  ; e = no, display linefeed
        cmp     flags.takflg,0          ; are Take commands being echoed?
        je      input1                  ; e = no, skip display
input0: cmp     inecho,0                ; Input echo off?
        je      input1                  ; e = yes
        mov     al,lf                   ; next line
        call    scdisp                  ; display the char
input1: call    serini                  ; initialize the system's serial port
        mov     status,stat_unk         ; clear status flag
        call    inptim                  ; get the timeout time, sets si
        mov     di,offset line          ; put text in compare buffer
        call    cnvlin                  ; convert \numbers in buf line
        jnc     input2                  ; nc = no error
        ret                             ; else return on error
input2: mov     parmsk,0ffh             ; parity mask, assume 8 bit data
        mov     di,portval
        cmp     [di].parflg,parnon      ; parity is none?
        je      input2a                 ; e = none
        mov     parmsk,07fh             ; else strip parity (8th) bit
input2a:mov     di,offset line
        mov     temptr,di               ; pointer to pattern char
        mov     temptr2,di              ; and we need pointer to end of string
        add     temptr2,cx              ; offset of end of string
        cmp     cx,0                    ; empty pattern? (cnvlin sets cx=cnt)
        jne     input4                  ; ne = not empty
                                        ; empty. read, display, and discard
input3: call    chkkbd                  ; check keyboard
        test    status,stat_cc          ; did user type control-c?
        jnz     input5                  ; nz = yes, quit
        test    status,stat_cr          ; did user type cr? [js]
        jnz     inputx                  ; nz = yes, return success [js]
        call    chktmo                  ; check timeout
        test    status,stat_tmo
        jnz     input5                  ; nz = timed out, quit
        call    bufread                 ; read from serial port buffer into al
        jmp     input3                  ; loop until timeout

                                        ; start main read and compare loop
input4: mov     di,temptr               ; pointer to current pattern char
        cmp     di,temptr2              ; at end of pattern?
        jae     inputx                  ; ae = yes, return success
        call    chkkbd                  ; check keyboard
        test    status,stat_cc          ; did user type control-c?
        jnz     input5                  ; nz = yes, quit
        test    status,stat_cr          ; did user type cr? [js]
        jnz     inputx                  ; nz = yes, return success [js]
        call    chktmo                  ; check timeout
        test    status,stat_tmo
        jnz     input5                  ; nz = timed out, quit
        call    bufread                 ; read from serial port buffer into al
        jc      input4                  ; c = nothing there, keep looking
        cmp     al,'a'                  ; candidate for case conversion? [js]
        jb      inpu4a                  ; b = no [js]
        cmp     al,'z'                  ; in lower case set? [js]
        ja      inpu4a                  ; a = no [js]
        and     al,incasv               ; apply case conversion mask
inpu4a: mov     di,temptr
        mov     ah,byte ptr [di]        ; get current pattern char again
        call    matchr                  ; al=rcvd, ah=pattern, do they match?
        jc      inpm                    ; c = no match, try substring
        inc     temptr                  ; matched, point to next pattern char
        jmp     input4
input5: or      errlev,2                ; set RECEIVE failure condition
        jmp     squit                   ; exit failure: timeout or control-c
inputx: jmp     rskp                    ; return success

; See if a trailing-subset of the matched chars + new port char can match
; the beginning part of the pattern. That is, if we were to simply "forget"
; the oldest of the matched chars and slide left the apparent port string
; then could we eventually find a match? Example: "Input 10 memema"
; gives the pattern of "memema"; suppose the received chars were "mememema".
; Forgetting one left-most rcv'd char at a time (two in this case) finally
; yields a match, from which we should continue to compare fresh port chars
; with successive pattern chars until either they match through all pattern
; chars or we encounter another break. If there is a later break, repeat this
; algorithm.
; Since we really have only the latest char from the port then pointers to
; the matched pattern chars are used to mimic the earlier received chars:
; they must have been identical to produce a match to date. The quick way
; to "forget" oldest received chars is to scan backward through the matched
; pattern chars looking for the current port char; if the first such find does
; not yield a matching substring then look back further.
                                ; no or partial match then break
                                ; di = temptr = pattern break char
                                ; al = port char causing break
                                ; di - offset line = # chars matched thus far
                        ; avoid cpu-brand side effects with "repne scasb"
inpm:   mov     tempa,al        ; save port char here
inpm1:  mov     tempd,di        ; pattern break loc, where matching failed
        mov     cx,di           ; char at di does not match current port char
        sub     cx,offset line  ; compute count of matched bytes
        jcxz    inpm4           ; z = 0 = mismatch on the initial pattern char

        mov     al,tempa        ; port char to find (in case we looped here)
inpm2:  dec     di              ; back up one pattern char
        mov     ah,byte ptr [di]; current pattern character to consider
        call    matchr          ; is port char = earlier pattern char? [js]
        jnc     inpm3           ; nc = equal values, go construct substring
        loop    inpm2           ; do cx times, max. (length of match to date)
        jmp     inpm4           ; get here when there are no matches [js]

inpm3:  mov     bx,tempd        ; get last break location
        sub     bx,di           ; displacement = break - new find of port char
        mov     tempd,di        ; remember new location of a port-like char
                                ; cx has number of chars in test substring
        dec     cx              ; matched one char already [jrs]
        jcxz    inpm3a          ; is there anything left? [jrs]
        call    matstr          ; does this substring match the pattern?
        jc      inpm1           ; c = no match, try making substring smaller

inpm3a: mov     di,tempd        ; sub-string matched. Use this shorter match
        mov     temptr,di       ; set di for exit (matstr messes up di)
        inc     temptr          ; matched, point to next pattern char
        jmp     input4          ; continue with fresh port info.

inpm4:  mov     temptr,offset line; complete failure, restart scanning
        jmp     input4          ; get something from the port.

; worker for SCINP
; compare strings. One starts at offset line, the other starts bx bytes later.
; cx = # chars to compare. Return carry clear if match, else carry set.
matstr: mov     si,offset line  ; start of pattern string
matstr1:mov     ah,byte ptr [si] ; pattern char
        mov     al,byte ptr [si+bx] ; "old port char" (same as pattern char)
        call    matchr          ; check match of these two characters
        jc      matstr2         ; c = no match (exit with carry flag set)
        inc     si              ; match, consider next pair
        loop    matstr1         ; consider rest of substring (cx is counter)
        clc                     ; clear c bit (substrings do match)
matstr2:ret                     ; preserves flags (c set = no match)

; worker for SCINP
; compare single characters, one in ah and the other in al. Allow the 0ffh
; wild card to match CR and LF individually. Return carry clear if match,
; or carry set if they do not match. Registers preserved.
matchr: cmp     ah,al           ; do these match?
        je      matchr6         ; e = yes
        cmp     ah,0ffh         ; the match cr/lf indicator?
        je      matchr2         ; e = yes
        cmp     al,0ffh         ; the match cr/lf indicator?
        jne     matchr5         ; ne = no match at all.
matchr2:push    ax              ; save both chars again
        and     ah,al           ; make a common byte for testing
        cmp     ah,cr
        je      matchr4         ; e = cr matches 0ffh
        cmp     ah,lf
        je      matchr4         ; e = lf matches 0ffh
        pop     ax              ; recover chars
matchr5:stc                     ; set carry (no match)
        ret
matchr4:pop     ax              ; recover chars
matchr6:clc                     ; clear carry (match)
        ret
SCINP   ENDP
;
; Pause for the specified number of seconds
; Pause [seconds]
;
SCPAU   PROC    NEAR
        mov     ah,cmfile               ; get a word (number)
        mov     dx,offset line          ; where to store it
        mov     byte ptr line,0         ; terminate line incase no text
        mov     bx,offset ptshlp        ; help msg
        call    comnd
         nop                            ; ignore parse errors (no text)
         nop                            ; must be at least 3 bytes
         nop
        mov     ah,cmcfm                ; get a confirm
        call    comnd
         jmp r                          ; no confirm
         nop
                                        ;
        call    inptim                  ; parse pause time (or force default)
        cmp     taklev,0                ; are we in a Take file
        je      paus0c                  ; e = no, print linefeed
        cmp     flags.takflg,0          ; are commands being echoed
        je      paus0d                  ; e = no, skip this
paus0c: cmp     inecho,0                ; Input echoing off?
        je      paus0d                  ; e = yes
        mov     al,lf                   ; next line
        call    scdisp                  ; display the char
paus0d: call    serini                  ; initialize the system's serial port
        mov     status,stat_unk         ; clear status flag
        push    si
        mov     parmsk,0ffh             ; parity mask, assume 8 bit data
        mov     si,portval
        cmp     [si].parflg,parnon      ; parity is none?
        pop     si
        je      pause1                  ; e = none
        mov     parmsk,07fh             ; else strip parity (8th) bit
pause1: call    chkport                 ; get and show any new port char
        call    chkkbd                  ; check keyboard
        test    status,stat_cc          ; control-c?
        jnz     pause2                  ; nz = yes, quit
        call    chktmo                  ; check tod for timeout
        test    status,stat_tmo         ; timeout?
        jz      pause1                  ; z = no, continue to wait
        jmp     rskp                    ; timeout, take successful exit
pause2: or      errlev,1+2              ; set SEND and RECEIVE error condx.
        jmp     squit                   ; take error exit
SCPAU   ENDP

; Output line of text to port, detect \b and \B as commands to send a Break
;  on the serial port line.
; Output text

SCOUT   PROC    NEAR
        mov     ah,cmtxt                ; get a whole line of asciiz text
        mov     bx,offset line          ; store text here
        mov     dx,offset outhlp        ; help message
        call    comnd
         jmp    r                       ; bad parse (no text)
         nop
        mov     ah,cmcfm                ; get a confirm
        call    comnd
         jmp r                          ; no confirm
         nop
        cmp     taklev,0                ; is this being done in a Take file?
        je      outpu0                  ; e = no, display linefeed
        cmp     flags.takflg,0          ; are commands being echoed?
        je      outp0a                  ; e = no, skip the display
outpu0: cmp     inecho,0                ; Input echoing off?
        je      outp0a                  ; e = yes
        mov     al,lf                   ; next line
        call    scdisp                  ; display the char
outp0a: mov     al,spause               ; wait three millisec or more
        add     al,3
        xor     ah,ah
        call    pcwait                  ; breathing space for HDX systems
        call    serini                  ; initialize the system's serial port
        mov     status,stat_unk         ; clear status flag
        mov     parmsk,0ffh             ; parity mask, assume 8 bit data
        mov     si,portval
        cmp     [si].parflg,parnon      ; parity is none?
        je      outp0b                  ; e = none
        mov     parmsk,07fh             ; else strip parity (8th) bit
outp0b: mov     si,portval              ; serial port structure
        mov     bl,[si].ecoflg          ; Get the local echo flag.
        mov     lecho,bl                ; our copy.
        mov     si,offset line          ; get start of line
        mov     di,offset line          ; put results in the same place
        mov     ah,incasv               ; save current case state
        push    ax
        mov     incasv,0ffh             ; say no case conversion
        call    cnvlin                  ; convert \numbers to binary
        pop     ax
        mov     incasv,ah               ; recover case state
        jnc     outpu1                  ; nc = no error
        ret                             ; return on error
outpu1: mov     temptr,offset line      ; save pointer here
        mov     tempd,cx                ; save byte count here
        jcxz    outpu2                  ; empty string

outpu2: cmp     tempd,0                 ; are we done?
        jg      outpu2a                 ; g = not done yet
        jmp     rskp                    ; return success
outpu2a:mov     si,temptr               ; recover pointer
        cld
        lodsb                           ; get the character
        dec     tempd                   ; one less char to send
        mov     temptr,si               ; save position on line
        mov     tempa,al                ; save char here for outchr
        mov     retry,0                 ; number of output retries
        cmp     al,5ch                  ; backslash?
        jne     outpu4d                 ; ne = no
        cmp     byte ptr [si],'b'       ; "\b" for Break?
        je      outpu4c                 ; e = yes
        cmp     byte ptr [si],'B'       ; "\B" ?
        jne     outpu4d                 ; ne = no
outpu4c:inc     temptr                  ; move scan ptr beyond "\b"
        dec     tempd
        call    sendbr                  ; call msx send-a-break procedure
        jmp     outpu5                  ; resume beyond echoing

outpu4d:inc     retry                   ; count output attempts
        cmp     retry,maxtry            ; too many retries?
        jle     outpu4g                 ; le = no
        or      errlev,1                ; set SEND failure condition
        jmp     squit                   ; return failure
outpu4g:mov     ah,tempa                ; outchr gets fed from ah
        call    outchr                  ; send the character to the port
         jmp     outpu4d                ; failure to send char
         nop                            ; ensure 3 bytes for rskp of outchr
        cmp     lecho,0                 ; is Local echo active?
        je      outpu5                  ; e = no
        mov     al,tempa                ;
        cmp     flags.capflg,0          ; is capturing active?
        je      outp4b                  ; e = no
        push    ax                      ; save char
        call    cptchr                  ; give it captured character
        pop     ax                      ; restore character and keep going
outp4b: cmp     inecho,0                ; Input echo off?
        je      outpu5                  ; e = yes
        call    scdisp                  ; echo character to the screen
                                        ;
outpu5: push    cx
outpu5a:mov     cx,10                   ; reset retry counter
outpu5b:call    chkkbd                  ; check keyboard for interruption
        test    status,stat_cc          ; control c interrupt?
        jnz     outpu6                  ; nz = yes, quit now
        call    chkport                 ; check for char at serial port
        test    status,stat_ok          ;   and put any in buffer
        jnz     outpu5a                 ; nz = have a char, look for another
        mov     ax,1                    ; wait 1 millisec between rereads
        call    pcwait
        dec     cx                      ; count down retries
        jge     outpu5b                 ; ge = keep trying
        pop     cx                      ; no more input, recover register
        jmp     outpu2                  ; resume command
outpu6: pop     cx                      ; recover register
        or      errlev,1                ; set SEND failure condition
        jmp     squit                   ; quit on control c
SCOUT   ENDP


; Raw file transfer to host (strips linefeeds)
; Transmit filespec [prompt]
; Optional prompt is the single char expected from the host to ACK each line.
; Default prompt is a linefeed (or a carriage return from us).
;
SCXMIT  PROC    NEAR
        mov     ah,cmfile               ; get a filename, asciiz
        mov     dx,offset line          ; where to store it
        mov     bx,offset xmthlp        ; help message
        call    comnd
         jmp    r                       ; exit on failure
         nop
        mov     ah,cmtxt                ; get a prompt string, asciiz
        mov     bx,offset line+80       ; where to keep it (end of "line")
        mov     byte ptr line+80,lf     ; store default prompt (line feed)
        mov     byte ptr line+81,0      ; add terminator
        mov     dx,offset pmthlp        ; Help in case user types "?".
        call    comnd
         nop                            ; ignore parse error if no prompt
         nop
         nop
        mov     ah,cmcfm                ; get a confirm
        call    comnd
         jmp r                          ; no confirm
         nop
        mov     si,offset line+80       ; convert possible numeric prompt
        cld
        call    katoi                   ; convert number to binary, if number
xmit0:  mov     tempa,al                ; save the code here
        mov     dx,offset line          ; point to filename
        mov     ah,open2                ; DOS 2 open file
        mov     al,0                    ; open for reading
        int     dos
        mov     fhandle,ax              ; store file handle here
        jnc     xmit1                   ; nc = successful opening

        mov     ah,prstr                ; give file not found error message
        mov     dx,offset xfrfnf
        int     dos
        or      errlev,1                ; set SEND failure condition
        jmp     squit                   ; exit failure

xmitx:  mov     ah,prstr                ; error during transfer
        mov     dx,offset xfrrer
        int     dos
xmitx2: mov     bx,fhandle              ; file handle
        mov     ah,close2               ; close file
        int     dos
        call    serrst                  ; reset serial port
        call    bufclear                ; clear script buffer
        call    clrbuf                  ; clear local serial port buffer
        or      errlev,1                ; set SEND failure condition
        jmp     squit                   ; exit failure
                                        ;
xmity:  mov     bx,fhandle              ; file handle
        mov     ah,close2               ; close file
        int     dos
        call    serrst                  ; reset serial port
        call    bufclear                ; clear buffers
        call    clrbuf
        jmp     rskp                    ; and return success

xmit1:  call    serini                  ; initialize serial port
        call    bufclear                ; clear script input buffer
        call    clrbuf                  ; clear serial port buffer
        mov     status,stat_unk         ; clear status flag
        mov     parmsk,0ffh             ; parity mask, assume 8 bit data
        mov     si,portval
        cmp     [si].parflg,parnon      ; parity is none?
        je      xmit1a                  ; e = none
        mov     parmsk,07fh             ; else strip parity (8th) bit
xmit1a: mov     bl,[si].ecoflg          ; Get the local echo flag.
        mov     lecho,bl                ; our copy
        mov     dx,offset crlf          ; display cr/lf
        mov     ah,prstr
        int     dos

xmit2:  mov     dx,offset line          ; buffer to read into
        mov     cx,linelen              ; # of bytes to read
        mov     ah,readf2               ; read bytes from file
        mov     bx,fhandle              ; file handle is stored here
        int     dos
        jc      xmitx                   ; c = failure
        mov     cx,ax                   ; number of bytes read
        jcxz    xmity                   ; z = none, end of file
                                        ;
        mov     si,offset line          ; buffer for file reads
xmit3:  lodsb                           ; get a byte
        push    si                      ; save position on line
        push    cx                      ; and byte count
        push    ax                      ; save char around outchr call
xmit4:  mov     retry,0                 ; clear retry counter
xmit4f: pop     ax                      ; recover saved char
        push    ax                      ; and save it again
        mov     ah,al                   ; outchr wants char in ah
        inc     retry                   ; count number of attempts
        cmp     retry,maxtry            ; too many retries?
        jle     xmit4g                  ; le = no
        or      status,stat_cc          ; simulate control-c abort
        pop     ax                      ; clean stack
        xor     al,al                   ; clear char
        jmp     xmita                   ; and abort transfer
xmit4g: cmp     al,lf                   ; line feed?
        je      xmit4h                  ; e = yes, don't send it
        call    outchr                  ; send the character to the port
         jmp     xmit4f                 ; failed, try again
         nop
xmit4h: pop     ax                      ; recover saved char
        cmp     lecho,0                 ; is local echoing active?
        je      xmit5                   ; e = no
        cmp     flags.capflg,0          ; capturing active?
        je      xmit4a                  ; e = no
        push    ax                      ; save char
        call    cptchr                  ; give it the character just sent
        pop     ax                      ; restore character and keep going
xmit4a: call    scdisp                  ; display char on screen

xmit5:  cmp     al,cr                   ; did we send a carriage return?
        je      xmit8                   ; e = yes, time to check keyboard

xmit7:  pop     cx
        pop     si
        loop    xmit3                   ; finish this buffer full
        jmp     xmit2                   ; read next buffer

xmit8:  test    status,stat_cc          ; Control-C seen?
        jnz     xmita                   ; nz = yes
        call    chkkbd                  ; check keyboard (returns char in al)
        test    status,stat_ok          ; have a char?
        jnz     xmita                   ; nz = yes
        cmp     tempa,0                 ; is prompt char a null?
        jne     xmit8b                  ; ne = no
        call    bufread                 ; check for char from serial port buf
        jnc     xmit8                   ; nc = a char, read til none
        jmp     xmit7                   ; continue transfer
xmit8b: call    bufread                 ; check for char from serial port buf
        jc      xmit8                   ; c = none
        cmp     al,tempa                ; is port char the ack?
        jne     xmit8                   ; ne = no, just ignore the char
        jmp     xmit7                   ; yes, continue transfer

xmita:  test    status,stat_cc          ; control-c?
        jnz     xmitc                   ; nz = yes
        test    status,stat_cr          ; a local ack?
        jz      xmit8                   ; no, ignore local char
        mov     dx,offset crlf          ; display cr/lf
        mov     ah,prstr
        int     dos
        jmp     xmit7                   ; continue transfer
xmitc:  pop     cx                      ; Control-C, clear stack
        pop     si                      ; ...
        mov     dx,offset xfrcan        ; say canceling transfer
        mov     ah,prstr
        int     dos
        jmp     xmitx2                  ; ctrl-c, quit

SCXMIT  ENDP

;;;;;;;;;;;;;;;;;; local support procedures ;;;;;;;;;;
;
;worker: copy line from si to di, converting \nnn strings to single chars
; returns carry set if error, else carry clear. Detects leading at-sign
; as an indicator to read command file for one line of text; command files
; may be nested to a depth of 100.
; Items of the form \chars which are not numbers are copied verbatium
; to the output string (ex: \a  is copied as \a). The string is first trimmed
; of trailing spaces, then the possible curly brace delimiter pair is
; removed, and finally \numbers are converted to binary. [jrd]
cnvlin  proc    near
        push    si                      ; source ptr
        push    di                      ; destination ptr
        push    ax
        mov     ax,ds
        mov     es,ax                   ; use data segment for es:di
        pop     ax
        mov     tempd,0                 ; count indirection depth
cnvln0: cmp     tempd,100               ; limit to 100 deep
        jbe     cnvln0a                 ; be = not too deep yet
        jmp     cnvln8                  ; too deep, quit
cnvln0a:cld
        xor     cx,cx                   ; initialize returned byte count
        lodsb                           ; get the first character
        cmp     al,40h                  ; at-sign indirection?
        je      cnvln5                  ; e = yes, open the file
        dec     si                      ; no, push back char just read
        call    cnvstr                  ; convert string's curly braces
cnvln1: xor     ah,ah                   ; clear high byte of number
        call    katoi                   ; get a char into al, convert number
        jnc     cnvln4                  ; nc = binary number converted
        cmp     al,0ffh                 ; cr/lf wild card?
        je      cnvln4                  ; e = yes, store it
        cmp     al,0                    ; end of line?
        jne     cnvln3                  ; ne = no
        jmp     cnvlnx                  ; yes, exit now
cnvln3: cmp     al,'a'                  ; candidate for conversion? [js]
        jb      cnvln4                  ; b = no
        cmp     al,'z'                  ; still in lower case set? [js]
        ja      cnvln4                  ; a = no
        and     al,incasv               ; else apply case conversion mask
cnvln4: stosb                           ; save the char
        inc     cx                      ; and count it
        cmp     ah,0                    ; was number larger than one byte?
        je      cnvln1                  ; e = no
        xchg    ah,al                   ; put high byte into al
        stosb                           ; store it too
        inc     cx                      ; count storage
        jmp     short cnvln1            ; read more

cnvln5: mov     dx,si                   ; get filename ptr from source line
        push    si
        inc     tempd                   ; count indirection depth
        mov     cx,64                   ; max length of a filename.
cnvln5a:cmp     byte ptr [si],' '       ; whitespace or control code?
        jbe     cnvln5b                 ; be = yes, found termination
        inc     si                      ; else look at next char
        loop    cnvln5a                 ; limit search
cnvln5b:mov     byte ptr [si],0         ; make asciiz
        pop     si
        mov     ah,open2                ; DOS 2 open file
        mov     al,0                    ; open for reading
        int     dos
        mov     word ptr fhandle,ax     ; store file handle
        jnc     cnvln7                  ; nc = open ok, read from file

        mov     ah,prstr
        mov     dx,offset indmis        ; file open error msg
        int     dos
        xor     cx,cx                   ; say zero bytes read
        pop     di                      ; destination ptr
        pop     si                      ; source ptr
        stc                             ; set c bit, failure
        ret

cnvln7: mov     bx,word ptr fhandle     ; file handle
        mov     cx,linelen              ; # of bytes to read
        mov     ah,ioctl                ; ioctl, is this the console device?
        mov     al,0                    ; get device info
        int     dos
        and     dl,81h                  ; ISDEV and ISCIN bits needed together
        cmp     dl,81h                  ; Console input device?
        jne     cnvln7d                 ; ne = no, use regular file i/o
        push    ds
        pop     es                      ; set es:di to datas segment
        push    di                      ; save starting pointer
cnvln7b:mov     ah,coninq               ; read console, no echo
        int     dos
        stosb
        cmp     al,cr                   ; end of the line yet?
        loopne  cnvln7b                 ; keep reading
cnvln7c:mov     byte ptr [di],0         ; insert terminator
        pop     di                      ; recover starting pointer
        mov     dx,di                   ; simulate read file read
        mov     ax,linelen
        sub     ax,cx                   ; ax = number of chars read
        jmp     cnvln7e                 ; close file, finish processing

cnvln7d:mov     dx,di                   ; destination ptr
        mov     byte ptr [di],0         ; insert null terminator, clears line
        mov     ah,readf2               ; DOS 2 read from file
        int     dos
cnvln7e:pushf                           ; save flags
        push    ax                      ; save byte count read
        mov     ah,close2               ; close file (wanted just one line)
        int     dos
        pop     ax
        popf                            ; recover flags now
        jc      cnvln8                  ; c = error
        mov     cx,ax                   ; ax = number of bytes read
        jcxz    cnvln8a                 ; cx = z = no bytes read
        mov     al,cr                   ; look for cr as terminator
        cld
        repne   scasb                   ; scan while not a cr and cx not zero
        jne     cnvln7a                 ; ne = no cr found
        dec     di                      ; point at cr
cnvln7a:mov     byte ptr [di],0         ; plant terminator on the cr
                                        ;  or after last read char, if no cr.
        pop     di                      ; get original destination ptr
        push    di                      ; and save it again
        mov     si,dx                   ; new source = this line
                                        ; go convert text, as necessary, and
        jmp     cnvln0                  ;  allow nested indirection

cnvln8: mov     ah,prstr
        mov     dx,offset inderr        ; error reading file message
        int     dos
cnvln8a:xor     cx,cx                   ; say zero bytes read
        pop     di
        pop     si
        stc                             ; set carry for failure
        ret                             ; and do a real return

cnvlnx: pop     di                      ; destination ptr
        pop     si                      ; source ptr
        clc                             ; clear c bit, success
        ret
cnvlin  endp
;
; worker: read the number of seconds to pause or timeout
;    returns timeofday for timeout in timhms, and next non-space or
;    non-tab source char ptr in si.
;
inptim  proc    near
        push    ax
        push    bx
        push    cx
        push    dx
        cld
        mov     si,offset line          ; source pointer
        mov     cx,10                   ; multiplier
        xor     bx,bx                   ; accumulated sum
        xor     ax,ax                   ; source char holder
        cmp     byte ptr [si],'9'       ; start with numeric input?
        ja      inptm3                  ; a = no, use default time
        cmp     byte ptr [si],'0'       ; ditto
        jb      inptm3
inptm1: lodsb                           ; get a byte into al
        cmp     al,'9'
        ja      inptm4                  ; non-numeric, exit loop
        cmp     al,'0'
        jb      inptm4                  ; b = non=numeric, exit loop
        xchg    ax,bx                   ; put sum into ax, char in bl
        mul     cx                      ; sum times ten
        xchg    ax,bx                   ; put char into al, sum in bx
        sub     al,'0'                  ; convert to binary
        add     bx,ax                   ; add to sum
        jmp     inptm1                  ; loop thru all chars

inptm3: mov     bx,indfto               ; no numbers, use default-timeout
        mov     si,offset line          ; reset pointer to start of line
        jmp     inptm5
inptm4: dec     si                      ; back up to non-numeric terminator
inptm4a:cmp     byte ptr [si],spc       ; space?
        je      inptm4b                 ; e = yes, skip over it
        cmp     byte ptr [si],tab       ; tab?
        je      inptm4b                 ; e = yes, skip over it
        jmp     inptm5                  ; neither
inptm4b:inc     si                      ; look at next char
        jmp     inptm4a                 ; continue scanning off white space

inptm5: push    si                      ; save ending scan position for return
        mov     timout,bx               ; # seconds of timeout desired
        mov     ah,gettim               ; read DOS tod clock
        int     dos
        mov     timhms[0],ch            ; hours
        mov     timhms[1],cl            ; minutes
        mov     timhms[2],dh            ; seconds
        mov     timhms[3],dl            ; hundredths of seconds
        mov     bx,2                    ; start with seconds field
inptm6: mov     ax,timout               ; our desired timeout interval
        add     al,timhms[bx]           ; add current tod digit to interval
        adc     ah,0
        xor     dx,dx                   ; clear high order part thereof
        mov     cx,60                   ; divide by 60
        div     cx                      ; compute number of minutes or hours
        mov     timout,ax               ; quotient
        mov     timhms[bx],dl           ; put remainder in timeout tod digit
        dec     bx                      ; look at next higher order time field
        cmp     bx,0                    ; done all time fields?
        jge     inptm6                  ; ge = no
        cmp     timhms[0],24            ; normalize hours
        jl      inptm7                  ; l = not 24 hours
        sub     timhms[0],24            ; discard part over 24 hours
inptm7: pop     si                      ; return ptr to next source char
        pop     dx
        pop     cx
        pop     bx
        pop     ax
        ret
inptim  endp

; worker: display the char in al on screen
; use caret-char notation for control codes
scdisp  proc    near
        push    dx
        push    ax
        mov     ah,conout       ; our desired function
        test    flags.remflg,d8bit ; show all 8 bits?
        jnz     scdisp0         ; nz = yes
        and     al,7fh          ; apply 7 bit display mask
scdisp0:cmp     al,0            ; null?
        je      scdis2          ; e = yes, ignore
        cmp     al,del          ; delete code?
        je      scdis2          ; e = yes, ignore
        cmp     al,spc          ; control char?
        jae     scdis1          ; ae = no, display as-is
        cmp     al,cr           ; carriage return?
        je      scdis1          ; e = yes, display as-is
        cmp     al,lf           ; line feed?
        je      scdis1
        cmp     al,tab          ; horizontal tab?
        je      scdis1
        cmp     al,bell         ; bell?
        je      scdis1
        cmp     al,bs           ; backspace?
        je      scdis1
        cmp     al,escape       ; escape?
        je      scdis1
        or      al,40h          ; convert control code to printable char
        push    ax
        mov     dl,5eh          ; display caret first
        int     dos
        pop     ax
scdis1: mov     dl,al           ; the char to be displayed
        int     dos
scdis2: pop     ax
        pop     dx
        ret
scdisp  endp

; workers
; Circular buffer for data from serial port. Written by Joe R. Doupnik
; Entry points -
;       bufread: read serial port for latest char (invokes bufwrite, sets
;                       status), get a char into al, return carry set if none.
;       bufwrite: put a char from al into buf. If this overwrites an unread
;                       character then: we lose the old char, the read pointer
;                       is moved to the next oldest unread char, and the
;                       number of chars in the buffer is decreased by one.
;       bufclear: empties the buffer.
; The buffer is prtbuf, of size prtbuflen bytes. Internally, integer bufcnt
; holds the number of buffer locations occupied, pointer bufrdptr is the
; offset of the char to be read, pointer bufwtptr is the offset of the
; place to store the next incoming char.
;
bufclear proc   near
        mov     bufcnt,0                ; clear count of bytes in buffer
        mov     bufrdptr,offset prtbuf  ; move read pointer to start of buf
        mov     bufwtptr,offset prtbuf  ; move write pointer to start of buf
        ret
bufclear endp

bufread proc    near
        call    chkport                 ; get any oldest char from port
        cmp     bufcnt,0                ; empty buffer?
        jne     bufrd1                  ; ne = no
        stc                             ; yes, set carry flag (no char)
        ret                             ; and quit (chkport sets status)
bufrd1: push    si
        mov     si,bufrdptr
        mov     al,byte ptr [si]        ; extract a char into al
        pop     si
        inc     bufrdptr                ; move pointer to next byte
        dec     bufcnt                  ; say have extracted a char
        cmp     bufrdptr,offset prtbuf+prtbuflen        ; beyond end?
        jb      bufrd2                  ; b = not yet, just return
        mov     bufrdptr,offset prtbuf  ; reset to start of buf (wrapping)
bufrd2: clc                             ; clear carry flag (have read a char)
        ret                             ; chkport sets status
bufread endp

bufwrite proc   near
        push    si
        mov     si,bufwtptr
        mov     byte ptr [si],al        ; store char held in al
        pop     si
        inc     bufwtptr                ; move pointer to next byte
        cmp     bufwtptr,offset prtbuf+prtbuflen        ; beyond end?
        jb      bufwt1                  ; b = not yet
        mov     bufwtptr,offset prtbuf  ; reset to start of buf (wrapping)
bufwt1: inc     bufcnt                  ; say have added a char to the buf
        cmp     bufcnt,prtbuflen        ; more than we can hold?
        jbe     bufwt3                  ; be = not overflowing
        push    bufwtptr                ; read ptr can't alias write ptr
        pop     bufrdptr                ; move up read pointer
        mov     bufcnt,prtbuflen        ; limit count to max buffer length
bufwt3: ret
bufwrite endp

; worker: check for timeout, return status=stat_tmo if timeout, else bit
;  stat_unk is cleared.
chktmo: and     status,not stat_tmo
        mov     ah,gettim               ; get the time of day
        int     dos
        sub     ch,timhms[0]            ; hours difference, ch = (now-timeout)
        je      chktmo2                 ; e = same, check mmss.s
        jl      chktmox                 ; l = we are early
        cmp     ch,12                   ; hours difference, large or small?
        jge     chktmox                 ; ge = not that time yet
        jl      chktmo3                 ; l = beyond that time
chktmo2:cmp     cl,timhms[1]            ; minutes, hours match
        jb      chktmox                 ; b = early
        ja      chktmo3                 ; a = late
        cmp     dh,timhms[2]            ; seconds, hhmm match
        jb      chktmox                 ; b = early
        ja      chktmo3                 ; a = late
        cmp     dl,timhms[3]            ; fractions, hhmmss match
        jb      chktmox                 ; b = early
chktmo3:or      status,stat_tmo         ; say timeout
chktmox:ret
;
; worker: check keyboard for char. Return status = stat_cc if control-C typed,
; stat_cr if carriage return, or stat_ok if any other char typed. Else return
; with these status bits cleared.
chkkbd: and     status,not (stat_ok+stat_cc+stat_cr) ; clear status bits
        cmp     flags.cxzflg,'C'        ; Control-C interrupt seen?
        je      chkkbd0                 ; e = yes
        mov     ah,dconio               ; keyboard char present?
        mov     dl,0ffH
        int     dos
        je      chkkbd1                 ; e = none
        or      status,stat_ok          ; have a char, return it in al
        cmp     al,3                    ; control c?
        jne     chkkbd1                 ; ne = not control c
chkkbd0:or      status,stat_cc          ; say control c
;;;     mov     flags.cxzflg,0          ; clear interrupt flag
chkkbd1:cmp     al,cr                   ; carriage return? [js]
        jne     chkkbd2                 ; ne = no
        or      status,stat_cr          ; say carriage return [js]
chkkbd2:ret
;
; worker: check serial port for received char. Return status = stat_ok if
;  char received, otherwise stat_ok cleared. Can echo char to screen. Will
;  write char to local circular buffer.
chkport:and     status,not stat_ok      ; clear status bit
        call    prtchr                  ; char at port (in al)?
         jmp    chkpor1                 ; yes, analyze it
         nop                            ; ensure 3 bytes for rskp of prtchr
        ret                             ; no, return
chkpor1:and     al,parmsk               ; strip parity, if any
        cmp     rxtable+256,0           ; is translation turned off?
        je      chkpor0                 ; e = yes, no translation
        push    bx                      ; translate incoming character
        mov     bx,offset rxtable       ; the translation table
        xlatb
        pop     bx
chkpor0:cmp     flags.capflg,0          ; capturing active?
        je      chkpor3                 ; e = no
        test    flags.remflg,d8bit      ; keep 8 bits for displays?
        jnz     chkpo0a                 ; nz = yes, 8 bits if possible
        cmp     flags.debug,0           ; is debug mode active?
        jne     chkpo0a                 ; ne = yes, record 8 bits
        and     al,7fh                  ; remove high bit
chkpo0a:push    ax                      ; save char
        call    cptchr                  ; give it captured character
        pop     ax                      ; restore character and keep going
chkpor3:test    flags.remflg,d8bit      ; keep 8 bits for displays?
        jnz     chkpo3a                 ; nz = yes, 8 bits if possible
        and     al,7fh                  ; remove high bit
chkpo3a:cmp     inecho,0                ; input echoing off?
        je      chkpor4                 ; e = yes
        call    scdisp                  ; display the char
chkpor4:call    bufwrite                ; put char in buffer
        or      status,stat_ok          ; say have a char (still in al)
        ret
;
; Squit is the script error exit pathway.
;
squit:  test    status,stat_tmo         ; timeout?
        jz      squit2                  ; z = no, another kind of failure
        cmp     inecho,0                ; Input echo allowed?
        je      squit1                  ; e = no, so skip timeout msg
        cmp     taklev,0                ; in a Take file?
        jne     squit1                  ; ne = yes, suppress msg
        push    dx
        mov     dx,offset tmomsg        ; say timed out
        mov     ah,prstr
        int     dos                     ; display it.
        pop     dx
squit1: cmp     inactv,0                ; action to do upon timeout
        je      squit3                  ; 0 = proceed, ne = non-zero = quit
squit2: mov     flags.cxzflg,'C'        ; simulate Control-C termination
        ret                             ; return failure, pop take level
squit3: jmp     rskp                    ; return success, ignore error

;
; Jumping to this location is like retskp. It assumes the instruction
;     after the call is a jmp addr.

RSKP    PROC    NEAR
        pop     bp
        add     bp,3
        push    bp
        ret
RSKP    ENDP

; Jumping here is the same as a ret.

R       PROC    NEAR
        ret
R       ENDP

code    ends
        end
