 	NAME	msster
; File MSSTER.ASM
	include mssdef.h
;	Copyright (C) 1982, 1997, Trustees of Columbia University in the 
;	City of New York.  The MS-DOS Kermit software may not be, in whole 
;	or in part, licensed or sold for profit as a software product itself,
;	nor may it be included in or distributed with commercial products
;	or otherwise distributed by commercial concerns to their clients 
;	or customers without written permission of the Office of Kermit 
;	Development and Distribution, Columbia University.  This copyright 
;	notice must not be removed, altered, or obscured.
;
; Edit history
; 12 Jan 1995 version 3.14
; Last edit
; 12 Jan 1995

	public	clscpt, defkey, clscpi, ploghnd, sloghnd, tloghnd
	public  dopar, shokey, cptchr, pktcpt, targ, replay, repflg
	public	kbdflg, shkadr, telnet, ttyact, write, dec2di, caplft
	public	cnvlin,  decout, valout, cnvstr
	public	pntchr, pntflsh, pntchk, prnhand, prnopen
	public	vfopen, vfread, ldiv, lmul, lgcd, atoi, atoibyte
	public  domath, domath_ptr, domath_cnt, domath_msg
	public	atoi_cnt, atoi_err, tod2secs

braceop	equ	7bh			; opening curly brace
bracecl	equ	7dh			; closing curly brace

data 	segment
	extrn	flags:byte, trans:byte, diskio:byte, portval:word
	extrn	rdbuf:byte, dosnum:word, filtst:byte, prnname:byte
	extrn	comand:byte, kstatus:word, flowon:byte, flowoff:byte
	extrn	cardet:byte
ifndef	no_terminal
	extrn	anspflg:byte
endif	; no_terminal

targ	termarg	<0,1,cptchr,0,parnon>
crlf    db      cr,lf,'$'
tmsg1	db	cr,lf,'(Connecting to host, type $' 
tmsg3	db	' C to return to PC)',cr,lf,cr,lf,cr,lf,'$'
erms21	db	cr,lf,'?Cannot start the connection.$'
erms25	db	cr,lf,'?Input must be numeric$' 
erms22	db	cr,lf,'?No open logging file$'
erms23	db	'*** Error writing session log, suspending capture ***$'
erms24	db	cr,lf,'?Error writing Packet log$'
erms26	db	' *** PRINTER IS NOT READY ***  press R to retry'
	db	' or D to discard printing:  $'
erms27	db	cr,lf
	db	'? Carrier signal not detected on this serial port.'
	db	cr,lf
	db    '  Use the DIAL command, SET CARRIER OFF, or check your modem.$'

esctl	db	'Control-$'
repflg	db	0		; REPLAY or SET TERM REPLAY filespec flag
reperr	db	cr,lf,'?File not found$'	; for REPLAY command
noterm	db	cr,lf,'Connect mode is not built into this Kermit$'
vfopbad	db	cr,lf,'?Cannot open file $'	; filename follows
vfclbad	db	cr,lf,'?Cannot close file$'
vfoptwice db	cr,lf,'?File is already open$'
vfnofile db	cr,lf,'?File is not open$'
vfrbad	db	cr,lf,'?Error while reading file$'
vfwbad	db	cr,lf,'?Error while writing file$'
vfrdbad	db	cr,lf,'?more parameters are needed$'
vfrhandle dw	-1			; READ FILE handle (-1 = invalid)
vfwhandle dw	-1			; WRITE FILE handle

opntab	db	3			; OPEN FILE table
	mkeyw	'Read',1
	mkeyw	'Write',2
	mkeyw	'Append',3

inthlp db cr,lf,' ', 22 dup ('-'),' Special keys within Connect mode '
       db	22 dup ('-')
       db cr,lf,'                           numeric keypad keys:'
       db cr,lf,'  Pg Up/Dn  roll screen vertically   Ctrl Pg Up/Dn  roll'
       db	' screen one line '
       db cr,lf,'  Home/End  roll to start/end screen Ctrl End screen dump'
       db cr,lf,'  - (minus) toggle status/mode line'
       db cr,lf,'                           other key combinations:'
       db cr,lf,'  Alt-b  send a BREAK                Alt-n  next active'
       db	' Telnet session'
       db cr,lf,'  Alt-c  start Compose sequence      Alt-x  exit emulator'
       db cr,lf,'  Alt-h  show this menu              Alt-z  network session'
       db	' hold'
       db cr,lf,'  Alt-= or Alt-r  reset emulator     Alt--  toggle terminal'
       db	' type'
       db cr,lf,'  Ctrl Prtscrn  toggle printing'
       db cr,lf,'  Additional commands are escape character '
inthlpc db '  '
       db	' followed by special characters.'
       db cr,lf,' ',78 dup ('-')
       db cr,lf,'  7n1 = SET TERM BYTE 7, SET PARITY NONE, 1 stop bit'
       db cr,lf,'  8n1 = SET TERM BYTE 8, SET PARITY NONE, 1 stop bit'
       db cr,lf,'  7e1 = SET PARITY EVEN, 1 stop bit   7s1 = SET PARITY SPACE'
       db	', 1 stop bit'
       db cr,lf,'  7o1 = SET PARITY ODD,  1 stop bit   7m1 = SET PARITY MARK'
       db	',  1 stop bit'
       db cr,lf,'  press a key to exit this menu'
       db	0

intqry db cr,lf,' ',20 dup ('-'),' Single character commands active now ' 
       db	20 dup ('-')
       db cr,lf,'  ?  Show this menu                  F  Dump screen to file'
       db cr,lf,'  C  Close the emulator              P  Push to DOS'
       db cr,lf,'  S  Status of the connection        Q  Quit logging'
       db cr,lf,'  M  Toggle mode line                R  Resume logging'
       db cr,lf,'  B  Send a Break                    0  Send a null'
       db cr,lf,'  L  Send a long 1.8 s Break         H  Hangup phone'
       db cr,lf,'  A  Send Telnet "Are You There"     I  Send Telnet' 
       db	' "Interrupt Process"' 
       db cr,lf,'  Typing the escape character '
intqryc db '  '					; where escape char glyphs go
       db	' will send it to the host'
       db cr,lf,'  press space bar to exit this menu'
       db 0

intprm	db	'Command> $'
intclet	db	'B','C','F','H','L'	; single letter commands
	db	'M','P','Q','R','S'	; must parallel dispatch table intcjmp
	db	'?','0','A','I'
numlet	equ	$ - intclet		; number of entries
	even
ifndef	no_terminal
intcjmp	dw	intchb,intchc,intchf,intchh,intchl
	dw	intchm,intchp,intchq,intchr,intchs
	dw	intchu,intchn,intayt,inttip
endif	; no_terminal

prnhand	dw	4		; printer file handle (4 = DOS default)

	even
ploghnd	dw	-1		; packet logging handle
sloghnd	dw	-1		; session logging handle
tloghnd	dw	-1		; transaction logging handle

clotab	db	6
	mkeyw	'READ-FILE',4001h
	mkeyw	'WRITE-FILE',4002h
	mkeyw	'All-logs',logpkt+logses+logtrn
	mkeyw	'Packets',logpkt
	mkeyw	'Session',logses
	mkeyw	'Transaction',logtrn

clseslog db	cr,lf,' Closing Session log$'
clpktlog db	cr,lf,' Closing Packet log$'
cltrnlog db	cr,lf,' Closing Transaction log$'

writetab db	5			; Write command log file types
	mkeyw	'FILE',4002h		; FILE
	mkeyw	'Packet',logpkt
	mkeyw	'Screen',80h		; unused value, to say screen
	mkeyw	'Session',logses
	mkeyw	'Transaction',logtrn

sttmsg	db	cr,lf,'Press space to continue ...$'
kbdflg	db	0			; non-zero means char here from Term
ttyact	db	0			; Connect mode active, if non-zero
shkadr	dw	0			; offset of replacement Show Key cmd
nbase	dw	10			; currently active number base
temp	dw	0
temp1	dw	0
tmp	db	0
tmp1	db	0
tmpstring db	6 dup (0)		; local string work buffer
pktlft	dw	cptsiz		; number free bytes left
caplft	dw	cptsiz		; number free bytes left

maxdepth equ	8			; domath, parenthesis nesting depth
listlen equ	6			; domath, outstanding numbers and ops
parendepth db	0			; domath, depth of ()'s
mathkind db	0			; domath, math operator symbol, temp
operators db	'()!~^*/%&+-|#@'	; domath, math operator list
operator_len equ $ - operators		; domath, length of the list
precedence db	6,6,5,4,3, 2,2,2,2,1, 1,1,1,1 ; domath, precedence of ops
fevalst	struc		; structure of demath work stacks, all on the stack
  savebp	dw	0		; where bp is pushed
  numcnt	dw	0		; count of values in numlist
  numlist	dw listlen dup (0,0)	; list of dword values
  opcnt		dw	0		; count of operators in oplist
  oplist	dw  listlen dup (0)	; list of pending operators
fevalst	ends
domath_ptr dw	0			; ds:offset of domath input text
domath_cnt dw	0			; count of bytes in input text
domath_msg dw	0			; non-zero to allow error msgs
matherr dw	0			; non-zero = offset of error msg
opmsg	db	'?too many math operators or too few values $'
opmsg1	db	'?math operator missing or invalid $'
opmsg2	db	'?unknown math symbol: $'
opmsg3 db	'?math expression error $'
opmsg4	db	'?parentheses nested too deeply $'
atoibyte db	0			; non-zero to say convert one char
atoi_err db	0			; atoi return error codes
atoi_cnt dw	0			; atoi, bytes supplied to/remaining
data	ends

data1	segment
rephlp	db	'name of file to playback$'
clohlp	db	cr,lf,' READ-FILE or WRITE-FILE, or the following log files:'
	db	cr,lf,' All-logs, Packets, Session, Transaction$'
msgtxt	db	'text to be written$'
vfophlp	db	'Filename$'
vfrdmsg	db	'name of variable  into which to read a line from file$'
pktbuf	db	cptsiz dup (0)	; packet logging buffer
pktbp	dw	pktbuf		; buffer pointer to next free byte
capbuf	db	cptsiz dup (0)	; session logging buffer
capbp	dw	capbuf		; buffer pointer to next free byte
prnbuf	db	cptsiz dup (0)	; printer buffer
pntptr	dw	prnbuf		; pointer to next free byte
data1	ends

code1	segment
	extrn	iseof:far, strlen:far, prtasz:far, isfile:far
	assume	cs:code1
code1	ends

code	segment
	extrn 	comnd:near, outchr:near, stat0:near, pcwait:far
	extrn	beep:near, puthlp:near, serhng:near, lnout:near
	extrn	serini:near, serrst:near, sendbr:near, putmod:near
	extrn	fpush:near,  sendbl:near, trnmod:near, dodecom:near
ifndef	no_terminal
	extrn	dumpscr:near,term:near
endif	; no_terminal
flnout	proc	far
	call	lnout
	ret
flnout	endp
	assume	cs:code, ds:data, es:nothing

; the show key command
shokey	proc	near
	cmp	shkadr,0		; keyboard translator present?
	je	shokey1			; e = no, use regular routines
	mov	bx,shkadr		; get offset of replacement routine
	jmp	bx			; and execute it rather than us
shokey1:clc
	ret
shokey	endp
; enter with ax/scan code to define, si/ pointer to definition, cx/ length
; of definition.  Defines it in definition table. Obsolete.
defkey	proc	near
	ret
defkey	endp


; This is the CONNECT command
ifdef	no_terminal
TELNET 	PROC	NEAR
	mov	ah,cmeol
	call	comnd			; get a confirm
	jnc	teln1			; nc = success
	ret
teln1:	mov	ah,prstr
	mov	dx,offset noterm
	int	dos
	clc
	ret
TELNET	ENDP
endif	; no_terminal
 
ifndef	no_terminal
TELNET 	PROC	NEAR
	mov	ah,cmeol
	call	comnd			; get a confirm
	jnc	teln1			; nc = success
	ret
teln1:	cmp	repflg,0		; REPLAY?
	jne	teln1d			; ne = yes
	call	serini			; ensure port is inited now
	jnc	teln1a			; nc = success
	test	flags.remflg,dquiet	; quiet display mode?
	jnz	teln1b			; nz = yes. Don't write to screen
	mov	dx,offset erms21	; say cannot start connection
	mov	ah,prstr
	int	dos
teln1b:	or	kstatus,ksgen		; general command failure
	ret
teln1a:	cmp	flags.comflg,'F'	; Fossil port?
	je	teln1c			; e = yes
	cmp	flags.comflg,'4'	; using real or Bios serial ports?
	ja	teln1d			; a = no
teln1c:	cmp	flags.carrier,0		; want CD on now?
	je	teln1d			; e = no
	test	cardet,80h		; CD on?
	jnz	teln1d			; nz = yes
	mov	ah,prstr		; complain on screen
	mov	dx,offset erms27
	int	dos
	stc				; fail
	ret
teln1d:	cmp	flags.vtflg,0		; emulating a terminal?
	jne	teln2			; ne= yes, no wait necessary
	mov	ah,prstr
	mov	dx,offset crlf		; output a crlf
	int	dos
	call	domsg			; reassure user
	mov	ax,2000			; two seconds
	call	pcwait			; pause
teln2:	xor	al,al			; initial flags
	mov	ttyact,1		; say telnet is active
	cmp	flags.vtflg,0		; emulating a terminal?
	je	teln3			; e = no, say mode line is to be off
	cmp	flags.modflg,0		; mode line enabled?
	jne	tel010			; ne = yes
teln3:	or	al,modoff		; no, make sure it stays off

tel010:	test	flags.debug,logses	; debug mode?
	jz	tel0			; z = no, keep going
	or	al,trnctl		; yes, show control chars
tel0:	cmp	flags.vtflg,0		; emulating a terminal?
	je	tel1			; e = no
	or	al,emheath		; say emulating some kind of terminal
tel1:	mov	bx,portval
	cmp	[bx].ecoflg,0		; echoing?
	jz	tel2			; z = no
	or	al,lclecho		; turn on local echo
tel2:	mov	targ.flgs,al		; store flags
	mov	ah,flags.comflg		; COMs port identifier
	mov	targ.prt,ah		; Port 1 or 2, etc
	mov	ah,[bx].parflg		; parity flag
	mov	targ.parity,ah
	mov	ax,[bx].baud		; baud rate identifier
	mov	targ.baudb,al
	xor	ah,ah
	test	flags.capflg,logses	; select session logging flag bit
	jz	tel3			; z = no logging
	mov	ah,capt			; set capture flag
tel3:	or	targ.flgs,ah
	jmp	short tem1

TEM:	call	serini			; init serial port
	jnc	tem1			; nc = success
	mov	ttyact,0		; say we are no longer active
	clc
	ret				; and exit Connect mode

tem1:	mov	dx,offset crlf		; give user an indication that we are
	mov	ah,prstr		; entering terminal mode
	int	dos
	mov	ax,offset targ		; point to terminal arguments
	call	term			; call the main Terminal procedure
	mov	al,kbdflg		; get the char from Term, if any
	mov	kbdflg,0		; clear	the flag
	or	al,al			; was there a char from Term?
	jnz	intch2			; nz = yes, else ask for one from kbd

intchar:call	iseof			; stdin at eof?
	jnc	intch1			; nc = not eof, get more
	mov	al,'C'			; use C when file is empty
	jmp	intchc			;  to provide an exit
intch1:	cmp	al,0			; asking for help?
	jne	intch1b			; ne = no
	mov	al,' '
	mov	ah,trans.escchr
	cmp	ah,' '			; printable now?
	jae	intch1a			; ae = yes
	mov	al,'^'
	add	ah,40h			; make control visible
intch1a:mov	word ptr intqryc,ax	; store escape char code
	mov	ax,offset intqry	; '?' get help message
	call	puthlp			; write help msg
	mov	dx,offset intprm
	mov	ah,prstr		; show prompt
	int	dos
intch1b:mov	ah,0ch			; clear Bios keyboard buffer and do
	mov	al,coninq		;  read keyboard, no echo
	int	dos			; get a char
	or	al,al			; scan code indicator?
	jnz	intch1c			; nz = no, ascii
	mov	ah,coninq		; read and discard scan code
	int	dos
	jmp	short intch1		; try again
intch1c:cmp	al,'?'			; want to see menu again?
	jne	intch2			; ne = no
	xor	al,al			; prep for menu display
	jmp	short intchar

intch2:	mov	flags.cxzflg,0		; prevent Control-C carryover
	cmp	al,' '			; space?
	je	tem			; e = yes, ignore it
	cmp	al,cr			; check ^M (cr) against plain ascii M
	je	tem			; exit on cr
	cmp	al,trans.escchr		; is it the escape char?
	jne	intch3			; ne = no
	mov	ah,al
	call	outchr
	jmp	tem			; return, we are done here
intch3:	push	es
	push	ds
	pop	es
	mov	di,offset intclet	; command letters
	mov	cx,numlet		; quantity of them
	cmp	al,' '			; control code?
	jae	intch3a			; ae = no
	or	al,40H			; convert control chars to printable
intch3a:cmp	al,96			; lower case?
	jb	intch3b			; b = no
	and	al,not (20h)		; move to upper case
intch3b:cld
	repne	scasb			; find the matching letter
	pop	es
	jne	intch4			; ne = not found, beep and get another
	dec	di			; back up to letter
	sub	di,offset intclet	; get letter number
	shl	di,1			; make it a word index
	jmp	intcjmp[di]		; dispatch to it
intch4:	call	beep			; say illegal character
	jmp	intchar

intayt:	mov	ah,255			; 'I' Telnet Are You There
	call	outchr			; send IAC (255) AYT (246)
	mov	ah,246
	call	outchr
	jmp	tem

intchb:	call	sendbr			; 'B' send a break
	jmp	tem			; And return

intchc:	mov	ttyact,0		; 'C' say we are no longer active
	clc				; and exit Connect mode
	ret

intchf:	call	dumpscr			; 'F' dump screen, use msy routine
	jmp	tem			; and return

intchh:	call	serhng			; 'H' hangup phone
	call	serrst			; turn off port
	jmp	tem

intchl:	call	sendbl			; 'L' send a long break
	jmp	tem

inttip:	mov	ah,255			; 'I' Telnet Interrrupt Process
	call	outchr			; send IAC (255) IP (244)
	mov	ah,244
	call	outchr
	jmp	tem

intchm:	cmp	flags.modflg,1		; 'M' toggle mode line, enabled?
	jne	intchma			; ne = no, leave it alone
	xor	targ.flgs,modoff	; enabled, toggle its state
intchma:jmp	tem			; and reconnect

intchp:	call	fpush			; 'P' push to DOS
	mov	dx,offset sttmsg	; say we have returned
	mov	ah,prstr
	int	dos
	jmp	short intchsb		; wait for a space

intchq:	and	targ.flgs,not capt	; 'Q' suspend session logging
	jmp	tem			; and resume

intchr:	test	flags.capflg,logses	; 'R' resume logging. Can we capture?
	jz	intchr1			; z = no
	or	targ.flgs,capt		; turn on session logging flag
intchr1:jmp	tem			; and resume

intchs:	call	stat0			; 'S' status, call stat0
	mov	dx,offset sttmsg
	mov	ah,prstr
	int	dos
intchsa:call	iseof			; is stdin at eof?
	jnc	intchsb			; nc = not eof, get more
	jmp	tem			; resume if EOF
intchsb:mov	ah,coninq		; console input, no echo
	int	dos
	cmp	al,' '			; space?
	jne	intchsa
	jmp	tem

intchu:	mov	ah,trans.escchr
	cmp	ah,' '			; printable now?
	jae	intchu1			; ae = yes
	mov	al,'^'
	add	ah,40h			; make control visible
intchu1:mov	word ptr inthlpc,ax	; store escape char code
	mov	ax,offset inthlp	; help message
	call	puthlp			; write help msg
	mov	ah,0ch			; clear Bios keyboard buffer and do
	mov	al,coninq		;  read keyboard, no echo
	int	dos			; get a char
	or	al,al			; scan code indicator?
	jnz	intchu2			; nz = no, ascii
	mov	ah,coninq		; read and discard scan code
	int	dos
intchu2:jmp	tem			; try again

intchn:	xor	ah,ah			; '0' send a null
	call	outchr
	jmp	tem
TELNET  ENDP
endif	; no_terminal
code	ends

code1	segment
	assume cs:code1
; Reassure user	about connection to the host. Tell him what escape sequence
; to use to return

DOMSG	PROC	FAR
	mov	ah,prstr
	mov	dx,offset tmsg1
	int	dos
	call	escprt
	mov	ah,prstr
	mov	dx,offset tmsg3
	int	dos
	ret
DOMSG	ENDP

; print	the escape character in readable format.  

ESCPRT	PROC	NEAR
	mov	dl,trans.escchr
	cmp	dl,' '
	jge	escpr2
	push	dx
	mov	ah,prstr
	mov	dx,offset esctl
	int	dos
	pop	dx
	add	dl,040H		; Make it printable
escpr2:	mov	ah,conout
	int	dos
	ret
ESCPRT	ENDP
code1	ends

code	segment
	assume	cs:code 

; Set parity for character in Register AL

dopar:	push	bx
	mov	bx,portval
	mov	bl,[bx].parflg		; get parity flag byte
	test	bl,PARHARDWARE		; hardware?
	jnz	parret			; nz = yes
	cmp	bl,parnon		; No parity?
	je	parret			; Just return
	and	al,07FH			; Strip parity. Same as Space parity
	cmp	bl,parspc		; Space parity?
	je	parret			; e = yes, then we are done here
	cmp	bl,parevn		; Even parity?
	jne	dopar0			; ne = no
	or	al,al
	jpe	parret			; pe = even parity now
	xor	al,080H			; Make it even parity
	jmp	short parret
dopar0:	cmp	bl,parmrk		; Mark parity?
	jne	dopar1			; ne = no
	or	al,080H			; Turn on the parity bit
	jmp	short parret
dopar1:	cmp	bl,parodd		; Odd parity?	
	or	al,al
	jpo	parret			; Already odd, leave it
	xor	al,080H			; Make it odd parity
parret:	pop	bx
	ret

; REPLAY filespec  through terminal emulator
replay	proc	near
	mov	bx,offset rdbuf		; place for filename
	mov	dx,offset rephlp	; help
	mov	repflg,0		; clear the replay active flag
	mov	ah,cmword		; get filename
	call	comnd
	jc	replay2			; c = failure
	mov	ah,cmeol		; get an EOL confirm
	call	comnd
	jc	replay2			; c = failure
ifndef	no_terminal
	mov	ah,open2		; open file
	xor	al,al			; open readonly
	cmp	byte ptr dosnum+1,2	; above DOS 2?
	jna	replay1			; na = no, so no shared access
	mov	al,0+40h		; open readonly, deny none
replay1:mov	dx,offset rdbuf		; asciiz filename
	int	dos
	jnc	replay3			; nc = success
	mov	ah,prstr
	mov	dx,offset reperr	; Cannot open that file
	int	dos
	clc
	ret
replay3:mov	diskio.handle,ax	; file handle
	mov	repflg,1		; set replay flag
	call	telnet			; enter Connect mode
	mov	bx,diskio.handle
	mov	ah,close2		; close the file
	int	dos
endif	; no_terminal
	mov	repflg,0		; clear the flag
	clc
replay2:ret
replay	endp

cptchr	proc	near			; session capture routine, char in al
	test	flags.capflg,logses	; session logging active now?
	jz	cptch1			; z = no
	push	di
	push	es
	mov	di,data1		; seg of capbp and capbuf
	mov	es,di
	cld
	mov	di,es:capbp		; buffer pointer
	stosb
	inc	es:capbp
	pop	es
	pop	di
	dec	caplft			; decrement chars remaining
	jg	cptch1			; more room, forget this part
	call	cptdmp			; dump the info
cptch1:	ret
cptchr	endp

cptdmp	proc	near			; empty the capture buffer
	push	ax
	push	bx
	push	cx
	push	dx
	mov	bx,sloghnd		; get file handle
	or	bx,bx			; is file open?
	jle	cptdm1			; le = no, skip it
	mov	cx,cptsiz		; original buffer size
	sub	cx,caplft		; minus number remaining
	jl	cptdm2			; means error
	jcxz	cptdm1			; z = nothing to do
	push	ds
	mov	dx,data1		; seg of capbuf
	mov	ds,dx
	mov	dx,offset capbuf	; the capture routine buffer
	mov	ah,write2		; write with filehandle
	int	dos			; write out the block
	pop	ds
	jc	cptdm2			; carry set means error
	cmp	ax,cx			; wrote all?
	jne	cptdm2			; no, an error
	push	es
	mov	dx,data1		; seg of capbuf and capbp
	mov	es,dx
	mov	es:capbp,offset capbuf
	pop	es
	mov	caplft,cptsiz		; init buffer ptr & chrs left
	clc
	jmp	short cptdm1
cptdm2:	and	targ.flgs,not capt	; so please stop capturing
	and	flags.capflg,not logses	; deselect session logging flag bit
	mov	dx,offset erms23	; tell user the bad news
	call	putmod			; write on mode line
	stc
cptdm1:	pop	dx
	pop	cx
	pop	bx
	pop	ax
	ret
cptdmp	endp

pktcpt	proc	near			; packet log routine, char in al
	test	flags.capflg,logpkt	; logging packets now?
	jz	pktcp1			; z = no
	push	di
	push	es
	mov	di,data1		; seg of pktbuf and pktbp
	mov	es,di
	mov	di,es:pktbp		; buffer pointer
	cld
	stosb				; store char in buffer
	inc	es:pktbp		; move pointer to next free byte
	pop	es
	pop	di
	dec	pktlft			; decrement chars remaining
	jg	pktcp1			; g = more room, forget this part
	call	pktdmp			; dump the info
pktcp1:	ret
pktcpt	endp

pktdmp	proc	near			; empty the capture buffer
	push	ax
	push	bx
	push	cx
	push	dx
	mov	bx,ploghnd		; get file handle
	or	bx,bx			; is file open?
	jle	cptdm1			; le = no, skip it
	mov	cx,cptsiz		; original buffer size
	sub	cx,pktlft		; minus number remaining
	jl	pktdm2			; l means error
	jcxz	pktdm1			; z = nothing to do
	push	ds
	mov	dx,data1		; seg of pktbuf
	mov	ds,dx
	mov	dx,offset pktbuf	; the capture routine buffer
	mov	ah,write2		; write with filehandle
	int	dos			; write out the block
	pop	ds
	jc	pktdm2			; carry set means error
	cmp	ax,cx			; wrote all?
	jne	pktdm2			; ne = no, error
	push	es
	mov	dx,data1		; seg of pktbuf
	mov	es,dx
	mov	es:pktbp,offset pktbuf
	pop	es
	mov	pktlft,cptsiz		; init buffer ptr & chrs left
	jmp	short pktdm1
pktdm2:	and	flags.capflg,not logpkt	; so please stop capturing
	mov	dx,offset erms24	; tell user the bad news
	mov	ah,prstr
	int	dos
	call	clscp4			; close the packet log
pktdm1:	pop	dx
	pop	cx
	pop	bx
	pop	ax
	ret
pktdmp	endp

; CLOSE command

clscpt	proc	near
	mov	ah,cmkey		; get kind of file to close
	mov	dx,offset clotab	; close table
	mov	bx,offset clohlp	; help
	call	comnd
       	jc	clscp2			; c = failure
	mov	temp,bx
	mov	ah,cmeol
	call	comnd
       	jc	clscp2			; c = failure
	mov	kstatus,kssuc		; success status thus far
	mov	bx,temp
	cmp	bh,40h			; READ-FILE or WRITE-FILE?
	jne	clscp0			; ne = no
	mov	ax,bx
	jmp	vfclose			; close FILE, pass kind in AX
clscp0:	cmp	bx,logpkt+logses+logtrn	; close all?
	je	clscpi			; e = yes
	cmp	bx,logpkt		; just packet?
	je	clscp4
	cmp	bx,logses		; just session?
	je	clscp6
	cmp	bx,logtrn		; just session?
	jne	clscp1			; ne = no
	jmp	clscp8
clscp1:	mov	dx,offset erms22	; say none active
	mov	ah,prstr
	int	dos
	clc
	ret
clscp2:	mov	kstatus,ksgen		; general cmd failure status
	stc
	ret
					; CLSCPI called at Kermit exit
CLSCPI:	mov	bx,portval
	mov	[bx].flowc,0		; set no flow control so no sending it
	call	pntflsh			; flush PRN buffer
	call	clscp4			; close packet log
	call	clscp6			; close session log
	call	clscp8			; close transaction log
	mov	al,2			; close WRITE FILE log
	call	vfclose
	clc				; return success
	ret

clscp4:	push	bx			; PACKET LOG
	mov	bx,ploghnd		; packet log handle
	or	bx,bx			; is it open?
	jle	clscp5			; e = no
	call	pktdmp			; dump buffer
	mov	ah,close2
	int	dos
	cmp	flags.takflg,0		; ok to echo?
	je	clscp5			; e = no
	mov	ah,prstr
	mov	dx,offset clpktlog	; tell what we are doing
	int	dos
clscp5:	mov	ploghnd,-1		; say handle is invalid
	pop	bx
	and	flags.capflg,not logpkt	; say this log is closed
	ret

clscp6:	push	bx			; SESSION LOG
	mov	bx,sloghnd		; session log handle
	or	bx,bx			; is it open?
	jle	clscp7			; e = no
	call	cptdmp			; dump buffer
	mov	ah,close2
	int	dos
	cmp	flags.takflg,0		; ok to echo?
	je	clscp7			; e = no
	mov	ah,prstr
	mov	dx,offset clseslog	; tell what we are doing
	int	dos
clscp7:	mov	sloghnd,-1		; say handle is invalid
	pop	bx
	and	flags.capflg,not logses	; say this log is closed
	ret

clscp8:	push	bx			; TRANSACTION LOG
	mov	bx,tloghnd		; transaction log handle
	or	bx,bx			; is it open?
	jle	clscp9			; e = no
	mov	ah,close2
	int	dos
	cmp	flags.takflg,0		; ok to echo?
	je	clscp9			; e = no
	mov	ah,prstr
	mov	dx,offset cltrnlog	; tell what we are doing
	int	dos
clscp9:	mov	tloghnd,-1		; say handle is invalid
	pop	bx
	and	flags.capflg,not logtrn	; say this log is closed
	ret
clscpt	endp

; Print on PRN the char in register al. On success return with C bit clear.
; On failure do procedure pntchk and return its C bit (typically C set).
; Uses buffer dumpbuf (screen dump).
pntchr	proc	near
	push	es
	push	ax
	mov	ax,data1		; segment of pntptr and prnbuf
	mov	es,ax
	cmp	es:pntptr,offset prnbuf+cptsiz ; buffer full yet?
	pop	ax
	pop	es
	jb	pntchr1			; b = no
	call	pntflsh			; flush buffer now
	jnc	pntchr1			; nc = success
	ret				; c = fail, discard char
pntchr1:push	es
	push	bx
	mov	bx,data1		; segment of pntptr and prnbuf
	mov	es,bx
	mov	bx,es:pntptr		; pointer to next open slot
	mov	es:[bx],al		; store the character
	inc	bx			; update pointer
	mov	es:pntptr,bx		; save pointer
	pop	bx
	pop	es
	clc				; clear carry bit
	ret
pntchr	endp

; Flush printer buffer. Return carry clear if success.
; On failure do procedure pntchk and return its C bit (typically C set).
; Uses buffer dumpbuf (screen dump).
pntflsh	proc	near
	push	es
	push	ax
	mov	ax,data1		; segment of pntptr and prnbuf
	mov	es,ax
	cmp	es:pntptr,offset prnbuf	; any text in buffer?
	pop	ax
	pop	es
	ja	pntfls1			; a = yes
	clc
	ret				; else nothing to do
pntfls1:cmp	prnhand,0		; is printer handle valid?
	jg	pntfls2			; g = yes
	push	es
	push	ax
	mov	ax,data1		; segment of pntptr and prnbuf
	mov	es,ax
	mov	es:pntptr,offset prnbuf
	pop	ax
	pop	es
	clc				; omit printing, quietly
	ret
pntfls2:push	ax
	push	bx
	push	cx
	push	dx
	push	si
	push	di
	push	es
	mov	ah,flowoff		; get flow control char
	or	ah,ah			; flow control active?
	jz	pntfls3			; z = no, not using xoff
	call	outchr			; output xoff (ah), no echo
pntfls3:mov	bx,prnhand		; file handle for DOS printer PRN
	push	DS			; about to change DS
	mov	dx,data1		; segment of prnbuf
	mov	ds,dx			; set DS
	mov	dx,offset prnbuf	; start of buffer
	mov	cx,ds:pntptr
	sub	cx,dx			; cx = current byte count
pntfls4:push	cx
	push	dx
	mov	cx,1
	mov	ah,write2
	int	dos			; write buffer to printer
	pop	dx
	pop	cx
	jc	pntfls5			; c = call failed
	cmp	ax,1			; did we write it?
	jne	pntfls5			; ne = no, dos critical error
	inc	dx			; point to next char
	loop	pntfls4
	mov	ds:pntptr,offset prnbuf	; reset buffer pointer
	pop	DS			; restore DS
	clc				; declare success
	jmp	pntfls11
pntfls5:mov	si,dx			; address of next char to be printed
	mov	di,offset prnbuf	; start of buffer
	sub	dx,di			; dx now = number successful prints
	mov	cx,ds:pntptr
	sub	cx,si			; count of chars to be printed
	jle	pntfls6
	mov	ax,ds
	mov	es,ax
	cld
	rep	movsb			; copy unwritten to start of buffer
pntfls6:sub	ds:pntptr,dx		; move back printer pointer by ok's
	pop	DS			; restore DS

pntfls7:mov	dx,offset erms26	; printer not ready, get user action
	call	putmod			; write new mode line
	call	beep			; make a noise
	mov	ah,0ch			; clear DOS typeahead buffer
	mov	al,1			; read from DOS buffer
	int	dos
	or	al,al			; Special key?
	jnz	pntfls8			; nz = no, consume
	mov	al,1			; consume scan code
	int	dos
	jmp	short pntfls7		; try again

pntfls8:and	al,not 20h		; lower to upper case quicky
	cmp	al,'R'			; Retry?
	jne	pntfls8a		; ne = no
	call	trnmod			; toggle mode line
	call	trnmod			; back to same state as before
	jmp	pntfls3			; go retry
pntfls8a:cmp	al,'D'			; Discard printing?
	jne	pntfls7			; ne = no, try again
	mov	bx,prnhand
	cmp	bx,4			; stdin/stdout/stderr/stdaux/stdprn?
	jbe	pntfls9			; be = yes, always available
	mov	ah,close2		; close this file
	int	dos
pntfls9:mov	bx,offset prnname	; name of printer file
	mov	word ptr [bx],'UN'	; set to NUL<0>
	mov	word ptr [bx+2],'L'+0
	push	es
	mov	ax,data1		; seg for pntptr
	mov	es,ax
	mov	es:pntptr,offset prnbuf	; reset pointer
	pop	es
	mov	prnhand,-1		; declare handle invalid
ifndef	no_terminal
	mov	anspflg,0		; mszibm/msyibm print status flag off
endif	; no_terminal
pntfls10:call	trnmod			; toggle mode line
	call	trnmod			; back to same state as before
	stc				; declare failure
pntfls11:pushf
	mov	ah,flowon
	or	ah,ah			; flow control active?
	jz	pntfls12		; z = no, not using xon
	call	outchr			; output xon (al), no echo
pntfls12:popf
	pop	es
	pop	di
	pop	si
	pop	dx
	pop	cx
	pop	bx
	pop	ax
	ret				; nc = success
pntflsh	endp

; Check for PRN (DOS's printer) being ready. If ready, return with C clear
; Otherwise, write Not Ready msg on mode line and return with C bit set.
; N.B. DOS Critical Error will occur here if PRN is not ready.  [jrd]
pntchk	proc	near
	push	dx
	push	cx
	push	ax
	mov	cx,10			; ten retries before declaring error
	cmp	prnhand,0		; printer handle valid?
	jle	pntchk2			; le = no, invalid
pntchk0:push	bx
	mov	bx,prnhand		; file handle
	mov	ah,ioctl		; get printer status, via DOS
	mov	al,7			; status for output
	int	dos
	pop	bx
	jc	pntchk1			; c = call failed
	cmp	al,0ffh			; code for Ready?
	je	pntchk3			; e = yes, assume printer is ready
pntchk1:push	cx			; save counter, just in case
	mov	ax,100			; wait 100 millisec
	call	pcwait
	pop	cx
	loop	pntchk0			; and try a few more times
					; get here when printer is not ready
pntchk2:pop	ax
	pop	cx
	pop	dx
	stc				; say printer not ready
	ret
pntchk3:pop	ax
	pop	cx
	pop	dx
	clc				; say printer is ready
	ret
pntchk	endp


prnopen	proc	near
	push	ax
	push	bx
	push	cx
	push	dx
	mov	prnhand,4		; preset default handle
	mov	dx,offset prnname	; name of disk file, from msssho
	mov	ax,dx			; where isfile wants name ptr
	call	isfile			; what kind of file is this?
	jc	prnop3			; c = no such file, create it
	test	byte ptr filtst.dta+21,1fh ; file attributes, ok to write?
	jnz	prnop2			; nz = no
	mov	al,1			; writing
	mov	ah,open2		; open existing file
	int	dos
	jc	prnop2			; c = failure
	mov	prnhand,ax		; save file handle
	mov	bx,ax			; handle for DOS
	mov	ah,ioctl
	mov	al,0			; get info
	int	dos
	or	dl,20h			; turn on binary mode
	xor	dh,dh
	mov	ah,ioctl
	mov	al,1			; set info
	int	dos
	mov	cx,0ffffh		; setup file pointer
	mov	dx,-1			; and offset
	mov	al,2			; move to eof minus one byte
	mov	ah,lseek		; seek the end
	int	dos
	pop	dx
	pop	cx
	pop	bx
	pop	ax
	clc
	ret
prnop2:	pop	dx
	pop	cx
	pop	bx
	pop	ax
	stc
	ret
prnop3:	test	filtst.fstat,80h	; access problem?
	jnz	prnop2			; nz = yes
	mov	ah,creat2		; file did not exist
	mov	cx,20h			; attributes, archive bit
	int	dos
	mov	prnhand,ax		; save file handle
	pop	dx
	pop	cx
	pop	bx
	pop	ax
	ret				; may have carry set
prnopen	endp

; worker: copy line from si to di, first removing trailing spaces, second
; parsing out curly braced strings, then third converting \{b##} in strings
; to binary numbers. Returns carry set if error; else carry clear, with byte
; count in cx. Braces are optional but must occur in pairs.
; Items which cannot be converted to legal numbers are copied verbatium
; to the output string (ex: \{c}  is copied as \{c}  but \{x0d} is hex 0dh).
; '\\' is written as '\'
; Requires byte count in CX to handle embedded \0 bytes.
cnvlin	proc	near
	push	ax
	push	si			; source ptr
	push	di			; destination ptr
	push	bp			; end of string marker
	push	temp			; output length counter
	push	es			; end of save regs
	push	ds			; move ds into es
	pop	es			; use data segment for es:di
	mov	temp,di			; initial output position
	call	cnvstr			; parse curly braces
	xor	dx,dx			; initialize returned byte count
	cld
	mov	bp,si
	add	bp,cx			; point to last byte + 1 of string
cnvln1:	cmp	si,bp			; at end of string?
	jae	cnvlnx			; ae = yes, exit
	mov	al,[si]			; look at starting byte
	cmp	al,'\'			; numeric intro?
	jne	cnvln3			; ne = no
	mov	atoi_cnt,cx		; length for atoi
	mov	atoibyte,1		; convert only one character
	call	atoi			; deal with \numbers
	jnc	cnvln4			; nc = success, converted number
cnvln3:	lodsb				; reread from string literally
	xor	ah,ah			; only one byte of output
cnvln4:	cld
	stosb				; save the char
	or	ah,ah			; is returned number > 255?
	jz	cnvln1			; z = no, do more chars
cnvln5:	stosb				; save high order byte next
	jmp	short cnvln1		; do more chars
cnvlnx:	mov	byte ptr [di],0		; plant terminator
	clc				; clear c bit, success
	sub	di,temp			; current - inital output position
	mov	cx,di			; returned byte count to CX
	pop	es			; restore regs
	pop	temp
	pop	bp
	pop	di			; destination ptr
	pop	si			; source ptr
	pop	ax
	ret
cnvlin	endp

; Convert string by removing surrounding curly brace delimiter pair. 
; Converts text in place.
; Enter with source ptr in si.
; Preserves all registers, uses byte tmp. 9 Oct 1987 [jrd]
; 
cnvstr	proc	near
	push	ax
	push	bx
	push	dx
	push	si			; save start of source string
	push	di
	push	es
	xor	bx,bx 			; output count, assume none
	mov	dx,si			; source address
	jcxz	cnvst4			; z = nothing there
	push	ds
	pop	es			; set es to data segment
	cld
	mov	di,si			; set destination address to source
					; 2. Parse off curly brace delimiters
	mov	bx,cx			; assume no changes to byte count
	cmp	byte ptr [si],braceop	; opening brace?
	jne	cnvst4			; ne = no, ignore brace-matching code
	xor	bx,bx			; count up output bytes
	inc	si			; skip opening brace
	mov	dl,braceop		; opening brace (we count them up)
	mov	dh,bracecl		; closing brace (we count them down)
	mov	tmp,1			; we are at brace level 1
cnvst1:	cld				; search forward
	lodsb				; read a string char
	stosb				; store char (skips opening brace)
	dec	cx
	inc	bx			; count bytes read and written
	or	cx,cx			; at end of string?
	jle	cnvst4			; le = yes, we are done
	cmp	al,dl			; an opening brace?
	jne	cnvst2			; ne = no
	inc	tmp			; yes, increment brace level
	jmp	short cnvst1		;  and continue scanning

cnvst2:	cmp	al,dh			; closing brace?
	jne	cnvst1			; ne = no, continue scanning
	dec	tmp			; yes, decrement brace level
	or	cx,cx			; have we just read the last char?
	jnz	cnvst3			; nz = no, continue scanning
	mov	tmp,0			; yes, this is the closing brace
cnvst3:	cmp	tmp,0			; at level 0?
	jne	cnvst1			; ne = no, #opening > #closing braces
	dec	bx			; don't count terminating brace
	mov	byte ptr [di-1],0	; plant terminator on closing brace

cnvst4:	pop	es			; recover original registers
	pop	di
	pop	si
	pop	dx
	mov	cx,bx			; return count in cx
	pop	bx
	pop	ax
	ret
cnvstr	endp

; OPEN { READ | WRITE | APPEND } filespec
vfopen	proc	near
	mov	ah,cmkey		; get READ/WRITE/APPEND keyword
	mov	dx,offset opntab	; keyword table
	xor	bx,bx			; help
	call	comnd
	jc	vfopen1			; c = failed
	mov	temp,bx
	mov	ah,cmword		; read filespec
	mov	bx,offset rdbuf		; buffer for filename
	mov	dx,offset vfophlp	; help
	call	comnd
	jc	vfopen1			; c = failed
	mov	ah,cmeol		; get end of line confirmation
	call	comnd
	jnc	vfopen2
	mov	kstatus,ksgen		; general cmd failure status
vfopen1:ret				; error return, carry set

vfopen2:mov	kstatus,kssuc		; assume success status
	mov	dx,offset rdbuf		; filename, asiiz
	mov	bx,temp			; kind of open
	cmp	bx,1			; open for reading?
	jne	vfopen4			; ne = no
					; OPEN READ
	cmp	vfrhandle,0		; is it open now?
	jge	vfopen8			; ge = yes, complain
	mov	ah,open2		; file open
	xor	al,al			; 0 = open readonly
	cmp	dosnum,300h		; at or above DOS 3?
	jb	vfopen3			; b = no, so no shared access
	or	al,40h			; open readonly, deny none
vfopen3:int	dos
	jc	vfopen9			; c = failed to open the file
	mov	vfrhandle,ax		; save file handle
	clc
	ret
					; OPEN WRITE or APPEND
vfopen4:cmp	vfwhandle,0		; is it open now?
	jge	vfopen8			; ge = yes, complain
	mov	ax,dx			; filename for isfile
	call	isfile		; check for read-only/system/vol-label/dir
	jc	vfopen7			; c = file does not exist
	test	byte ptr filtst.dta+21,1fh	; the no-no file attributes
	jnz	vfopen9			; nz = do not write over one of these
vfopen5:test	filtst.fstat,80h	; access problem?
	jnz	vfopen9			; nz = yes, quit here
	test	byte ptr filtst.dta+21,1bh	; r/o, hidden, volume label?
	jnz	vfopen9			; we won't touch these
	mov	ah,open2	       ; open existing file (usually a device)
	mov	al,1+1			; open for writing
	int	dos
	jc	vfopen9			; carry set means can't open
	mov	vfwhandle,ax		; remember file handle
	mov	bx,ax			; file handle for lseek
	xor	cx,cx
	xor	dx,dx			; cx:dx = displacment
	xor	al,al			; 0 means from start of file
	cmp	temp,2			; WRITE? means from start of file
	je	vfopen6			; e = yes, else APPEND
	mov	al,2			; move to eof
vfopen6:mov	ah,lseek		; seek the place
	int	dos
	clc
	ret

vfopen7:cmp	temp,1			; READ?
	je	vfopen9			; e = yes, should not be here
	mov	ah,creat2		; create file
	xor	cx,cx			; 0 = attributes bits
	int	dos
	jc	vfopen9			; c = failed
	mov	vfwhandle,ax		; save file handle
	clc				; carry clear for success
	ret

vfopen8:mov	dx,offset vfoptwice	; trying to reopen a file
	mov	ah,prstr
	int	dos
	mov	kstatus,kstake		; Take file failure status
	stc
	ret
vfopen9:mov	dx,offset vfopbad	; can't open, complain
	mov	ah,prstr
	int	dos
	mov	dx,offset rdbuf		; filename
	call	prtasz
	mov	kstatus,kstake		; Take file failure status
	stc
	ret
vfopen	endp

; CLOSE {READ-FILE | WRITE-FILE}
vfclose	proc	near
	mov	tmp,al			; remember kind (1=READ, 2=WRITE)
	mov	bx,vfrhandle		; READ FILE handle
	cmp	al,1			; READ-FILE?
	je	vfclos1			; e = yes
	cmp	al,2			; WRITE-FILE?
	jne	vfclos3			; ne = no, a mistake
	mov	bx,vfwhandle		; write handle
	or	bx,bx
	jl	vfclos3			; l = invalid handle
	mov	dx,offset rdbuf
	xor	cx,cx			; zero length
	push	bx			; save handle
	mov	ah,write2		; set file high water mark
	int	dos
	pop	bx
vfclos1:or	bx,bx
	jl	vfclos3			; l = invalid handle
	mov	kstatus,kssuc		; success status thus far
	mov	ah,close2		; close file
	int	dos
	jc	vfclos4			; c = error
	cmp	tmp,1			; READ?
	jne	vfclos2			; ne = no, WRITE
	mov	vfrhandle,-1		; declare READ handle to be invalid
	clc
	ret
vfclos2:mov	vfwhandle,-1		; declare WRITE handle to be invalid
	clc
	ret
vfclos4:mov	ah,prstr
	mov	dx,offset vfclbad	; complain
	int	dos
vfclos3:mov	kstatus,ksgen		; general cmd failure status
	stc
	ret
vfclose	endp

; READ-FILE variable name
vfread	proc	near
	mov	comand.cmper,1		; do not react to '\%' in macro name
	mov	comand.cmarray,1	; allow sub in [..] of \&<char> arrays
	mov	ah,cmword
	mov	bx,offset rdbuf+2	; buffer for macro name
	mov	word ptr rdbuf,0
	mov	dx,offset vfrdmsg
	call	comnd			; get macro name
	jnc	vfread1			; nc = success
	mov	kstatus,ksgen		; general cmd failure status
	ret				; failure
vfread1:or	ax,ax			; null entry?
	jnz	vfread2			; nz = no
	mov	dx,offset vfrdbad	; more parameters needed
	mov	ah,prstr
	int	dos
	mov	kstatus,ksgen		; general cmd failure status
	stc
	ret

vfread2:push	ax
	mov	ah,cmeol		; get command confirmation
	call	comnd
	pop	ax			; ax is variable length
	jc	vfreadx			; c = failed
	mov	cx,cmdblen		; length of rdbuf
	sub	cx,ax			; minus macro name
	dec	cx			; minus space separator
	mov	temp,0			; leading whitespace and comment flgs
	mov	di,offset rdbuf+2	; destination buffer
	add	di,ax			; plus variable name
	mov	byte ptr [di],' '	; space separator
	inc	di			; put definition here
	mov	bx,vfrhandle		; READ FILE handle
	or	bx,bx			; check for valid handle
	jge	vfread3			; ge = valid
	mov	ah,prstr
	mov	dx,offset vfnofile	; say no file
	int	dos
vfreadx:mov	kstatus,ksgen		; general cmd failure status
	stc				; failure return
	ret
vfread3:push	cx			; read from file
	mov	kstatus,kssuc		; assume success status
	mov	cx,1			; read 1 char
	mov	dx,di			; place here
	mov	byte ptr [di],0		; insert terminator
	mov	ah,readf2
	int	dos
	pop	cx
	jc	vfreadx			; c = read failure
	or	ax,ax			; count of bytes read
	jz	vfread9			; z means end of file
	mov	al,[di]			; get the character
	cmp	flags.takflg,0		; echo Take files?
	je	vfread3a		; e = no
	push	ax
	mov	ah,conout
	mov	dl,al			; echo character
	int	dos
	pop	ax
vfread3a:cmp	al,CR			; CR?
	je	vfread7			; e = yes, ignore it
	cmp	al,LF			; LF?
	je	vfread8			; e = yes, exit
	cmp	byte ptr temp,0		; seen non-spacing char yet?
	jne	vfread6			; ne = yes
	cmp	al,' '			; is this a space?
	je	vfread7			; e = yes, skip it
	cmp	al,TAB			; or a tab?
	je	vfread7			; e = yes, skip it
	mov	byte ptr temp,1		; say have seen non-spacing char

vfread6:inc	di			; next storage cell
vfread7:loop	vfread3			; loop til end of line

vfread8:cmp	byte ptr [di-1],'-'	; last printable is line-continuation?
	jne	vfread10		; ne = no, use this line as-is
	dec	di			; point to hyphen
	mov	cx,offset rdbuf+cmdblen	; end of rdbuf
	sub	cx,di			; minus where we are now
	jle	vfread10		; le = no space remaining
	mov	temp,0			; leading whitespace and comment flgs
	jmp	vfread3			; read another line

vfread9:mov	kstatus,ksgen		; EOF, general command failure status

vfread10:cmp	flags.takflg,0		; echo Take files?
	je	vfread11		; e = no
	mov	ah,prstr
	mov	dx,offset crlf		; for last displayed line
	int	dos

vfread11:mov	byte ptr [di],0		; insert final terminator
	mov	dx,offset rdbuf+2	; start counting from here
	push	kstatus			; preserve status from above work
	call	strlen			; cx becomes length
	call	dodecom			; create the variable
	pop	kstatus			; recover reading status
	jc	vfread12		; c = failure
	ret
vfread12:mov	kstatus,ksgen		; general command failure status
	mov	ah,prstr
	mov	dx,offset vfrbad	; say error while reading
	int	dos
	stc
	ret
vfread	endp

; WRITE {FILE or log} text
Write	proc	near
	mov	ah,cmkey		; get kind of log file
	mov	dx,offset writetab	; table of possibilities
	xor	bx,bx			; help, when we get there
	call	comnd
	jnc	write1			; nc = success
	mov	kstatus,ksgen		; general cmd failure status
	ret
write1:	mov	temp,bx			; save log file kind
	mov	comand.cmdonum,1	; \number conversion allowed
	mov	comand.cmblen,cmdblen	; use full length
	mov	ah,cmline		; get string to be written
	mov	bx,offset rdbuf		; where to put text
	mov	dx,offset msgtxt	; help
	call	comnd
	jnc	wpkt0
	mov	kstatus,ksgen		; general cmd failure status
	ret				; c = failure
wpkt0:	mov	kstatus,kssuc		; success status thus far
	mov	si,offset rdbuf		; start of text in buffer
	mov	di,si			; convert in-place, to asciiz
	mov	cx,ax			; length for cnvlin
	call	cnvlin
	mov	bx,temp			; log file kind
	jcxz	wpkt2			; z = no chars to write
	mov	si,offset rdbuf
	cmp	bx,logpkt		; Packet log?
	jne	wses1			; ne = no
wpkt1:	lodsb				; get byte to al
	call	pktcpt			; write to packet log
	loop	wpkt1
wpkt2:	clc				; say success
	ret

wses1:	cmp	bx,logses		; Session log?
	jne	wtrn1			; ne = no
wses2:	lodsb				; get byte to al
	call	cptchr			; write to log
	loop	wses2			; do cx chars
	clc				; say success
	ret

wtrn1:	cmp	bx,logtrn		; Transaction log?
	jne	wtrn2			; ne = no
	mov	bx,tloghnd		; file handle
	jmp	short wtrn4

wtrn2:	cmp	bx,80h			; WRITE SCREEN?
	jne	wtrn3			; ne = no
	xor	bx,bx			; handle is stdout
	jmp	short wtrn4

wtrn3:	cmp	bx,4002h		; WRITE FILE?
	jne	wtrn5			; ne = no, forget this
	mov	bx,vfwhandle		; handle

wtrn4:	or	bx,bx			; is handle valid?
	jl	wtrn5			; l = no
	mov	dx,offset rdbuf
	mov	ah,write2		; write to file handle in bx
	int	dos
	jc	wtrn4a			; c = failure
	ret
wtrn4a:	mov	ah,prstr
	mov	dx,offset vfwbad	; say error while writing
	int	dos
	mov	kstatus,ksgen
	stc
	ret
wtrn5:	mov	kstatus,ksgen		; general cmd failure status
	mov	ah,prstr
	mov	dx,offset vfnofile	; say no file
	int	dos
	stc
	ret
write	endp
code	ends

code1	segment
	assume	cs:code1

; Convert ascii strings of the form "\{bnnn}" to a binary dword in dx:ax.
; The braces are optional but must occur in pairs. Numeric base indicator "b"
; is O or o or X or x or D or d or missing, for octal, hex, or decimal (def).
; Enter with si pointing at "\".
; Returns binary value in ax with carry clear and si to right of "}" or at
; terminating non-numeric char if successful; otherwise, a failure,
; return carry set with si = entry value and first read char in al.
; Public byte  atoibyte  is used to control conversion of one character, if
;  non-zero, or as many as can be parsed if zero. This routine always clears
;  atoibyte upon exiting.
; atoi_err byte values, carry bit state, meaning
; 0 nc	successfully converted, can accept more data
; 1 nc	successfully converted and string is terminated
; 2 nc	successfully converted with break char pointed to by SI
; 4 c	insufficient bytes to resolve
; 8 c	syntax error in expression, cannot convert
atoi	proc	far
	push	temp
	push	temp1
	push	cx			; save working reg
	push	si			; save entry si
	push	bx
	push	ax			; save read char
	xor	ax,ax
	mov	temp,ax
	mov	temp1,ax
	mov	atoi_err,al		; assume success and can read more
	xor	bx,bx			; assume no opening brace
	cld
	mov	cx,atoi_cnt		; bytes available to examine
	or	cx,cx
	jnz	atoi1
	mov	atoi_cnt,16		; safety
atoi1:	call	atoi_read		; read a byte or fail
	jnc	atoi1a
	jmp	atoix			; fail

atoi1a:	cmp	al,' '			; leading space?
	je	atoi1			; e = yes, read again, for do_math
	cmp	al,'\'			; number introducer?
	jne	atoi1b			; ne = no
	call	atoi_read		; get next byte
	jnc	atoi1b
	jmp	atoix

atoi1b:	cmp	al,braceop		; opening brace?
	jne	atoi1c			; ne = no
	mov	bl,bracecl		; remember a closing brace is needed
	call	atoi_read		; get next byte
	jnc	atoi1c
	jmp	atoix

atoi1c:	call	isnumbase		; number base introducer?
	jc	atoi1d			; c = no
	call	atoi_read		; get next byte
	jnc	atoi2			; nc = got a byte
	jmp	atoix
atoi1d:	mov	nbase,10		; assume base 10
	mov	cx,3			; and three digits per char

atoi2:	call	cnvdig			; do we have a digit now?
	jnc	atoi2a			; nc = yes
	mov	atoi_err,8		; syntax error, digit required
	jmp	atoix
atoi2a:	dec	si			; back over it for read below
	inc	atoi_cnt
	mov	ax,cx			; number base byte count
	mov	cx,atoi_cnt		; available bytes
	cmp	atoibyte,0		; read as many bytes as possible?
	je	atoi3			; e = yes
	cmp	ax,cx			; number base wants more than we have?
	ja	atoi3			; a = yes, use available bytes
	mov	cx,ax			; limit loop to num base requirements
	mov	atoi_err,1		; presume successful and terminated

atoi3:	call	atoi_read		; get a byte
	jc	atoix			; c = failed
	call	cnvdig			; convert ascii to binary digit
	jc	atoi4			; c = cannot convert, SI-1 is break
	xor	ah,ah			; clear high order value
	cmp	atoibyte,0		; read as many bytes as possible?
	je	atoi3b			; e = yes
	push	ax			; test for fitting into byte result
	mov	ax,temp			; current low order value
	mul	nbase			; times number base
	add	dx,ax			; to dx, plus any overflow
	pop	ax
	add	dx,ax			; next result would be in dx
	or	dh,dh			; overflow beyond one byte
	jnz	atoi4			; nz = cannot accept this byte
	cmp	cx,1			; reading last byte?
	ja	atoi3b			; a = no
	
atoi3b:	inc	bh			; say we did a successful conversion
	push	ax			; save this byte's value
	mov	ax,temp1
	mul	nbase			; high order
	mov	temp1,ax		; keep low order part of product
	mov	ax,temp
	mul	nbase			; low order
	add	temp1,dx		; high order carry
	mov	temp,ax			; low order
	pop	ax			; recover binary digit
	add	temp,ax
	adc	temp1,0
	loop	atoi3			; get more
	jmp	short atoi4a		; out of data, don't backup
					; here on break char in al

atoi4:	dec	si			; backup to reread terminator
	inc	atoi_cnt
	mov	atoi_err,2		; terminated on break char
atoi4a:	or	bl,bl			; closing brace needed?
	jz	atoi6			; z = no, success so far
atoi5:	call	atoi_read		; get a byte for brace
	jnc	atoi5a
	cmp	atoi_cnt,0		; out of counts?
	je	atoix			; e = yes
atoi5a:	cmp	al,bl			; the closing brace?
	jne	atoi5b			; ne = no
	mov	atoi_err,1		; success, terminator seen (brace)
	jmp	atoi6
atoi5b:	cmp	al,' '			; space padding?
	je	atoi5			; e = yes, skip it
atoi5c:	mov	atoi_err,8		; syntax error (failed to get brace)
	jmp	atoix

atoi6:	or	bh,bh			; did we do any conversion?
	jz	atoix			; z = no
	pop	ax			; throw away old saved ax
	pop	bx			; restore bx
	pop	ax			; throw away starting si, keep current
	pop	cx			; restore old cx
	mov	dx,temp1
	mov	ax,temp
	pop	temp1
	pop	temp
	mov	atoibyte,0
	mov	atoi_cnt,0
	clc				; clear carry for success
	ret
atoix:	pop	ax			; restore first read al
	pop	bx
	pop	si			; restore start value
	pop	cx			; restore old cx
	pop	temp1
	pop	temp
	xor	dx,dx
	mov	atoibyte,0
	mov	atoi_cnt,0
	stc				; set carry for failure
	ret
atoi	endp

; Examine char in AL for being a number base indicator, O, X, D, in
; either case.
; If matched return carry clear, number base nbase set, and cx holding
; the qty of bytes in a single character result value.
; If not matched return carry set.
; AL is preserved.
isnumbase proc	near
	push	ax			; try for number bases
	cmp	al,'a'			; lower case?
	jb	isnum1			; b = no
	cmp	al,'z'			; in range of lower case?
	ja	isnum1			; a = no
	and	al,5fh			; map to upper case
isnum1:	cmp	al,'O'			; octal?
	jne	isnum2			; ne = no
	mov	nbase,8			; set number base
	cmp	atoibyte,0		; all bytes?
	je	isnum4			; e = yes
	mov	cx,3			; three octal chars per final char
	jmp	short isnum4
isnum2:	cmp	al,'X'			; hex?
	jne	isnum3			; ne = no
	mov	nbase,16
	cmp	atoibyte,0		; all bytes?
	je	isnum4			; e = yes
	mov	cx,2			; two hex chars per final char
	jmp	short isnum4
isnum3:	cmp	al,'D'			; decimal?
	jne	isnum5			; ne = no, syntax error
	mov	nbase,10
	cmp	atoibyte,0		; all bytes?
	je	isnum4			; e = yes
	mov	cx,3
isnum4:	pop	ax			; successful result
	clc
	ret
isnum5:	pop	ax
	stc				; carry set for failure
	ret
isnumbase endp

; Read a byte from ds:si.
; If success decrement byte atoi_cnt, inc si, return byte in AL, carry clear.
; If failure return carry set, no change to atoi_cnt, AL unchanged.
atoi_read proc	near
	cmp	atoi_cnt,0		; any bytes left?
	jg	atoi_read1		; g = yes
	mov	atoi_err,4		; c = insufficient bytes to resolve
	stc				; return failure
	ret
atoi_read1:
	cld
	lodsb				; get first char
	dec	atoi_cnt		; one less byte availble
	clc
	ret
atoi_read endp

					; worker for atoi
cnvdig	proc	near			; convert ascii code in al to binary
	push	ax			; return carry set if cannot
	cmp	al,'a'			; lower case hexadecimal candidate?
	jb	cnvdig1			; b = no
	sub	al,20h			; lower case hex to upper
cnvdig1:cmp	al,'A'			; uppercase hex
	jb	cnvdig2
	sub	al,'A'-10-'0'		; 'A' becomes '10'
cnvdig2:sub	al,'0'			; remove ASCII bias
	jc	cnvdigx			; c = out of range
	cmp	al,byte ptr nbase	; out of range?
	jae	cnvdigx			; ae = yes, out of range
	add	sp,2			; pop saved ax
	clc				; success, binary value is in al
	ret
cnvdigx:pop	ax			; return ax unchanged
	stc				; c set for failure
	ret
cnvdig	endp	

decout	proc	far		; display decimal number in ax
	push	ax
	push	cx
	push	dx
	mov	cx,10		; set the numeric base
	call	mvalout		; convert and output value
	pop	dx
	pop	cx
	pop	ax
	ret
decout	endp

mvalout	proc	near		; output number in ax using base in cx
				; corrupts ax and dx
	xor	dx,dx		; clear high word of numerator
	div	cx		; (ax / cx), remainder = dx, quotient = ax
	push	dx		; save remainder for outputting later
	or	ax,ax		; any quotient left?
	jz	mvalout1	; z = no
	call	mvalout		; yes, recurse
mvalout1:pop	dx		; get remainder
	add	dl,'0'		; make digit printable
	cmp	dl,'9'		; above 9?
	jbe	mvalout2	; be = no
	add	dl,'A'-1-'9'	; use 'A'--'F' for values above 9
mvalout2:mov	ah,conout
	int	dos
	ret
mvalout	endp


valout	proc	far		; output number in ax using base in cx
				; corrupts ax and dx
	xor	dx,dx		; clear high word of numerator
	div	cx		; (ax / cx), remainder = dx, quotient = ax
	push	dx		; save remainder for outputting later
	or	ax,ax		; any quotient left?
	jz	valout1		; z = no
	call	valout		; yes, recurse
valout1:pop	dx		; get remainder
	add	dl,'0'		; make digit printable
	cmp	dl,'9'		; above 9?
	jbe	valout2		; be = no
	add	dl,'A'-1-'9'	; use 'A'--'F' for values above 9
valout2:mov	ah,conout
	int	dos
	ret
valout	endp

; Write binary number in AX as decimal asciiz to buffer pointer DS:DI.
dec2di	proc	far		; output number in ax using base in cx
	push	ax
	push	cx
	push	dx
	mov	cx,10
	call	dec2di1		; recursive worker
	pop	dx
	pop	cx
	pop	ax
	ret

dec2di1	proc	near		; worker of dec2di
	xor	dx,dx		; clear high word of numerator
	div	cx		; (ax / cx), remainder = dx, quotient = ax
	push	dx		; save remainder for outputting later
	or	ax,ax		; any quotient left?
	jz	dec2di2		; z = no
	call	dec2di1		; yes, recurse
dec2di2:pop	dx		; get remainder
	add	dl,'0'		; make digit printable
	mov	[di],dl		; store char in buffer
	inc	di
	mov	byte ptr[di],0	; add terminator
	ret
dec2di1	endp
dec2di	endp

; Perform math. Reads argptr string, returns binary value in DX:AX.
; Expects pointer to string to be in domath_ptr and its length in domath_cnt.
; Note that value and operator lists are stored on the stack in positive
; order (increasing array values are at increasing addresses, not in push
; order).
; Returns carry set if error.
domath	proc	far
	push	bx
	push	cx
	push	si
	push	di
	mov	si,domath_ptr
	mov	di,domath_cnt
	mov	parendepth,0		; parenthesis nesting depth
	call	domath_main
	mov	domath_msg,0		; re-enable domath error messages
	jnc	domathx1
	mov	domath_ptr,si
	mov	domath_cnt,di
domathx1:	
	pop	di
	pop	si
	pop	cx
	pop	bx
	ret
domath	endp

domath_main proc near
	push	si
	push	bp
	sub	sp,size fevalst		; counters and lists
	mov	bp,sp			; remember base of lists
	mov	[bp].numcnt,0		; no numbers in list
	mov	[bp].opcnt,0		; no operators in list
	mov	[bp].numlist,0		; returned value
	mov	[bp].numlist[2],0	; high order returned value
	inc	parendepth		; recursion limit counter
	cmp	parendepth,maxdepth	; called too many times?
	jbe	domath0			; be = no
	mov	matherr,offset opmsg4
	jmp	domath_error		; a = too many parentheses

domath0:call	gettoken		; read a token from string
	jc	domath_exit		; c = nothing present
	cmp	bl,'O'			; operator?
	je	domath1			; e = yes
	cmp	[bp].numcnt,2		; have two numbers (num num)?
	jb	domath0			; b = no, get an operator
	mov	matherr,offset opmsg1	; missing operator
	jmp	domath_error		; exit with grammar error

	; have initial operator in AX, decide in/pre/post-fix style
domath1:cmp	al,'!'			; postfix? (number is on stack)
	jne	domath2			; ne = no
	call	domath_calc1		; do math on one arg, result on stack
	jc	domath_error		; failure, exit
	jmp	short domath0		; done here, get next tokens

					; prefix or infix?
domath2:call	gettoken		; get expected following number
	jc	domath_error		; c = eof, unexpected, error
	cmp	bl,'N'			; got the number?
	jne	domath4			; ne = no, operator
domath3:call	domath_la		; call lookahead proc
	; c clear, bl = N for use current number on stack
	; c clear, bl = O for have higher prec operator on stack & ax, delay
	; c clear, bl = F for read end of file, no next operator
	; c set,   bl = F for math failure, best to exit
	jc	domath_error		; failure on math
	cmp	bl,'F'			; end of file?
	je	domath_end		; e = eof, do remaining math
	cmp	bl,'O'			; found higher prec op (delay)?
	je	domath1			; e = yes, do newest operator
	call	domath_calc2		; bl = 'N', number ready, do math
	jc	domath_error		; c = failure
	cmp	[bp].opcnt,0		; any ops backlogged?
	jne	short domath3		; be = try again to reduce back math
	jmp	domath0			; start over

			; Have op op. Is second op a prefix operator?
domath4:cmp	al,'~'			; prefix?
	je	domath5			; e = yes
	cmp	al,'-'			; prefix, overload?
	je	domath5			; e = yes
	cmp	al,'+'			; prefix, overload?
	jne	domath_error		; ne = no, inconsistent syntax
		; could be  op number postfix  so look at next token to see
		; and if so reduce <number postfix> before applying prefix.
domath5:call	gettoken		; get expected number following prefix
	jc	domath_end		; c = eof, do remaining math
	cmp	bl,'O'			; operator?
	je	domath_error		; e = yes, that makes op op op
	call	domath_la		; do lookahead for postfix
	jc	domath_error		; c = failure on math
	call	domath_calc1		; do prefix calculation now
	jc	domath_error		; c = failure
	call	popop			; get first operator to ax
	call	pushop			; put it back as if just read
	mov	bl,'O'			; mark as operator
	jmp	short domath3		; reanalyze from new operator

					; end of data, finish up math & exit
domath_end:				; do calcs remaining on stack
	cmp	[bp].opcnt,0		; operators remaining to be done?
	je	domath_exit		; e = no
	call	domath_calc2		; finish math
	jc	domath_error		; c = failure
	jmp	short domath_end	; keep trying til no more operators

domath_error:				; math errors seen, exit in error
	mov	cx,1			; set carry for error
	jmp	short domath_exit1

domath_exit:				; only way out, restore stack
	xor	cx,cx			; assume success, cx = error counter
domath_exit1:
	dec	parendepth		; nesting depth
	cmp	[bp].numcnt,1		; only one value remaning?
	je	domath_exit2		; e = yes, that's good
	jb	domath_exit2a		; b = no numbers, empty arg
	jcxz	domath_exit2		; z = no error

	cmp	domath_msg,0		; msgs permitted?
	jnz	domath_exit2a		; nz = no
	mov	matherr,offset opmsg1	; missing operator
domath_exit2a:
	inc	cx			; say error
domath_exit2:
	cmp	[bp].opcnt,0		; used all operators?
	je	domath_exit3		; e = yes
	mov	matherr,offset opmsg	; too many operators
	inc	cx			; say error

domath_exit3:				; all returns
	mov	ax,[bp].numlist		; returned value
	mov	dx,[bp].numlist[2]	; high order part
	mov	sp,bp			; base of lists
	add	sp,size fevalst		; counters and lists
	or	cx,cx			; any errors?
	jz	domath_exit4		; z = no (clears carry bit)
	cmp	domath_msg,0		; ok to complain?
	jne	domath_exit3a		; ne = no, keep quiet
	mov	ah,prstr
	mov	dx,matherr		; math error pointer
	or	dx,dx			; if any
	jz	domath_exit3a		; z = none
	push	dx
	mov	dx,offset crlf
	int	dos
	pop	dx
	int	dos			; display error message
domath_exit3a:
	xor	ax,ax			; return zero on errors
	xor	dx,dx
	stc				; set carry for fail
domath_exit4:
	mov	matherr,0
	pop	bp
	pop	si
	ret				; all done. Result in DX:AX
domath_main endp

; Worker for domath. Reads argptr, inc's it, dec's arglen. Recognizes
; numbers (with or without leading \), recognizes math operators and ().
; Returns numbers as binary in DX:AX and BL = 'N',
; returns operator and precedence in AL and AH with BL = 'O'. 
; Returns Carry set and BL = 'F' if neither kind of token.
; Tolerates leading whitespace.
; Uses domath_ptr as source pointer, domath_cnt as source count.
; Array domath_tmp is owned by this routine as private space.
gettoken proc	near
	xor	ax,ax			; no operator, lowest precedence
	xor	dx,dx

gettok0:cmp	domath_cnt,0		; anything to read?
	jg	gettok1			; g = yes
	mov	bl,'F'			; F for end of file, failure
	stc
	ret

gettok1:mov	si,domath_ptr
	mov	al,byte ptr [si]
	cmp	al,' '			; leading whitepace?
	jne	gettok1a		; ne = no
	inc	domath_ptr
	dec	domath_cnt
	jmp	short gettok0

gettok1a:jb	gettok1b		; b = non-printable
	cmp	al,127
	jb	gettok2			; b = printable
gettok1b:mov	bl,'F'			; declare F for end of file, failure
	mov	domath_cnt,0		; truncate string
	clc
	ret

gettok2:mov	cx,domath_cnt
	mov	atoi_cnt,cx
	call	atoi			; convert DS:SI to number in DX:AX
	jc	gettok5			; c = failed to convert number
	mov	cx,si
	sub	cx,domath_ptr		; amount read
	sub	domath_cnt,cx		; deduct that read
	mov	domath_ptr,si		; break char
	call	pushval			; push onto number stack
	mov	bl,'N'			; say returning number
	ret				; may have carry bit set

gettok5:mov	si,domath_ptr
	lodsb				; reread non-numeric
	inc	domath_ptr
	dec	domath_cnt
	push	di			; look for operators
	push	es
	mov	di,seg operators	; list of math operators
	mov	es,di
	mov	di,offset operators
	mov	cx,operator_len		; length of list
	cld
	repne	scasb			; look for match
	pop	es
	je	gettok6			; e = found a match
	pop	di
	cmp	domath_msg,0		; allowed to display messages?
	jne	gettok5a		; ne = no
	push	ax
	mov	ah,prstr
	mov	dx,offset crlf
	int	dos
	mov	dx,offset opmsg2	; unknown symbol
	int	dos
	pop	ax
	mov	dl,al			; unknown op
	mov	ah,conout
	int	dos
	mov	ah,prstr
	mov	dx,offset crlf
	int	dos
gettok5a:
	dec	domath_ptr		; set to read on this symbol
	inc	domath_cnt
	mov	bl,'F'			; say returning parse failure
	stc				; fail
	ret
gettok6:sub	di,offset operators + 1	; index into list
	mov	ah,precedence[di]	; precedence to return
	pop	di
	mov	bl,'O'			; say returning operator
	cmp	al,'('			; start recursion?
	je	gettok7
	cmp	al,')'			; closure?
	je	gettok9			; e = yes, it's an EOF here
	call	pushop			; push operator on op stack
	clc
	ret

gettok7:call	domath_main		; '(', invoke new scan
	jnc	gettok8			; nc = succeeded
	ret				; c = return failure
gettok8:call	pushval			; push returned DX:AX
	mov	bl,'N'			; report number
	ret				; may have carry bit set

gettok9:mov	bl,'O'
	stc				; ')' returns EOF
	ret
gettoken endp

; Lookahead processor. Have ...<op><number> on stack
; Return bl = N for return number present on stack, carry clear
; Return bl = O for return higher prec operator on stack, carry clear
; Return bl = F and carry clear for end of file
; Return bl = F and carry set for math failure (errors)

domath_la proc near
	call	gettoken		; expect operator or end of file
	jnc	domath_la1		; nc = success
	mov	bl,'F'			; say eof
	clc				; carry clear for eof
	ret

domath_la1:
	cmp	bl,'O'			; lookahead operator?
	je	domath_la2		; e = yes
	mov	bl,'F'			; fail, case of number number
	stc				; syntax error, signal quit
	ret

domath_la2:
	mov	si,[bp].opcnt		; get size of op list
	dec	si			; zero based index
	or	si,si			; sanity check, should be > 0
	jnz	domath_la2a		; nz = have current op available
	mov	bl,'F'			; signal total failure
	stc
	ret
domath_la2a:
	shl	si,1			; index words
	mov	ax,[bp].oplist[si]	; lookahead operator
	mov	cx,[bp].oplist[si-2]	; currently active operator
	cmp	ah,ch			; lookahead versus current op
	ja	domath_la3		; a = lookahead is higher, delay
	call	popop			; remove lookahead operator from stack
	dec	domath_ptr		; set to reread operator
	inc	domath_cnt
	mov	bl,'N'			; signal number ready on stack
	clc
	ret

domath_la3:				; delay
	cmp	al,'!'			; postfix on number?
	jne	domath_la5		; ne = no, another operator, delay
	call	domath_calc1		; combine number!, report number
	jnc	domath_la4		; nc = success, use number on stack
	mov	bl,'F'			; report failed
	ret				; carry set

domath_la4:mov	bl,'N'			; report number is on stack
	clc				; say success
	ret

domath_la5:mov	bl,'O'		; lookahead (delayed op) is on stack and ax
	clc			; bl = 'O', signal delay and lookahead op
	ret			; is on the stack
domath_la endp

; Calculate results of top operator and one top value.
; Return value on top of the value stack.
; Returns carry set if failure
; Pops one operator, and zero (failure) or one value from stack before
; starting.
domath_calc1	proc	near
	mov	si,[bp].opcnt		; count of operators
	cmp	si,0			; any?
	jg	dom_c1_1a		; g = yes
	mov	matherr,offset opmsg3
	stc				; fail
	ret
dom_c1_1a:
	cmp	[bp].numcnt,0		; one or more values?
	jne	dom_c1_1b		; ne = yes
	mov	matherr,offset opmsg	; too few values
	stc
	ret
dom_c1_1b:
	call	popop			; get top operator from list to AX
	mov	mathkind,al		; operation to do
	call	popval			; get first value from list to DX:AX

	cmp	mathkind,'!'		; factorial?
	jne	dom_c1c			; ne = no
	mov	bx,ax			; save argument
	mov	cx,ax
	mov	ax,-1			; default for too large an arg
	mov	dx,ax
	cmp	cx,10			; largest within 32 bits?
	ja	dom_c1b			; a = too large, report -1
	mov	ax,1			; default for 0! and 1!
	xor	dx,dx
	sub	cx,ax
	jle	dom_c1b			; le = special cases of 0 and 1
	mov	ax,bx			; recover argument
dom_c1a:mul	cx
	loop	dom_c1a
dom_c1b:jmp	dom_c1_1x

dom_c1c:cmp	mathkind,'~'		; logical not?
	jne	dom_c1d			; ne = no
	not	ax			; 1's complement
	not	dx
	jmp	dom_c1_1x

dom_c1d:cmp	mathkind,'-'		; negate?
	jne	dom_c1e			; ne = no
	neg	dx
	neg	ax			; change sign
	sbb	dx,0
	jmp	dom_c1_1x

dom_c1e:cmp	mathkind,'+'		; unary '+'?
	je	dom_c1_1x		; e = yes

dom_c1f:stc				; unknown operator
	mov	matherr,offset opmsg2
	ret
dom_c1_1x:
	call	pushval
	ret				; may have carry bit set
domath_calc1 endp

; Calculate results of top operator and two top values.
; Return value on top of the value stack.
; Returns carry set if failure
; Pops one operator, and one (failure) or two values from stack before
; starting.
domath_calc2 proc near
	cmp	[bp].numcnt,2		; two or more args?
	jb	domath_calc1		; b = no, do 1 arg math

	call	popop			; get top operator from list to AX
	jnc	dom_c2_2a		; nc = success
	mov	matherr,offset opmsg3
	ret				; fail
dom_c2_2a:
	mov	mathkind,al		; operation to do
	call	popval			; get first value from list to DX:AX
	mov	bx,ax			; right side, low order
	mov	cx,dx			; right side, high order
	call	popval			; first (left side) to dx:ax
	cmp	mathkind,'^'		; raise arg1 to power of arg2?
	jne	dom_c2_6		; ne = no
	mov	cx,bx			; use low order of exponent
	dec	cx			;
	cmp	cx,31			; largest exponent we allow
	jbe	dom_c2_1		; be = ok
	mov	ax,-1			; report -1 as overflow result
	cwd
	jc	dom_c2_2y		; c = overflow error
dom_c2_1:or	cl,cl			; zero power?
	jne	dom_c2_2		; ne = no
	mov	ax,1			; report 1
	xor	dx,dx
	jmp	dom_c2_2x
dom_c2_2:push	si
	push	di
	mov	si,ax			; original number
	mov	di,dx
dom_c2_3:push	cx			; save loop counter
	mov	cx,di
	mov	bx,si			; original number
	call	lmul			; use long multiply
	pop	cx
	jc	dom_c2_4		; c = overflow
	loop	dom_c2_3
	pop	di
	pop	si
	jmp	dom_c2_2x
dom_c2_4:pop	di
	pop	si
	stc
	jmp	dom_c2_2y		; failure from overflow

dom_c2_6:cmp	mathkind,'*'		; times?
	jne	dom_c2_7		; ne = no
	call	lmul			; do 32 bit signed multiply
	jc	dom_c2_2y		; c = overflow error
	jmp	dom_c2_2x

dom_c2_7:cmp	mathkind,'/'		; divide?
	jne	dom_c2_8		; ne = no
	call	ldiv
	jc	dom_c2_2y		; c = overflow error
	jmp	dom_c2_2x

dom_c2_8:cmp	mathkind,'%'		; modulo?
	jne	dom_c2_9		; ne = no
	call	ldiv			; divide
	jc	dom_c2_2y		; c = overflow error
	xchg	cx,dx			; get remainder to dx:ax
	xchg	bx,ax
	jmp	dom_c2_2x

dom_c2_9:cmp	mathkind,'&'		; logical AND?
	jne	dom_c2_11		; ne = no
	and	ax,bx
	and	dx,cx
	jmp	dom_c2_2x

dom_c2_11:cmp	mathkind,'+'		; addition?
	jne	dom_c2_12		; ne = no
dom_c2_11a:
	push	si
	mov	si,dx			; get sign bit
	xor	si,cx			; different? if so then 1
	add	ax,bx
	adc	dx,cx
	test	si,8000h		; check for case of same start signs
	jnz	dom_c2_11b		; nz = different signs, no overflow
	mov	si,dx
	xor	si,cx			; different
	test	si,8000h		; different signs now?
	pop	si
	jnz	dom_c2_2y		; nz = yes, overflow
	jmp	dom_c2_2x
dom_c2_11b:pop	si
	jmp	dom_c2_2x

dom_c2_12:cmp	mathkind,'-'		; subtraction?
	jne	dom_c2_13		; ne = no
	neg	cx
	neg	bx			; change sign of right side number
	sbb	cx,0
	jmp	short dom_c2_11a	; now do addition with bounds check

dom_c2_13:cmp	mathkind,'|'		; logical OR?
	jne	dom_c2_14		; ne = no
	or	ax,bx
	or	dx,cx
	jmp	dom_c2_2x

dom_c2_14:cmp	mathkind,'#'		; exclusive OR?
	jne	dom_c2_15		; ne = no
	xor	ax,bx
	xor	dx,cx
	jmp	dom_c2_2x

dom_c2_15:cmp	mathkind,'@'		; GCD?
	jne	dom_c2_16		; ne = no
	call	lgcd			; call the gcd routine
	jc	dom_c2_2y		; c = overflow error
	jmp	dom_c2_2x
dom_c2_16:stc
	ret				; unknown operator

dom_c2_2x:
	call	pushval			; store result
	ret
dom_c2_2y:
	call	pushval			; store result (typically -1)
	mov	matherr,offset opmsg3
	stc				; say failure of math
	ret
domath_calc2 endp

; Push operator (AL) and precedence (AH) onto the operator stack.
; Increments [bp].opcnt.
; Return carry set if no room.
pushop	proc	near
	mov	si,[bp].opcnt		; count of operators
	cmp	si,listlen		; list full?
	jae	pushop1			; ae = yes, fail
	shl	si,1			; words
	mov	[bp].oplist[si],ax	; save operator (AL) and preced (AH)
	inc	[bp].opcnt		; one more operator in the list
	clc				; success
	ret
pushop1:mov	matherr,offset opmsg1
	stc				; fail
	ret
pushop	endp

; Pop current opcode from op stack to AX. Decrements [bp].opcnt.
; Returns carry set if no operators are available
popop	proc	near
	mov	si,[bp].opcnt		; count of operators in list
	or	si,si			; any?
	jz	popop1			; z = no, exit
	dec	si			; index from zero
	shl	si,1			; words
	mov	ax,[bp].oplist[si]	; get lookahead (last) operator
	dec	[bp].opcnt
	clc				; success
	ret
popop1:	mov	matherr,offset opmsg1
	stc				; failed to op (empty list)
	ret
popop	endp

; Push value in DX:AX onto value stack. Increments [bp].numcnt.
; Returns carry set if not enough space.
pushval	proc	near
	mov	si,[bp].numcnt
	cmp	si,listlen		; at limit of list?
	ja	pushval1		; a = exceeded list length, fail
	shl	si,1			; address dwords
	shl	si,1
	mov	[bp].numlist[si],ax	; push dx:ax onto value stack
	mov	[bp].numlist[si+2],dx	; high order part
	inc	[bp].numcnt		; occupancy is one greater
	clc				; succeed
	ret
pushval1:stc				; fail
	ret
pushval	endp

; Pop top value from value stack into DX:AX. Decrements [bp].numcnt.
; Returns carry set if no value were available.
popval	proc	near
	mov	si,[bp].numcnt		; count of values in list
	or	si,si			; count
	jz	popval1			; z = none
	dec	si			; index from 0
	shl	si,1			; address dwords
	shl	si,1
	mov	ax,[bp].numlist[si]	; pop top value to dx:ax
	mov	dx,[bp].numlist[si+2]	; high order part
	dec	[bp].numcnt		; occupancy is one less
	clc				; success
	ret
popval1:stc				; fail
	ret
popval	endp
					; end of domath procedures

; Perform 32 bit division. Numerator is in DX:AX, denominator in CX:BX.
; Returns quotient in DX:AX, remainder in CX:BX. Carry set and -1 on
; divide by zero.
ldiv	proc	far
	push	temp
	push	temp1
	push	si
	push	di
	mov	tmp,0			; holds final sign (0=positive)
	or	dx,dx			; numerator is negative?
	jge	ldiv1			; ge = no
	neg	dx
	neg	ax
	sbb	dx,0			; change sign
	xor	tmp,1			; remember to change sign later
ldiv1:	or	cx,cx			; denominator is negative?
	jge	ldiv2			; ge = no
	neg	cx
	neg	bx
	sbb	cx,0			; change sign
	xor	tmp,1			; remember to change sign later
ldiv2:	mov	di,cx			; denominator, hold here
	mov	si,bx
	cmp	dx,cx			; is numerator larger than denom?
	jne	ldiv10			; ne = yes, and 32 bits too
	or	dx,dx			; 32 bit number?
	jnz	ldiv10			; nz = yes
				; only 16 bit numbers here
	or	bx,bx			; denominator zero?
	jnz	ldiv3			; nz = no
	jmp	ldivf			; overflow, report failure

ldiv3:	div	bx			; regular signed division
	mov	bx,dx			; remainder
	xor	dx,dx			; high quotient
	xor	cx,cx			; high remainder
	jmp	ldivx			; exit success

				; 32 bit numbers here
ldiv10:	mov	tmp1,0			; shift counter
	mov	temp,0			; shift accumulator, low part
	mov	temp1,0			; shift accumulator, high part
	or	cx,cx			; check for zero denominator
	jnz	ldiv11			; nz = not zero
	or	bx,bx
	jz	ldivf			; zero, exit failure

ldiv11:	cmp	dx,di			; top vs bottom high order words
	jb	ldiv13			; b = top smaller than bottom
	ja	ldiv12			; a = top larger than bottom
					; high order words are the same
	cmp	ax,si			; top vs bottom low order words
	jb	ldiv13			; b = top eq or larger than bottom
	je	ldiv15			; e = top equals bottom
				; top is larger than bottom
ldiv12:	or	di,di			; can shift left further?
	js	ldiv14			; s = no, accumulate and back down
	inc	tmp1			; remember doing left shift
	shl	si,1			; shift denominator left one bit
	rcl	di,1			; include carry-out from low half
	jmp	short ldiv11		; compare again til top < bot

; either we start with top < bottom (tmp1 = 0), or we have left shifted
; the bottom enough (tmp1 > 0) to create that condition.

       				; top is smaller than or equal to bottom
ldiv13:	cmp	tmp1,0			; any shifts remaining?
	je	ldiv16			; e = no
; If tmp1 is 0 then we are done, no further adjustments can be made.
; Else back off tmp1 by one position
	dec	tmp1			; shift right by 1 to get top >= bot
	shr	di,1
	rcr	si,1

ldiv14:	cmp	dx,di			; is top less than bottom?
	jb	ldiv13			; b = yes, shift again
	ja	ldiv15			; a = no, greater than
	cmp	ax,si			; low order part
	jb	ldiv13			; b = top is less than bottom
ldiv15:	sub	ax,si			; (top - bottom) to get remainder
	sbb	dx,di
	call	ldiv_acc		; accumulate shifted success
	jmp	short ldiv14		; try again til top < bot

ldiv16:	mov	bx,ax			; remainder to cx:bx
	mov	cx,dx			; quotient to dx:ax
	mov	ax,temp			; extract accumlated shifts
	mov	dx,temp1		; and exit success

					; successful exit, update signs
ldivx:	cmp	tmp,0			; need to adjust signs?
	je	ldivx1			; e = no
	neg	dx
	neg	ax			; change sign of quotient
	sbb	dx,0
	neg	cx
	neg	bx			; change sign of remainder
	sbb	cx,0
ldivx1:	pop	di
	pop	si
	pop	temp1
	pop	temp
	clc				; success
	ret

ldivf:	mov	ax,-1			; failure
	cwd				; report -1
	mov	bx,ax
	mov	cx,ax
	pop	di			; failure exit
	pop	si
	pop	temp1
	pop	temp
	stc				; say failure
	ret

ldiv_acc proc	near			; add current shift to accumulator
	push	cx
	push	ax
	push	dx
	mov	cl,tmp1			; shift bit count
	xor	ch,ch
	mov	ax,1			; value = 2 ** temp1
	xor	dx,dx
	jcxz	ldiv_acc2		; z = nothing to do
ldiv_acc1:shl	ax,1
	rcl	dx,1
	loop	ldiv_acc1
ldiv_acc2:add	temp,ax			; accumulate shifts
	adc	temp1,dx
	pop	dx
	pop	ax
	pop	cx
	ret
ldiv_acc endp
ldiv	endp

; Multiplies 32 bit values in DX:AX by CX:BX and returns the result in
; DX:AX. Overflows result in carry set and -1 as the answer. Signed
; arithmetic is used here, beware.
lmul	proc	far
	push	si
	push	di
	push	bx
	push	cx
	push	temp
	mov	temp,0			; zero means no sign change at end
	or	dx,dx			; negative?
	jns	lmul1			; ns = no
	neg	ax			; make positive
	neg	dx
	sbb	dx,0
	xor	temp,1			; sign change needed
lmul1:	or	cx,cx			; ditto for cx:bx
	jns	lmul2
	neg	bx
	neg	cx
	sbb	cx,0
	xor	temp,1
lmul2:	or	cx,cx			; check for high word in both numbers
	jz	lmul3			; z = bottom does not have it
	or	dx,dx
	jnz	lmul4			; nz = both have parts too large

lmul3:	push	dx
	push	ax
	mul	bx			; low bottom time whole top
	mov	di,dx
	mov	si,ax			; regular product to di:si
	pop	ax
	pop	dx			; recover normal top
	push	dx
	push	ax
	mov	ax,dx
	mul	bx
	add	di,ax
	pop	ax
	pop	dx			; recover normal top
	js	lmul4			; s = overflow in high accumlator
	mul	cx			; high bottom times regular top
	add	di,ax			; high reg product plus new product
	jns	lmul5			; ns = no overflow in high accumulator

lmul4:	mov	ax,-1			; overflow, yield minus one as answer
	cwd				; extend sign to dx
	mov	temp,0			; no sign change
	stc				; carry set for problem
	jmp	short lmul6
lmul5:	mov	dx,di			; results to dx:ax
	mov	ax,si
	cmp	temp,0			; sign change needed?
	je	lmul6			; e = no
	neg	dx			; flip output sign
	neg	ax
	sbb	dx,0
	clc				; carry clear for no problem
lmul6:	pop	temp
	pop	cx
	pop	bx
	pop	di
	pop	si
	ret
lmul	endp

; Greatest common divisor, 32 bit (removes signs). Inputs are DX:AX and
; CX:BX. Results are in DX:AX. Carry bit set and -1 returned if error.
lgcd	proc	far
	push	bx
	push	cx
	or	dx,dx			; negative?
	jns	lgcd1			; ns = no
	neg	ax			; make positive
	neg	dx
	sbb	dx,0
lgcd1:	or	cx,cx			; negative?
	jns	lgcd2			; ns = no
	neg	bx
	neg	cx			; make positive
	sbb	cx,0
lgcd2:	cmp	dx,cx			; first arg same/larger than second?
	ja	lgcd4			; a = yes
	jb	lgcd3			; b = smaller
	cmp	ax,bx			; low order parts
	jae	lgcd4			; ae = larger
lgcd3:	xchg	dx,cx			; make largest the top number dx:ax
	xchg	ax,bx
lgcd4:	push	bx			; preserve smaller number
	push	cx
	call	ldiv			; large / small
	jc	lgcd6			; c = divide by zero
	mov	dx,cx			; temp spot
	or	dx,bx			; remainder?
	jz	lgcd5			; z = no, small is the answer
	pop	dx			; new values, new top is old bot
	pop	ax			; bottom is new remainder
	jmp	short lgcd2		; repeat calculation
lgcd5:	pop	dx			; recover smaller number from stack
	pop	ax			; result in dx:ax
	pop	dx
	pop	cx
	clc
	ret				; success
lgcd6:	pop	ax
	pop	ax			; clean stack
	pop	dx
	pop	cx
	mov	ax,-1			; dx:ax = -1 on error
	cwd
	stc
	ret
lgcd	endp

; Convert ASCIIZ string in DS:SI of form hh:mm:ss to ASCIIZ string seconds
; in ES:DI.
tod2secs proc	far
	mov	temp,0
	mov	temp1,0
	xor	dx,dx			; dh=field counter, dl=read byte
	xor	bx,bx			; three groups possible
tod2s1:	mov	dl,[si]			; get a char
	inc	si			; next char
	inc	dh			; count char in field
	or	dl,dl			; null terminator?
	jz	tod2s3			; z = yes, wrap calculations and quit
	cmp	dl,':'			; field separator?
	je	tod2s3			; e = a separator, step fields
	sub	dl,'0'			; remove ascii bias
	cmp	dl,9
	ja	short tod2s6		; a = failure to get expected digit
	cmp	dh,2			; more than two bytes in this field
	ja	tod2s6			; a = yes, fail translation
	mov	al,bh			; get sum to al
	mov	ah,10
	mul	ah			; sum times ten
	add	al,dl			; sum = 10 * previous + current
	mov	bh,al			; current sum
	mov	ah,60
	or	bl,bl			; doing hours?
	jne	tod2s2			; ne = no, min, sec
	mov	ah,24			; max for hours field
tod2s2:	cmp	bh,ah			; more than legal?
	jae	tod2s6			; ae = illegal
	jmp	short tod2s1		; continue analysis

tod2s3:	mov	al,bh			; current sum
	xor	ah,ah
	mov	cx,1
	cmp	bl,2			; seconds?
	je	tod2s4			; e = yes
	mov	cx,60
	or	bl,bl			; hours?
	jne	tod2s4			; ne = no
	mov	cx,60*60		; seconds per hour
tod2s4:	mul	cx
	add	temp,ax
	adc	temp1,dx
	xor	bh,bh			; bh = current field sum
	xor	dh,dh			; dh = counter of bytes in field
	cmp	byte ptr [si-1],0	; ended on null?
	je	tod2s5			; e = yes, end of conversion
	inc	bl			; point to next field
	cmp	bl,2			; last field to use (secs)
	jbe	tod2s1			; be = get more text
tod2s5:	mov	ax,temp
	mov	dx,temp1
	push	di
	mov	di,offset tmpstring	; build ASCII in this buffer
	call	flnout
	pop	di
	mov	dx,offset tmpstring
	mov	si,dx
	call	strlen
	cld
	rep	movsb			; copy string to es:di area
	mov	byte ptr es:[di],0	; and null terminator
	clc				; carry clear for success
	ret
tod2s6:	mov	byte ptr es:[di],0
	stc				; carry set for illegal value	
	ret
tod2secs endp
code1	ends 
	end
