	NAME	msscmd
; File MSSCMD.ASM
	include mssdef.h
;  Copyright (C) 1985, 1993, Trustees of Columbia University in the 
;  City of New York.  Permission is granted to any individual or institution
;  to use this software as long as it is not sold for profit.  This copyright
;  notice must be retained.  This software may not be included in commercial
;  products without written permission of Columbia University.
;
; Edit history:
; 27 August 1992 version 3.13
; 6 Sept 1991 version 3.11
; 13 March 1991 version 3.10
; Last edit 21 Oct 1992
; 5 Jan 1991 Add \$(Environment variable) substitution.
; 9 Sept 1990 Add \v(named variable) substitution.
; 21 Nov 1988 Version 2.32
; 17 Oct 1988 Make command keyword search failure yield global kstatus of
;  failure status, to inform Take/Macros of a defective command.
; 4 Aug 1988 Fix keyword count initialization in help display.
; 1 July 1988 Version 2.31
; 7 June 1988 Add comand.impdo flag to permit a keyword search failure to
;  retry as a DO cmd (macro table). Used only by main Kermit command level.
; 27 May 1988 Allow "-<cr>" as line continuation pair and let "\-<cr>" 
;  stand for "-<end of line>". Add comand.cmblen to sense buffer overflow on
;  calls to cmtxt; if comand.cmblen is left at zero then use 128 byte limit
;  (comand.cmblen is cleared at command end).
; 15 May 1988 Make :label keywords no-ops, make query ordinary char in TAKEs.
; 6 May 1988 Show ambiguous keywords at parse time, do graceful interaction.
; 28 March 1988 Permit '\%x' (x = '0' or above) in commands as 'variables'
;  (substituted string of text). Words are obtained from Macro table mcctab.
;  DEF MAC and DO MAC define variables. DEF macro uses comand.cmper > 0 to
;  allow '\%x'to be stored as a literal. Variables work in any command except
;  DEF MAC. Major redesign of whole parser. [jrd]
; 4 March 1988 Rewrite keyword parsing to permit non-alphabetized tables
;  and 8-bit characters. Add byte comand.cmwhite which when non-zero permits
;  leading whitespace in cmline and cmword commands; it is reset at command
;  completion. Move procedure Prompt here from mssker. [jrd]
; 27 Feb 1988 Add capability of stdin being a file. [jrd]
; 1 Jan 1988 version 2.30

 	public comnd, comand, isdev, iseof, prompt, tolowr, valbuf, valtab
	public parstate, pardone, parfail, nparam, param, lparam, ninter
	public inter, atparse, atpclr, atdispat, cspb, dspb, mprompt, nvaltoa
	public savepoff, saveplen, keyboard, fwrtdir, cspb1

env	equ	2CH			; environment address in psp
braceop	equ	7bh			; opening curly brace
bracecl	equ	7dh			; closing curly brace

data 	segment
	extrn	flags:byte, taklev:byte, takadr:word, mcctab:byte
	extrn	kstatus:word, oldtak:byte, errlev:byte, psp:word
	extrn	portval:word, bdtab:byte, tdfmt:byte, machnam:byte
	extrn	verident:word, kstatus:word, errlev:byte, comptab:byte
	extrn	termtb:byte, sescur:word, dosnum:word

				; Start Patch structure. Must be first.
	even
dspb	dw	code		; segment values for patcher
	dw	code1
	dw	code2
	dw	data
	db	64 dup (0)	; data segment patch buffer
				;  with space for other material, if req'd
				; end of Patch structure
progm	db	'MS-DOS_KERMIT$'	; for \v(program)
system	db	'MS-DOS$'		; for \v(system)
keyboard dw	88			; \v(keyboard) kind of keybd 88/101

comand	cmdinfo	<>
cmer00  db      cr,lf,'?Program internal error, recovering$'
cmer01	db	cr,lf,'?More parameters are needed$'
cmer02	db	cr,lf,'?Word "$'
cmer03	db	'" is not usable here$'
cmer04	db	'" is ambiguous$'
cmer07	db	cr,lf,'?Ignoring extra characters "$'
cmer08	db	'"$'
cmer09	db	cr,lf,'?Text exceeded available buffer capacity$'
cmin00  db      ' Press ENTER to execute command$'
cmin01	db	' Use one of the following words in this position:',cr,lf,'$'
stkmsg	db	cr,lf,bell,'?Exhausted work space! Circular definition?$'
crlf    db      cr,lf,'$'
ctcmsg	db	5eh,'C$'
cmunk	db	'unknown'
errflag	db	0			; non-zero to suppress cmcfrm errors
kwstat	db	0			; get-keyword status
prevch	db	0			; previous char read by cmgetc
noparse	db	0			; semicolons not special, if non-zero
subcnt	db	0			; count of chars matched in '\%'
subtype db	0			; kind of sub (% or v)
bracecnt db	0			; curly brace counter
cmsflg	db	0			; Non-zero when last char was a space
cmdbuf	db	cmdblen dup (0)		; Buffer for command parsing
	even
cmdstk	dw	0			; stack pointer at comand call time
cmptab	dw	0			; Address of present keyword table
cmhlp	dw	0			; Address of present help
cmwptr	dw	0			; Pointer for next char write
cmrptr	dw	0			; Pointer for next char read
cmsiz	dw	0			; Size info of user input
cmsptr	dw	0			; Place to save a pointer
mcmprmp	dw	0			; master prompt, string address
mcmrprs	dd	0			; master prompt, reparse address
mcmostp	dw	0			; master prompt, stack pointer
temp	dw	0			; temp (counts char/line so far)

valtab	db	1+19			; table of values for \v(value)
	mkeyw	'argc)',1
	mkeyw	'count)',2
	mkeyw	'date)',3
	mkeyw	'ndate)',14
	mkeyw	'directory)',5
	mkeyw	'dosversion)',19
	mkeyw	'errorlevel)',4
	mkeyw	'keyboard)',10
	mkeyw	'line)',15
	mkeyw	'platform)',8
	mkeyw	'port)',15
	mkeyw	'program)',12
	mkeyw	'session)',17
	mkeyw	'speed)',11
	mkeyw	'status)',13
	mkeyw	'system)',9
	mkeyw	'terminal)',16
	mkeyw	'time)',6
	mkeyw	'ntime)',18
	mkeyw	'version)',7

envtab	db	1			     	; \$(..) Environment table
	mkeyw	'An Environment variable)',0	; reserve 0 in valtab

valtmp	dw	0
valbuf	db	130 dup (0)		; storage of variable definition

	even
envadr	dd	0			; seg:offset of a string in Environemt
envlen	dw	0			; length of envadr's string

	even				; Control sequence storage area
maxparam equ	16			; number of ESC and DCS Parameters
maxinter equ	16			; number of ESC and DCS Intermediates

savepoff label	word			; Start per session save area
parstate dw	0			; parser state, init to startup
pardone dw	0			; where to jmp after Final char seen
parfail	dw	0			; where to jmp if parser fails
nparam	dw	0			; number of received Parameters
param	dw	maxparam dup (0)	; Parameters for ESC
lparam	db	0			; a single letter Parameter for ESC
ninter	dw	0			; number of received Intermediates
inter	db	maxinter dup (0),0	; Intermediates for ESC, + guard 
saveplen dw	($-savepoff)

pcmcfrm	dd	cmcfrm 			; FAR pointers to main parser procs
pcmkeyw	dd	cmkeyw 			; located in code segment code1
pcmtext	dd	cmtext
pcmfil0	dd	cmfil0
pprserr	dd	prserr
pcmexit	dd	cm6
pfvaltoa dd	fvaltoa
data	ends

code1	segment
cspb1	equ	this byte
	db	(256-($-cspb1)) dup (0)	; code1 segment patch buffer
				; end of Patch area
code1	ends

code	segment
	extrn	ctlu:near, cmblnk:near, locate:near, takrd:near, dec2di:near
	extrn	takclos:near, docom:near, prtasz:near, getenv:near
	extrn	getbaud:near, strlen:near, prtscr:near

	assume	cs:code, ds:data, es:nothing

				; Patch area. Must be first in MSK's Code Seg
cspb	equ	this byte
	dw	seg code1
	dw	seg code2
	dw	seg data
	dw	seg data1
	db	(256-($-cspb)) dup (0)	; code segment patch buffer
				; end of Patch area

fctlu	proc	far		; FAR callable versions of items in seg code
	call	ctlu		;  for calling from code segment code1 below
	ret
fctlu	endp
fcmblnk	proc	far
	call	cmblnk
	ret
fcmblnk	endp
fgetbaud proc	far
	call	getbaud
	ret
fgetbaud endp
fgetenv	proc	far
	call	getenv
	ret
fgetenv	endp
flocate	proc	far
	call	locate
	ret
flocate	endp
ftakrd	proc	far
	call	takrd
	ret
ftakrd	endp
fdec2di	proc	far
	call	dec2di
	ret
fdec2di	endp
fstrlen	proc	far
	call	strlen
	ret
fstrlen	endp
ftakclos proc	far
	call	takclos
	ret
ftakclos endp
fprtasz	proc	far
	call	prtasz
	ret
fprtasz	endp
fprtscr	proc	far
	call	prtscr
	ret
fprtscr	endp
fisdev	proc	far
	call	isdev
	ret
fisdev	endp
fiseof	proc	far
	call	iseof
	ret
fiseof	endp
ftolowr	proc	far
	call	tolowr
	ret
ftolowr	endp
nvaltoa	proc	near
	call	dword ptr pfvaltoa
	ret
nvaltoa	endp

;       This routine parses the specified function in AH. Any additional
;       information is in DX and BX.
;       Returns carry clear on success and carry set on failure
 
COMND	PROC NEAR
	mov	cmdstk,sp		; save stack ptr for longjmp exit
	mov	noparse,0	  	; recognize semicolons in Take files
	mov	bracecnt,0		; curly brace counter
	mov	al,taklev		; Take level now
	mov	oldtak,al		; remember past internal takclos call
	cmp	ah,cmeol		; Parse a confirm?
	jne	cm2			; nz = no
	call	dword ptr pcmcfrm	; get a Carriage Return end of line
	ret
cm2: 	cmp	ah,cmkey		; Parse a keyword?
	jne	cm3
	call	dword ptr pcmkeyw	; try and get one
	ret
cm3:	cmp	ah,cmline		; parse line of text
	jne	cm4
	call	dword ptr pcmtext
	ret
cm4:	cmp	ah,cmword		; parse arbitrary word
	jne	cm5
	call	dword ptr pcmfil0
	ret
cm5:	mov	ah,prstr		; else give error
	mov	dx,offset cmer00	; "?Program internal error"
	int	dos
	jmp	dword ptr pprserr	; reparse
					; Control-C exit path (far to near)
cm6:	mov	sp,cmdstk		; restore command entry stack pointer
	stc
	ret				;  and fail immediately (a longjmp)

COMND	ENDP
code	ends

code1	segment
	assume	cs:code1

; This routine parses a keyword from the table pointed at by DX, help text
; point to by BX. Format of the table is as follows (use macro mkeyw):
;	addr:	db	N	  ; Where N is the # of entries in the table
;		dw	M	  ; M is the size of the keyword
;		db	'string'  ; String is the keyword
;		dw	value	  ; Value is data to be returned
; Keywords may be in any order and in mixed case.
; Return is rskp for success and ret for failure.

; cmptab: pointer to keyword table (supplied by caller)
; cmhlp: pointer to help message (supplied by caller)
; cmsptr: pointer to current user word text
; cmsiz: length of user text, excluding terminator
; comand.cmcr: 0 = empty lines not allowed, 1 = empty lines allowed
; comand.cmwhite: non-zero allows leading whitespace for cmline and cmword,
;                 reset automatically at end of call
; cmwptr: buffer write pointer to next free byte
; cmrptr: buffer read pointer for next free byte
; comand.cmper:	0 to do \%x substitution. Set to 0 at end of call
; comand.impdo: non-zero permits keyword failure to retry as DO command, reset
;		 automatically at time of failure.
cmkeyw	proc	far
	mov	cmsiz,0			; user word length
	mov	ax,cmrptr		; get command reading pointer
	mov	cmsptr,ax		; set pointer for start of user word
	mov	cmhlp,bx		; save the help pointer
        mov	cmptab,dx		; save the beginning of keyword table
	mov	bx,dx
	cmp	byte ptr[bx],0		; get number of entries in table
	jne	cmky1
	jmp	cmky7			; e = no keywords to check, error
cmky1:	mov	cmsflg,0ffh		; skip leading spaces/tabs
	call	cmgtch			; get char from the user into ah
	jc	cmky3			; c = terminator
	mov	dx,cmrptr		; next byte to read
	dec	dx			; where we just read a char
	mov	cmsptr,dx		; remember start of keyword
	inc	cmsiz			; start counting user chars
cmky2:	call	cmgtch			; read until terminator
	jc	cmky3			; c = terminator
	inc	cmsiz			; count user chars
	jmp	short cmky2		; no terminator yet

cmky3:	cmp	ah,'?'              	; need help?
	jne	cmky4			; ne = no
	jmp	cmkyhlp			; do help and exit
cmky4:	cmp	ah,escape		; escape?
	jne	cmky6			; ne = no
	call	cmkyesc			; process escape
	jc	cmky5			; c = failure (no unique keyword yet)
	mov	comand.cmper,0		; reset to variable recognition
	mov	comand.cmkeep,0
	mov	comand.impdo,0		; clear flag to prevent loops
	mov	comand.cmquiet,0	; permit echoing again
	clc
	ret				; return successfully to user

cmky5:	cmp	cmsiz,0			; started a word yet?
	je	cmky1			; e = no, ignore escape, keep looking
	jmp	cmkyhlp			; ne = yes, show type of error

cmky6:	cmp	cmsiz,0			; length of user's text, empty?
	je	cmky7			; e = yes, parse error
	push	bx
	mov	bx,cmsptr		; point at first user character
	cmp	byte ptr[bx],':'	; start of a label?
	pop	bx
	jne	cmky6a			; ne = no, return success
	mov	cmsiz,1			; say just one byte
cmky6a:	call	getkw			; get unique kw, point to it with bx
	jc	cmky8			; c = not found
	add	bx,[bx]			; add length of keyword text (CNT)
	add	bx,2			; point at value field
	mov	bx,[bx]			; bx = return value following keyword
	xor	ax,ax
	mov	comand.cmper,al		; reset to variable recognition
	mov	comand.cmkeep,al
	mov	comand.impdo,al		; clear flag to prevent loops
	mov	comand.cmquiet,al	; permit echoing again
	mov	errflag,al
	clc
	ret				; return successfully
					; all other terminators come here
cmky7:	cmp	cmsiz,0			; empty table or empty user's text?
	jne	cmky8			; ne = no
	cmp	comand.cmcr,0		; empty lines allowed?
	jne	cmky10			; ne = yes, do not complain
	push	dx
	mov	ah,prstr
	mov	dx,offset cmer01	; command word expected
	int	dos
	pop	dx
	xor	al,al
	mov	comand.cmquiet,al	; permit echoing again
	mov	comand.impdo,al		; clear flag to prevent loops
	stc				; failure
	ret

cmky8:	cmp	comand.impdo,0		; failed here, ok to try Macro table?
	je	cmky8a			; e = no, use regular exit path
	mov	comand.impdo,0		; yes, but clear flag to prevent loops
	mov	cmrptr,offset cmdbuf	; reinit read pointer
	mov	comand.cmquiet,1	; suppress echoing of same keyword
	mov	bx,offset docom		; return DO as "found" keyword
	clc
	ret				; return success to invoke DO

cmky8a:	mov	errflag,1		; say already doing error recovery
	or	kstatus,ksgen		; global command status, failure
	mov	comand.cmquiet,0	; permit echoing again
	call	fisdev			; reading pretyped lines?
	jnc	cmky9			; nc = yes, consume rest of line
	cmp	taklev,0		; in a Take file?
	jne	cmky9			; ne = yes
	call	cmskw			; display offending keyword
	dec	cmrptr			; interactive, backup to terminator
	mov	bx,cmrptr		; look at it
	cmp	byte ptr [bx],' '	; got here on space terminator?
	jne	cmky10			; ne = no, (cr,lf,ff) exit failure
	mov	ah,prstr		; start a fresh line
	mov	dx,offset crlf
	int	dos
	call	bufreset		; cut back buffer to just before term
	jmp	repars			; reparse interactive lines

cmky9:	call	dword ptr pcmcfrm	; get formal end of command line
					;  to maintain illusion of typeahead
					;  and let user backspace to correct
					;  mistakes (we reparse everything)
	call	cmskw			; display offending keyword
cmky10:	mov	comand.cmquiet,0	; permit echoing again
	stc				; say failure
	ret
cmkeyw	endp

;;;;;; start support routines for keyword parsing.

cmkyesc	proc	near			; deal with escape terminator
	call	bufreset		; reset buffer to end just before ESC
	cmp	cmsiz,0 		; user word length, empty?
	jne	cmkye2			; ne = have user text, else complain
cmkye1:	call	esceoc			; do normal escape end-of-command
	stc				; say failure to fill out word
	ret
					; add unique keyword to buffer
cmkye2:	call	getkw			; is there a matching keyword?
	jc	cmkye1			; c = ambiguous or not found
	push	bx			; unique, bx points to structure
	push	si
	mov	cx,[bx]			; length of keyword
	add	bx,2			; point to first letter
	mov	si,cmwptr		; where next char goes
	mov	dx,cmsiz		; length of user word
	add	bx,dx			; add chars known so far
	sub	cx,dx			; calculate number yet to add
	add	cmsiz,cx
	jcxz	cmkye4			; z = none
	mov	ah,conout		; display new char
cmkye3:	mov	al,[bx]			; get a keyword letter
	inc	bx
	call	ftolowr			; lowercase
	mov	[si],al			; store it
	inc	si
	mov	dl,al
	int	dos			; display it
	loop	cmkye3			; do all new chars
cmkye4:	mov	byte ptr[si],' '	; insert space terminator in buffer
	mov	dl,' '			; display it
	mov	ah,conout
	int	dos
	inc	si
	mov	cmrptr,si		; move token pointer after the space
	mov	cmwptr,si		; next free slot is after the space
	mov	cmsflg,0ffh		; set space-seen flag
	pop	si
	pop	bx			; bx = keyword structure
	add	bx,[bx]			; add length of keyword text
	add	bx,2			; point at value field
	mov	bx,[bx]			; bx = return value following keyword
	clc				; carry clear for success
	ret
cmkyesc	endp

esceoc	proc	near			; do normal escape end-of-command
	push	ax
	push	dx
	mov	ah,conout		; ring the bell
	mov	dl,bell
	int	dos
	pop	dx
	pop	ax
	call	bufreset		; reset buffer
	stc				; say error condition
	ret
esceoc	endp

; Help. Question mark entered by user.  Display all the keywords that match
; user text. If text is null then use external help if available; otherwise,
; display all keywords in the table. Removes question mark from buffer and
; invokes reparse of command line to-date. User word starts at cmsptr and
; is cmsiz bytes long.
cmkyhlp	proc	near
	xor	cx,cx			; clear number of keyword (none yet)
	cmp	cmsiz,0			; user text given?
	jne	cmkyh1			; ne = yes, use matching keywords
	cmp	cmhlp,0			; external help given?
	jne	cmkyh6			; yes, use it instead of full table
cmkyh1:	mov	temp,0			; count # chars printed on this line
	mov	bx,cmptab		; beginning of kw table
	mov	ch,[bx]			; length of table
	xor	cl,cl			; no keywords or help displayed yet
	inc	bx			; point at CNT field
cmkyh2:	cmp	cmsiz,0			; length of user word
	je	cmkyh3			; e = null, use full table
	call	cmpwrd			; compare keyword with user word
	jc	cmkyh5			; c = no match, get another keyword
cmkyh3:	mov	ax,[bx]			; length of table keyword
	add	byte ptr temp,al	; count chars printed so far
	cmp	temp,76			; will this take us beyond column 78?
	jbe	cmkyh4			; be = no, line has more room
	mov	byte ptr temp,al	; reset the count
	mov	ah,prstr
	mov	dx,offset crlf		; break the line
	int	dos
cmkyh4:	or	cl,cl			; any keywords found yet?
	jnz	cmkyh4a			; nz = yes
	mov	dx,offset cmin01	; start with One of the following: msg
	mov	ah,prstr
	int	dos
	inc	cl			; say one keyword has been found
cmkyh4a:mov	dl,spc			; put two spaces before each keyword
	mov	ah,conout
	int	dos
	int	dos
	add	temp,2			; count output chars
	mov	di,bx			; get current keyword structure
	add	di,2			; text part
	push	cx
	mov	cx,[bx]			; string length to cx, offset to di
	call	fprtscr			; display counted string
	pop	cx
cmkyh5:	dec	ch			; are we at end of table?
	jle	cmkyh7			; le = yes, quit now
	add	bx,[bx]			; next keyword, add CNT chars to bx
	add	bx,4			; skip CNT and 16 bit value
	jmp	cmkyh2			; go examine this keyword

cmkyh6:	mov	dx,cmhlp		; external help text
	mov	ah,prstr
	int	dos
	inc	cl			; say gave help already
cmkyh7:	or	cl,cl			; found any keywords?
	jnz	cmkyh9			; nz = yes
	mov	cx,cmsiz		; length of word
	or	cx,cx
	jg	cmkyh8			; g = something to show
	push	dx
	mov	ah,prstr
	mov	dx,offset cmer01	; command word expected
	int	dos
	pop	dx
	jmp	prserr
cmkyh8:	mov	kwstat,0		; set keyword not-found status
	call	cmskw			; display offending keyword
cmkyh9:	mov	ah,prstr		; start a fresh line
	mov	dx,offset crlf
	int	dos
	call	bufreset		; cut back buffer to just before '?'
        jmp	repars
cmkyhlp	endp

; See if keyword is ambiguous or not from what the user has typed in.
; Return carry set if word is ambiguous or not found, carry clear otherwise.
; Uses table pointed at by cmptab, user text pointed at by cmsptr and length
; in cmsiz.
cmambg	proc	near
	push	bx
	push	cx
	push	dx
	xor	dl,dl			; count keyword matches so far
	mov	bx,cmptab		; look at start of keyword table
	mov	cl,[bx]			; get number of entries in table
	xor	ch,ch			; use cx as a counter
	jcxz	cmamb8			; z = no table so always ambiguous
	inc	bx			; look at CNT byte of keyword
cmamb4:	call	cmpwrd			; user vs table words, same?
	jc	cmamb6			; c = no match
	inc	dl			; count this as a match
	cmp	dl,1			; more than one match?
	ja	cmamb8			; a = yes, quit now
cmamb6:	add	bx,[bx]			; add CNT chars to bx
	add	bx,4			; skip CNT and 16 bit value
	loop	cmamb4			; do rest of keyword table
	cmp	dl,1			; how many matches were found?
	jne	cmamb8			; ne = none or more than 1: ambiguous
	pop	dx			; restore main registers
	pop	cx
	pop	bx
	clc
	ret				; ret = not ambiguous
cmamb8:	pop	dx			; restore main registers
	pop	cx
	pop	bx
	stc
	ret				; return ambiguous or not found
cmambg	endp

; Compare user text with keyword, abbreviations are considered a match.
; Enter with bx pointing at keyword table CNT field for a keyword.
; Return carry clear if they match, set if they do not match. User text
; pointed at by cmsptr and length is in cmsiz.
; Registers preserved.

cmpwrd	proc	near
	push	cx
	mov	cx,cmsiz		; length of user's text
	jcxz	cmpwrd2			; z: null user word matches no keyword
	cmp	cx,[bx]			; user's text longer than keyword?
	ja	cmpwrd2			; a = yes, no match
	push	ax
	push	bx
	push	si
	add	bx,2		    	; point at table's keyword text
	mov	si,cmsptr		; buffer ptr to user input
	cld
cmpwrd1:lodsb				; user text
	mov	ah,[bx]			; keyword text
	inc	bx			; next keyword letter
	call	ftolowr			; force lower case on both chars
	cmp	ah,al			; same?
	loope	cmpwrd1			; e = same so far
	pop	si
	pop	bx
	pop	ax
	jne	cmpwrd2			; ne = mismatch
	pop	cx			; recover keyword counter
	clc				; they match
	ret
cmpwrd2:pop	cx			; recover keyword counter
	stc				; they do not match
	ret
cmpwrd	endp

; Get pointer to keyword structure using user text. Uses keyword table
; pointed at by cmptab and cmsiz holding length of user's keyword (cmpwrd
; needs comand.cmsptr pointing at user's keyword and length of cmsiz).
; Structure pointer returned in BX.
; Return carry clear for success and carry set for failure. Modifies BX.
getkw	proc	near
	push	cx
	mov	kwstat,0		; keyword status, set to not-found
	cmp	cmsiz,0			; length of user word, empty?
	je	getkw3			; e = yes, fail
	mov	bx,cmptab		; table of keywords
	mov	cl,[bx]			; number of keywords in table
	xor	ch,ch
	jcxz	getkw3			; z = none, fail
	inc	bx			; point to first
getkw1:	call	cmpwrd			; compare user vs table words
	jc	getkw2			; c = failed to match word, try next
	mov	kwstat,1		; say found one keyword, maybe more
	push	dx
	mov	dx,cmsiz		; users word length
	cmp	[bx],dx			; same length (end of keyword)?
	pop	dx
	je	getkw4			; e = yes, exact match. Done
	call	cmambg			; ambiguous?
	jnc	getkw4			; nc = unique, done, return with bx
	mov	kwstat,2		; say more than one such keyword
getkw2:	add	bx,[bx]			; next keyword, add CNT chars to bx
	add	bx,4			; skip CNT and 16 bit value
	loop	getkw1			; do all, exhaustion = failure
getkw3:	pop	cx
	stc				; return failure
	ret
getkw4:	pop	cx
	clc				; return success
	ret
getkw	endp

; show offending keyword message. Cmsptr points to user word,
; cmsiz has length. Modifies AX, CX, and DX.
cmskw	proc	near
	cmp	comand.cmquiet,0	; Quiet mode?
	je	cmskw0			; e = no, regular mode
	ret				; else say nothing
cmskw0:	mov	ah,prstr		; not one of the above terminators
	mov	dx,offset cmer02	; '?Word "'
	int	dos
	mov	ah,conout
	mov	cx,cmsiz		; length of word
	jcxz	cmskw3			; z = null
	mov	ah,conout
	push	si
	mov	si,cmsptr		; point to word
	cld
cmskw1:	lodsb
	cmp	al,' '			; control code?
	jae	cmskw2			; ae = no
	push	ax
	mov	dl,5eh			; caret
	int	dos
	pop	ax
	add	al,'A'-1		; plus ascii bias
cmskw2:	mov	dl,al			; display chars in word
	int	dos
	loop	cmskw1
	pop	si
cmskw3:	mov	dx,offset cmer03	; '" not usable here.'
	cmp	kwstat,1		; kywd status from getkw, not found?
	jb	cmskw4			; b = not found, a = ambiguous
	mov	dx,offset cmer04	; '" ambiguous'
cmskw4:	mov	ah,prstr
        int	dos
	ret
cmskw	endp
;;;;;;;;;; end of support routines for keyword parsing.

; Parse	arbitrary text up to a CR. Enter with BX = pointer to output buffer,
; DX pointing to help text. Produces asciiz string. Return updated pointer in
; BX and output size in AX. Leading spaces are omitted unless comand.cmwhite
; is non-zero (cleared upon exit). It does not need to be followed by the
; usual call to confirm the line. Byte comand.cmblen can be used to specify
; the length of the caller's buffer; cleared to zero by this command to
; imply a length of 127 bytes (default) and if zero at startup use 127 bytes.
cmtext	proc	far
	mov	cmptab,bx		; save pointer to data buffer
	mov	cmhlp,dx		; save the help message
	xor	cx,cx			; init the char count
	cmp	comand.cmblen,0		; length of user's buffer given?
	jne	cmtxt0			; ne = yes
	mov	comand.cmblen,127	; else set 127 byte limit plus null
cmtxt0:	mov	cmsflg,0ffh		; skip initial spaces
	cmp	comand.cmwhite,0	; allow leading whitespace?
	je	cmtxt1a			; e = no
cmtxt1:	mov	cmsflg,0		; get all spaces
cmtxt1a:call	cmgtch			; get a char
	jnc	cmtxt5			; nc = non-terminator, put in buffer
	cmp	ah,' '			; space?
	je	cmtxt5			; e = yes, record it
	cmp	ah,escape		; escape?
	jne	cmtxt2			; ne = no
	call	esceoc			; do normal escape end-of-command
	jmp	short cmtxt0		; try again

cmtxt2:	cmp	ah,'?'			; asking a question?
	je	cmtxt3			; e = yes
	cmp	ah,cr			; formal carriage return?
	je	cmtxt2a			; e = yes
	inc	cmrptr			; accept char into buffer
	jmp	short cmtxt5		;  and record the char
cmtxt2a:mov	bx,cmptab		; return updated pointer
	xor	ax,ax
	mov	byte ptr[bx],al		; put terminator into the buffer
	mov	comand.cmwhite,al	; clear leading whitespace flag
	mov	comand.cmper,al		; reset to variable recognition
	mov	comand.cmblen,ax	; set user buffer length to unknown
	mov	comand.cmkeep,al
	mov	ax,cx			; return count in AX
	call	rprompt			; restore master prompt level
	clc
	ret
					; Help processor
cmtxt3:	inc	cmrptr			; count the ?
	or	cx,cx			; is "?" the first char?
	jnz	cmtxt5			; nz = no, just add to buffer
	dec	cmrptr
	mov	cmsiz,0			; no keyword for help
	mov	comand.cmwhite,0	; clear leading whitespace flag
	cmp	cmhlp,0			; external help given?
	jne	cmtxt3a			; ne = yes
	mov	cmhlp,offset cmin00	; confirm with c/r msg
	mov	comand.cmblen,0		; set user buf length to unknown
cmtxt3a:jmp	cmkyhlp			; do help process

cmtxt5:	inc	cx			; increment the count
	mov	bx,cmptab		; pointer into destination array
	mov	[bx],ah			; put char into the buffer
	inc	bx
	xor	al,al
	mov	[bx],al			; insert null terminator
	mov	cmptab,bx
	cmp	cx,comand.cmblen	; buffer filled?
	ja	cmtxt6			; a = yes, declare error
	jb	cmtxt5a			; a = not filled yet
	mov	ah,conout		; notify user that the buffer is full
	mov	dl,bell
	int	dos
cmtxt5a:jmp	cmtxt1
cmtxt6:	mov	ah,prstr
	mov	dx,offset cmer09
	int	dos
	jmp	prserr			; declare parse error
cmtext	endp


; Parse arbitrary text up to whitespace.  Enter with DX pointing to output
; buffer and BX pointing to help text. Produces asciiz string. Return updated
; pointer in DX and input size in AH. Skips leading whitespace unless 
; comand.cmwhite is non-zero (cleared upon exit). Does a return skip exit.

cmfil0	proc	far
	mov	cmptab,dx		; save pointer to data buffer
	mov	cmhlp,bx		; save the help message
	mov	cmsiz,0			; init the char count
	cmp	comand.cmblen,0		; length of user's buffer given?
	jne	cmfil0a			; ne = yes
	mov	comand.cmblen,127	; else set 127 byte limit plus null
cmfil0a:cmp	comand.cmwhite,0	; allow leading whitespace?
	jne	cmfil1			; ne = yes
	mov	cmsflg,0ffh		; omit leading space
cmfil1:	call	cmgtch			; get a char
	jc	cmfi1a			; c = terminator 
	jmp	short cmfil5		; put char into the buffer
cmfi1a:	cmp	ah,escape		; escape?
	je	cmfi1b			; e = yes
	jmp	short cmfil2		; process other terminators
cmfi1b:	call	esceoc			; do normal escape end-of-command
	jmp	short cmfil0a		; try again

cmfil2:	cmp	ah,'?'			; asking a question?
	je	cmfil3			; e = yes
	xchg	dx,bx			; re-interchange bx and dx
	xor	ax,ax
	mov	comand.cmwhite,al	; clear whitespace flag
	mov	comand.cmper,al		; reset to variable recognition
	mov	comand.cmkeep,al	; do not keep Take file open
	mov	comand.cmblen,ax	; set user buffer length to unknown
	mov	bx,cmptab		; pointer into destination array
	mov	byte ptr[bx],al		; put null terminator into the buffer
	inc	bx
	mov	ax,cmsiz		; return count in AX
	mov	dx,cmptab		; return updated pointer
	xchg	dx,bx			; re-interchange bx and dx
	call	rprompt			; restore master prompt level
	clc
	ret				; return success

cmfil3:	inc	cmrptr			; count the ?
	cmp	cmsiz,0			; Is "?" first char?
	jne	cmfil5			; ne = no, just add to buffer
	dec	cmrptr
	mov	cmsiz,0
	cmp	cmhlp,0			; external help given?
	jne	cmfil3a			; ne = yes
	mov	cmhlp,offset cmin00	; confirm with c/r msg
cmfil3a:jmp	cmkyhlp			; do help process

cmfil5:	inc	cmsiz			; increment the count
	mov	bx,cmptab		; pointer into destination array
	mov	[bx],ah			; put char into the buffer
	inc	bx
	mov	cmptab,bx
	mov	cx,cmsiz		; length of command so far
	cmp	cx,comand.cmblen	; buffer filled?
	ja	cmfil7			; a = yes, declare error
	jb	cmfil6			; a = not filled yet
	mov	ah,conout		; notify user that the buffer is full
	mov	dl,bell
	int	dos
cmfil6:	jmp	cmfil1

cmfil7:	mov	ah,prstr
	mov	dx,offset cmer09
	int	dos
	jmp	prserr			; declare parse error
cmfil0	endp

; This routine gets a confirm (CR) and displays any extra non-blank text.
; errflag non-zero means suppress "extra text" display in this routine
; because another routine is handling errors.
cmcfrm	proc	far
	mov	comand.cmper,1		; do not react to \%x substitutions
cmcfr1:	mov	cmsflg,0ffh		; set space-seen flag (skip spaces)
	call	cmgtch			; get a char
	push	cmrptr
	pop	temp			; remember first non-space position
	jc	cmcfr4			; c = terminator
	dec	temp			; backup to text char
cmcfr3:	mov	cmsflg,0ffh		; set space-seen flag (skip spaces)
	call	cmgtch
	jnc	cmcfr3			; read until terminator
cmcfr4:	cmp	ah,' '
	je	cmcfr3			; ignore ending on space
	cmp	ah,escape		; escape?
	jne	cmcfr5			; ne = no
	call	esceoc			; do standard end of cmd on escape
	mov	ax,cmrptr
	cmp	ax,temp			; started text yet?
	je	cmcfr1			; e = no
	jmp	short cmcfr3		; try again
cmcfr5: cmp	ah,'?'			; curious?
        jne	cmcfr6			; ne = no
	mov	cmhlp,offset cmin00	; msg Confirm with c/r
	mov	cmsiz,0			; no keyword
	mov	errflag,0
	jmp	cmkyhlp			; do help
cmcfr6:	cmp	ah,cr			; the confirmation char?
	jne	cmcfr3			; ne = no
	cmp	errflag,0		; already doing one error?
	jne	cmcfr7			; ne = yes, skip this one
	mov	cx,cmrptr		; pointer to terminator
	mov	dx,temp			; starting place
	sub	cx,dx			; end minus starting point = length
	jle	cmcfr7			; le = nothing to display
	push	dx			; save source pointer
	mov	ah,prstr
	mov	dx,offset cmer07	; ?Ignoring extras
	int	dos
	pop	dx
	mov	bx,1			; stdout handle, cx=count, dx=src ptr
	mov	ah,write2		; allow embedded dollar signs
	int	dos
	mov	ah,prstr
	mov	dx,offset cmer08	; trailer msg
	int	dos
cmcfr7:	mov	errflag,0
	mov	comand.cmper,0		; reset to variable recognition
	mov	comand.cmkeep,0
	call	rprompt			; restore master prompt level
	clc				; return confirmed
	ret
cmcfrm	endp

;;; Routines to get and edit incoming text.

; Detect '\%x' (x = '0' or above) and substitute the matching Macro string
; in place of the '\%x' phrase in the user's buffer. If comand.cmper != 0
; then treat '\%' as literal characters. If no matching parameter exists
; just remove '\%x'. Ditto for \v(variable). Returns carry clear if nothing
; done, else carry set and new text already placed in user's buffer.
; Includes \v(variable) and \$(Environment variable) and \m(macro name).
; Uses depth-first recursion algorithm. All registers preserved.
subst	proc	near
	cmp	comand.cmper,0		; recognize '\%','\v(','\$(','\m(' ?
	jne	subst2			; ne = no, treat as literals
	cmp	ah,'\'			; is it the first char of the pattern?
	jne	subst1			; ne = no, try next
	mov	subcnt,1		; say first is matched
	mov	subtype,0
	jmp	short subst2		; exit successfully
subst1:	cmp	subcnt,1		; first char matched already?
	ja	subst3			; a = first two have been matched
	jb	subst2			; b = none yet
	inc	subcnt			; assume a match follows
	mov	subtype,ah		; remember kind of substitution
	or	subtype,20h		; convert to lower case
	cmp	ah,'%'			; second match char, same?
	je	subst2			; e = yes
	cmp	subtype,'v'		; \v(...)?
	je	subst2			; e = yes
	cmp	subtype,'$'		; \$(..)?
	je	subst2
	cmp	subtype,'m'		; \m(..)?
	je	subst2
subst1a:mov	subcnt,0		; mismatch, clear match counter
	mov	subtype,0		; clear substitution kind
subst2:	clc				; carry clear = no substitution done
	ret
subst3:	cmp	subtype,'v'		; doing \v(..)?
	je	subst3a			; e = yes
	cmp	subtype,'$'		; doing \$(..)?
	je	subst3a			; e = yes
	cmp	subtype,'m'		; doing \m(..)?
	jne	subst3b			; ne = no, do \%<char>
subst3a:cmp	ah,'('			; have leading parenthesis?
	jne	subst1a			; ne = no, mismatch, exit
	jmp	subst10			; process \v(..), \$(..), \m(..)
subst3b:mov	subcnt,0		; do \%<char>, clear match counter
	cmp	ah,'0'			; third char is '0' or above?
	jb	subst1a			; b = out of range, no match
	push	bx			; save working regs
	push	cx
	push	es
	sub	cmrptr,3		; reread commands where backslash was
	call	bufreset		; reset buffer to this point
	push	cmptab			; save current keyword parsing parms
	push	cmsptr
	push	cmsiz
	mov	bx,cmrptr		; points at backslash
	mov	cmsptr,bx		; direct keyword routine to it
	mov	cmsiz,3			; three bytes (\%x) of user text
	mov	cmptab,offset mcctab 	; use Macro table for new text
	call	getkw			; get ptr, bx, to matching keyword
	pop	cmsiz			; restore borrowed keyword parameters
	pop	cmsptr
	pop	cmptab
	jc	substx			; c = not found, keep after pops
	cmp	taklev,maxtak		; room in take level?
	jae	subst6			; ae = no
	mov	cx,[bx]			; length of found word
	add	cx,2			; plus count field
	add	bx,cx			; point to 16 bit value (string ptr)
	mov	es,[bx]			; point to string structure segment
	xor	bx,bx
	mov	cx,es:[bx]		; length of string
	add	takadr,size takinfo	; pointer to new Take structure
	inc	taklev
	mov	bx,takadr		; pointer to new Take structure
	mov	[bx].takbuf,es		; segment of Take buffer
	mov	[bx].takcnt,cx		; number of unread bytes
	mov	[bx].taktyp,0fdh	; flag as an internal macro
	mov	[bx].takptr,2		; init pointer to definition itself
substx:	pop	es
	pop	cx
	pop	bx
	stc				; set carry to say have stored chars
	ret

subst6:	mov	ah,prstr
	mov	dx,offset stkmsg	; out of work space msg
	int	dos
	jmp	prserr			; and declare parse error

					; \m(..), \v(..), \$(..)
subst10:push	bx			; save working regs
	push	cx
	push	es
	push	cmptab			; save current keyword parsing parms
	push	cmsptr
	push	cmsiz
	push	cmhlp
	push	dx
	mov	subcnt,0		; clear match counter
	mov	cmhlp,0
	mov	ax,cmrptr
	mov	valtmp,ax		; remember current read pointer
	mov	cmsptr,ax		; start of word
	mov	cmptab,offset valtab	; table of keywords for \v(..)
	cmp	subtype,'v'		; \v(..)?
	je	subst10a		; e = yes
	mov	cmptab,offset envtab	; Environment variable table \$(..)
	cmp	subtype,'$'		; \$(..)?
	je	subst10a		; e = yes
	mov	cmptab,offset mcctab	; main Macro table for \m(..)
subst10a:mov	cmsflg,0		; see leading spaces/tabs
	mov	cmsiz,0			; word size
subst11:call	cmgtch			; read a character into ah
	jnc	subst13			; nc = non-terminator
	cmp	ah,'?'			; need help?
	jne	subst11a		; ne = no
	jmp	cmkyhlp			; do help and exit
subst11a:cmp	ah,escape		; escape?
	jne	subst12			; ne = no
	mov	bx,cmsptr		; where word started
	mov	cmrptr,bx		; omit word to date
	mov	cmwptr,bx
	call	cmkyesc			; process escape
	mov	dx,1			; signal valtoa to add trailing space
	jnc	subst14			; nc = success (found the keyword)
					;
subst12:mov	bx,cmsptr		; where word started
	sub	bx,3			; back over "\v(" pr "\$(" part
	mov	cmrptr,bx		; omit "\v(..." or "\$(..." to date
	mov	cmwptr,bx		;  and "\m(..." too
	jmp	repars			; reparse command without \v(...
					;
subst13:inc	cmsiz			; count user chars
	cmp	ah,')'			; end bracket?
	jne	subst11			; ne = no, keep looking
	dec	cmsiz			; omit user's ')' from tests
	cmp	subtype,'$'		; \$(..)?
	je	subst13a		; e = yes, no keyword in table
	call	getkw			; \m(..) and \v(..) test for keyword
	jc	subst12			; c = failure
	jmp	short subst13b		; success

subst13a:call	envvar			; search Environment for the word
	jc	subst12			; c = failure
	mov	bx,offset envtab+1	; Environment data structure

subst13b:mov	ax,valtmp		; where word started
	mov	cmrptr,ax
	sub	ax,3			; backup to "\v(" or "\$("
	mov	cmrptr,ax		; write output where backslash was
	mov	cmwptr,ax
	mov	cx,[bx]			; bx = structure pointer, cx=keyw len
	add	cx,2			; skip count byte
	add	bx,cx			; point at 16 bit value field
	mov	bx,[bx]			; get value to bx for valtoa
	xor	dx,dx			; signal valtoa to not add trailing sp
subst14:
	cmp	taklev,maxtak		; room in take level?
	jb	subst15			; b = yes
	mov	dx,offset stkmsg	; out of work space msg
	mov	ah,prstr		; display error message
	int	dos
	jmp	short subst17
subst15:push	di
	call	valtoa			; make text be an internal macro
	jc	subst16			; c = failed
	add	takadr,size takinfo	; pointer to new Take structure
	inc	taklev			; next Take level
	push	bx
	mov	bx,takadr		; address of take structure
	mov	ax,ds
	mov	[bx].takbuf,ax		; segment of Take buffer
	mov	[bx].takptr,offset valbuf+2 ; offset of beginning of def text
	mov	[bx].takcnt,di		; # of chars in buffer
	mov	[bx].taktyp,0fdh	; flag as an internal macro
	pop	bx
subst16:pop	di
subst17:pop	dx
	pop	cmhlp
	pop	cmsiz			; restore borrowed keyword parameters
	pop	cmsptr
	pop	cmptab
	jmp	repars			; reparse cmd line with new material
subst	endp

; Make an internal macro defined as the text for one of the value variables.
; Use incoming DX as trailing space suppression flag, if null.
valtoa	proc	near
	push	dx			; save trailing space flag
	mov	di,offset valbuf+2	; start text here
	mov	word ptr [di],0		; fill buffer with sweet nothings
					; BX has index of variable
	cmp	bx,0			; Environment?
	jne	valtoa1			; ne = no
	mov	cx,envlen		; string length
	jcxz	valtoa0			; z = empty
	push	es
	push	ds
	mov	ax,ds
	mov	es,ax			; destination is es:di
	lds	si,envadr		; ds:si is source from Environment
	cld
	rep	movsb			; copy string
	pop	ds			; recover ds
	pop	es
valtoa0:jmp	valtoa30
	
valtoa1:cmp	bx,1			; ARGC?
	jne	valtoa2			; ne = no
	call	wrtargc			; write argc
	jmp	valtoa30
valtoa2:cmp	bx,2			; COUNT?
	jne	valtoa3			; ne = no
	call	wrtcnt			; write it
	jmp	valtoa30
valtoa3:cmp	bx,3			; DATE?
	jne	valtoa4
	call	wrtdate
	jmp	valtoa30
valtoa4:cmp	bx,4			; ERRORLEVEL?
	jne	valtoa5			; ne = no
	call	wrterr
	jmp	valtoa30
valtoa5:cmp	bx,5			; DIR?
	jne	valtoa6
	call	wrtdir
	jmp	valtoa30
valtoa6:cmp	bx,6			; TIME?
	jne	valtoa7
	call	wrttime
	jmp	valtoa30
valtoa7:cmp	bx,7			; VERSION?
	jne	valtoa8			; ne = no
	mov	ax,version		; get version such as 300
	call	fdec2di			; convert binary to asciiz
	mov	word ptr [di],0020h	; space, null
	inc	di
	jmp	valtoa30
valtoa8:cmp	bx,8			; PLATFORM?
	jne	valtoa9			; ne = no
	call	wrtplat			; get machine name, e.g. "IBM-PC"
	jmp	valtoa30
valtoa9:cmp	bx,9			; SYSTEM?
	jne	valtoa10		; ne = no
	call	wrtsystem		; get "MS-DOS" string
	jmp	valtoa30
valtoa10:cmp	bx,10			; KEYBOARD?
	jne	valtoa11		; ne = no
	call	wrtkbd			; 88 or 101 value
	jmp	valtoa30
valtoa11:cmp	bx,11			; SPEED?
	jne	valtoa12		; ne = no
	push	di
	call	fgetbaud		; read baud rate from hardware
	pop	di
	mov	bx,portval
	mov	ax,[bx].baud
	cmp	al,byte ptr bdtab	; index versus number of table entries
	jb	valtoa11a		; b = index is in the table
	mov	si,offset cmunk-2	; unrecognized value, say "unknown"
	mov	bx,7			; length of string
	jmp	short valtoa11c
valtoa11a:mov	si,offset bdtab		; ascii rate table
	mov	cl,[si]			; number of entries
	inc	si			; point to an entry
valtoa11b:
	mov	bx,[si]			; length of text string
	cmp	ax,[si+bx+2]		; our index vs table entry index
	je	valtoa11c		; e = match
	add	si,bx			; skip text
	add	si,4			; skip count and index word
	loop	valtoa11b		; look again
	mov	si,offset cmunk-2	; unrecognized value, say "unknown"
	mov	bx,7			; length of string
valtoa11c:mov	cx,bx			; length of string
	add	si,2			; point at string
	rep	movsb			; copy string
	jmp	valtoa30

valtoa12:cmp	bx,12			; PROGRAM?
	jne	valtoa13		; ne = no
	call	wrtprog			; get "MS-DOS_KERMIT" string
	jmp	valtoa30
valtoa13:cmp	bx,13			; STATUS?
	jne	valtoa14		; ne = no
	call	wrtstat			; compose status string
	jmp	valtoa30
valtoa14:cmp	bx,14			; NDATE?
	jne	valtoa15		; ne = no
	call	wrtndate
	jmp	valtoa30
valtoa15:cmp	bx,15			; LINE, PORT?
	jne	valtoa16		; ne = no
	call	wrtport
	jmp	valtoa30
valtoa16:cmp	bx,16			; TERMINAL?
	jne	valtoa17		; ne = no
	call	wrtterm
	jmp	valtoa30
valtoa17:cmp	bx,17			; SESSION (internal Telnet)?
	jne	valtoa18		; ne = no
	mov	ax,sescur		; get internal Telnet session ident
	inc	ax			; count from 1 for users (0 == none)
	call	fdec2di			; convert binary to asciiz
	mov	word ptr [di],0020h	; space, null
	inc	di
	jmp	valtoa30
valtoa18:cmp	bx,18			; \v(ntime) (seconds in day)?
	jne	valtoa19		; ne = no
	mov	ah,gettim		; get DOS time of day
	int	dos			; ch=hh, cl=mm, dh=ss, dl=0.01 sec
	mov	bx,60
	mov	al,ch			; hours
	mul	bl			; to minutes
	xor	ch,ch
	add	ax,cx			; plus minutes
	mov	cl,dh			; preserve seconds
	mul	bx			; need carry out to DX
	add	ax,cx			; add seconds
	adc	dx,0
	mov	cx,10			; down to 16 bit size
	div	cx
	call	fdec2di			; convert binary to asciiz
	xchg	ax,dx
	call	fdec2di			; convert binary to asciiz
	mov	word ptr [di],0020h	; space, null
	inc	di
	jmp	short valtoa30
valtoa19:cmp	bx,19			; \v(dosversion)?
	jne	valtoa29		; ne = no
	mov	ax,dosnum		; DOS verson, major high, minor low
	push	ax
	xchg	ah,al
	xor	ah,ah
	call	fdec2di			; write major
	pop	ax
	xor	ah,ah
	cmp	al,10			; less than 10?
	ja	valtoa19a		; a = no
	mov	byte ptr [di],'0'	; use two digits for minor
	inc	di
valtoa19a:call	fdec2di			; write minor
	mov	word ptr [di],0020h	; space, null
	inc	di
	jmp	short valtoa30
valtoa29:pop	dx			; assume an internal Macro
	add	takadr,size takinfo	; pointer to new Take structure
	inc	taklev			; next Take level
	push	bx
	mov	ax,bx			; save seg of string here
	mov	bx,takadr		; address of take structure
	mov	[bx].takbuf,ax		; segment of Take buffer
	mov	[bx].takptr,2 		; offset of beginning of def text
	push	es
	mov	es,ax			; segment of string definition
	mov	di,es:[0]		; get length of string text (count)
	pop	es
	mov	[bx].takcnt,di		; # of chars in buffer
	mov	[bx].taktyp,0fdh	; flag as an internal macro
	pop	bx
	jmp	short valtoax		; exit early with CARRY SET(!)

valtoa30:pop	dx			; trailing space flag
	or	dx,dx			; leave the spaces?
	jnz	valtoa31		; nz = yes
	cmp	word ptr [di-1],0020h	; trailing space?
	jne	valtoa31		; ne = no
	dec	di			; remove space
valtoa31:sub	di,offset valbuf+2	; di = length of the buffer contents
	clc
	ret
valtoax:stc
	ret
valtoa	endp

; Far callable version
fvaltoa	proc	far
	call	valtoa
	ret
fvaltoa	endp
; Set envadr to the string following the <variable=> keyword in the
; Environment and set envlen to its length after removing leading and
; trailing whitespace.
; <variable> starts at ds:<valtmp>, of length cmsiz-1, and can be mixed case.
; Return carry set if can't find the <variable=> line in the Environment.
envvar	proc	near
	push	es
	mov	bx,valtmp		; start of variable name
	mov	cx,cmsiz		; length of variable name, w/o ')'
	or	cx,cx			; empty?
	jle	envvar3			; le = nothing to look for, fail
	push	bx
	push	cx
envvar1:mov	al,byte ptr [bx]	; scan variable name in our buffer
	cmp	al,'a'			; lower case
	jb	envvar2			; b = no
	cmp	al,'z'			; still in lower case range?
	ja	envvar2			; a = no
	and	al,not 20h		; convert to DOS's upper case
	mov	byte ptr [bx],al	; replace char
envvar2:inc	bx
	loop	envvar1
	pop	cx
	pop	bx			; find "<variable>=" in Environment
	call	fgetenv			; dx = offset in Environment of "="
	jnc	envvar4			; nc = success
envvar3:pop	es			; no such variable
	stc				; c = failure
	ret
; dx has offset in Environment of char "="
; ds:valtmp is start of variable name, cmsiz is length + 1 of the name.
; Return seg:offset and length variables so we can copy from there in valtoa
envvar4:push	di
	push	si
	xor	ax,ax
	mov	word ptr envadr,ax	; offset in env of variable's string
	mov	word ptr envadr+2,ax	; seg of same
	mov	envlen,ax		; length of string
	mov	es,psp			; our Prog Seg Prefix segment
	mov	ax,es:word ptr[env]	; pick up Environment address
	mov	es,ax			; set es: to Environment segment
	mov	di,dx			; line scan pointer
	cmp	byte ptr es:[di],'='	; did we stop on this?
	jne	envvar5			; ne = no
	inc	di			; skip the "=" char
envvar5:mov	al,es:[di]		; scan over leading white space
	inc	di
	or	al,al			; end of line terminator?
	jz	envvarf			; z = yes, fail
	cmp	al,TAB			; HT?
	jne	envvar6			; ne = no
	mov	al,' '			; HT becomes a space
envvar6:cmp	al,' '			; white space?
	je	envvar5			; scan off white space
	dec	di			; backup to non-white char
	mov	word ptr envadr,di	; offset of string in Environment
	mov	word ptr envadr+2,es	; seg of string in Environment
	mov	si,di			; remember starting offset here
					; remove trailing spaces from string
	xor	al,al			; a null
	mov	cx,127			; max length to search
	cld
	repne	scasb			; skip over non-nulls
	dec	di			; backup to null
	dec	di			; backup to last string char
	mov	cx,di			; ending offset
	inc	cx			; count the char
	sub	cx,si			; minus starting offset yields length
	jcxz	envvar9			; z = empty string
envvar7:mov	al,es:[di]		; last char
	dec	di			; backup one char
	cmp	al,' '			; space?
	je	envvar8			; e = yes
	cmp	al,TAB			; HT?
	jne	envvar9			; ne = no, end of white space
envvar8:loop	envvar7			; keep looking
envvar9:mov	envlen,cx		; store the length
	clc				; say success
	jmp	short envvarx
envvarf:stc				; say failure
envvarx:pop	si
	pop	di
	pop	es
	ret
envvar	endp

; Read chars from Take file, keyboard, or redirected stdin. Edit and remove
; BS & DEL, Tab becomes space, act on Control-C, pass Control-U and Control-W.
; Do echoing unless comand.cmquiet is non-zero. Do semicolon comments in Take
; and indirect stdin files (\; means literal semicolon). Return char in AL.
CMGETC	proc	near			; Basic raw character reader
cmget01:cmp	prevch,0		; left over char yet to be exported?
	je	cmget02			; e = no
	mov	al,prevch		; get old char
	mov	prevch,0		; clear storage
	jmp	cmget6			; analyze it
cmget02:cmp	taklev,0		; in a Take file?
	jne	cmget1			; ne = yes, do Take reader section
	call	fisdev			; is stdin a device or a file?
	jnc	cmget20			; nc = file (redirection of stdin)
	jmp	cmget10			; c = device, do separately

cmget20:call	fiseof			; see if file is empty
	jc	cmget21			; c = EOF on disk file
	mov	ah,coninq		; read the char from file, not device
	int	dos
	cmp	al,cr			; is it a cr?
	je	cmget01			; yes, ignore and read next char
	cmp	al,ctlz			; Control-Z?
	je	cmget21			; e = yes, same as EOF here
	cmp	al,lf			; LF's end lines from disk files
	jne	cmget8			; ne = not LF, pass along as is
	mov	al,cr			; make LF a CR for this parser
	call	fiseof			; see if this is the last char in file
	jnc	cmget8			; nc = not EOF, process new CR
cmget21:mov	flags.extflg,1		; EOF on disk file, set exit flag
	jmp	short cmget6		; do echoing and return

cmget1:	push	bx			; read from Take file
	mov	bx,takadr		; offset of this Take structure
	cmp	[bx].takcnt,0		; bytes remaining in Take buffer
	jne	cmget4			; ne = not empty
	cmp	[bx].taktyp,0feh	; type of Take (file?)
	jne	cmget3			; ne = no (macro)
	call	ftakrd			; read another buffer
	cmp	[bx].takcnt,0		; anything in the buffer?
	jne	cmget4			; ne = yes
cmget3:	pop	bx			; clear stack
	jmp	short cmget5		; close take file

cmget4:	push	si
	push	es
	mov	es,[bx].takbuf		; segment of Take buffer
	mov	si,[bx].takptr		; current offset in Take buffer
	mov	al,es:[si]		; read a char from Take buffer
	inc	si
	mov	[bx].takptr,si		; move buffer pointer
	dec	[bx].takcnt		; decrease number of bytes remaining
	pop	es
	pop	si
	pop	bx
	cmp	al,ctlz			; Control-Z?
	jne	cmget6			; ne = no
cmget5:	push	bx
	mov	bx,takadr		; offset of this Take structure
	mov	ah,[bx].taktyp		; save kind of Take/macro
	pop	bx
	cmp	ah,0fdh			; internal macro?
	je	cmget5b			; e = yes, close but no auto-CR
	cmp	comand.cmkeep,0		; keep Take/macro open after eof?
	jne	cmget5a			; ne = yes
cmget5b:push	ax
	call	ftakclos		; close take file
	pop	ax
	cmp	ah,0fdh			; internal macro?
	jne	cmget5a			; ne = no, file or regular macro
	jmp	cmgetc			; internal macros have no auto CR
cmget5a:mov	al,cr			; report cr as last char
	mov	noparse,0		; and say end of comment
					; start common code
cmget6:
	cmp	al,lf			; line feed?
	jne	cmget8			; ne = no
	jmp	cmget01			; yes, ignore and read another char
					; handle comments (echo but not parse)
cmget8:	cmp	noparse,0		; parsing?
	jne	cmget9			; ne = yes, do echo and no parse
	cmp	prevch,0		; have previous char to analyze?
	jne	cmget8c			; ne = yes
	cmp	taklev,0		; in a Take file or Macro?
	je	cmget8e			; e = no, use ";" as a literal
	push	bx			; treat ";" as literal in int macros
	mov	bx,takadr		; offset of this Take structure
	cmp	[bx].taktyp,0fdh	; internal macro?
	pop	bx
	je	cmget8e			; e = yes, semicolons are literals
	cmp	al,'\'			; start of '\;'?
	jne	cmget8a			; ne = no
	mov	prevch,al		; yes, maybe. save '\' til later
	jmp	cmget02			; read next char
cmget8a:cmp	al,';'			; possible start of comment?
	jne	cmget8e			; no, export al
	mov	al,' '			; replace ';' with space for comment
	jmp	short cmget9		; go start a comment

cmget8c:cmp	al,';'			; end of '\;'?
	je	cmget8d			; e = yes, omit leading backslash
	xchg	prevch,al		; no, save new, recover old '\'
	jmp	short cmget8e		; export '\'
cmget8d:mov	prevch,0		; clear old '\'
cmget8e:jmp	short cmget12		; do parsing

					; echo comment
cmget9:	cmp	flags.takflg,0		; echoing take files?
	je	cmget9a			; e = no
	push	ax
	push	dx
	mov	ah,conout		; echo current char
	mov	dl,al
	int	dos
	pop	dx
	pop	ax
cmget9a:mov	noparse,0		; cr ends comment
	cmp	al,cr			; end of comment?
	je	cmget13			; e = yes, export cr
	mov	noparse,1		; still in comment
	jmp	cmget01			; read more chars

					; read from tty device
cmget10:mov	ah,coninq		; Get a char from device, not file
	int	dos			;  with no echoing
	or	al,al
	jnz	cmget12			; ignore null bytes of special keys
	int	dos			; read and discard scan code byte
	jmp	short cmget10		; try again

cmget12:cmp	al,'C'and 1Fh		; Control-C?
	je	cmget14			; e = yes
	cmp	al,TAB			; tab is replaced by space
	jne	cmget13			; ne = not tab
	mov	al,' '
	ret
cmget13:cmp	al,LF			; LF becomes CR interactively
	jne	cmget13a
	mov	al,CR
cmget13a:ret				; normal exit, char is in AL

cmget14:mov	ah,prstr		; Control-C handler
	push	dx
	mov	dx,offset ctcmsg	; show Control-C
	int	dos
	pop	dx
	mov	prevch,0
	mov	flags.cxzflg,'C'	; tell others the news
	jmp	dword ptr pcmexit	; fail immediately via longjmp
cmgetc	endp

; Read chars from user (cmgetc). Detect terminators. Reads from buffer
; cmbuf. Set read pointer cmrptr to next free buffer byte if
; char is not a terminator: chars CR, LF, FF, '?' (returns carry set for
; terminators). Do ^U, ^W editing, convert FF to CR plus clear screen.
; Edit "-<cr>" as line continuation, "\-<cr>" as "-<end of line>".
; Return char in AH.
CMINBF	proc	near			; Buffer reader, final editor
	cmp	cmwptr,offset cmdbuf+size cmdbuf-3 ; max buffer size - 3
	jb	cminb1			; b = not full for writing
	mov	ah,conout		; almost full, notify user
	push	dx
	mov	dl,bell
	int	dos
	pop	dx
	cmp	cmrptr,offset cmdbuf+size cmdbuf ; reading beyond buffer?
	jb	cminb1			; b = no
	mov	ah,prstr
	mov	dx,offset cmer09	; command too long
	int	dos
	jmp	prserr			; overflow = parse error

cminb1:	push	bx
	mov	bx,cmrptr		; read pointer
	mov	ah,[bx]			; get current command char while here
	cmp	bx,cmwptr		; do we need to read more?
	pop	bx			; no if cmrptr < cmwptr
	jb	cminb2			; b: cmrptr < cmwptr (have extra here)
	call	cmgetc			; no readahead, read another into al
	mov	ah,al			; keep char in 'ah'
	push	bx
	mov	bx,cmwptr		; get the pointer into the buffer
	mov	[bx],ah			; put it in the buffer
	inc	bx
	mov	cmwptr,bx		; inc write pointer
	pop	bx
	jmp	short cminb1		; call cmgetc until cmwptr >= cmrptr
					; Char to be delivered is in ah
cminb2:	cmp	ah,'W' and 1fh		; is it a ^W?
	jne	cminb3
	call	cntrlw			; kill the previous word
	jnc	cminbf			; nc = no change, get another char
	jmp	repars			; need a new command scan (cleans stk)

cminb3:	cmp	ah,'U' and 1fh		; is it a ^U?
	jne	cminb3a			; ne = no
	mov	cmwptr,offset cmdbuf	; reset buffer write pointer
	jmp	repars			; go start over (cleans stack)
					; BS and DEL
cminb3a:cmp	ah,DEL			; delete code?
	je	cminb3b			; e = yes
	cmp	ah,BS			; Backspace (a delete operator)?
	jne	cminb4			; ne = no
cminb3b:call	bufdel			; delete char from buffer
	jc	cminb3c			; c = did erasure
	jmp	cminbf			; no erasure, ignore BS, get more
cminb3c:jmp	repars			; could have deleted previous token

cminb4:	push	bx			; look for hyphen or \hyphen
	cmp	ah,cr			; check for hyphen line continuation
	jne	cminb4b			; ne = not end of line
	mov	bx,cmwptr		; get the pointer into the buffer
	cmp	bx,offset cmdbuf-2	; do we have a previous char?
	jb	cminb4b			; b = no
	cmp	byte ptr[bx-2],'-'	; previous char was a hyphen?
	jne	cminb4b			; ne = no
	pop	bx
	call	bufdel			; delete the hyphen
	jmp	repars
cminb4b:pop	bx
					; Echoing done here
	cmp	comand.cmquiet,0	; quiet mode?
	jne	cminb5			; yes, skip echoing
	cmp	taklev,0		; in a take file?
	je	cminb4a			; e = no
	cmp	flags.takflg,0		; echo take file?
	je	cminb5			; e = no
cminb4a:push	ax			; save the char
	cmp	ah,' '			; printable?
	jae	cminb4c			; yes, no translation needed
	cmp	ah,cr			; this is printable
	je	cminb4c
	cmp	ah,lf
	je	cminb4c
	cmp	ah,escape		; escape?
	je	cminb4d			; do not echo this character
	push	ax			; show controls as caret char
	push	dx
	mov	dl,5eh			; caret
	mov	ah,conout
	int	dos
	pop	dx
	pop	ax
	add	ah,'A'-1		; make control code printable
cminb4c:push	dx
	mov	dl,ah
	mov	ah,conout
	int	dos			; echo it ourselves
	pop	dx
cminb4d:pop	ax			; and return char in ah

cminb5:	cmp	ah,cr			; carriage return?
	je	cminb6
	cmp	ah,lf			; line feed?
	je	cminb6
	cmp	ah,ff			; formfeed?
	jne	cminb7			; none of the above, report bare char
	call	fcmblnk			; FF: clear the screen and
	push	bx
	push	cx
	push	dx
	call	flocate			; Home the cursor
	mov	bx,cmwptr		; make the FF parse like a cr
	mov	byte ptr [bx-1],cr	; pretend a carriage return were typed
	pop	dx
	pop	cx
	pop	bx
cminb6: cmp	cmwptr,offset cmdbuf	; parsed any chars yet?
	jne	cminb7			; ne = yes
	cmp	comand.cmcr,0		; bare cr's allowed?
	jne	cminb7			; ne = yes
	jmp	prserr			; If not, just start over
cminb7:	clc
	ret
cminbf	endp

; Read chars from cminbf. Cmrptr points to next char to be read.
; Compresses repeated spaces if cmsflg is non-zero. Exit with cmrptr pointing
; at a terminator or otherwise at next free slot.
; Non-space then space acts as a terminator but cmrptr is incremented.
; Substitution variables, '\%x', are detected and expanded. Return char in AH.

CMGTCH	proc	near			; return char in AH, from rescan buf
	call	cminbf			; get char from buffer or user
	push	bx
	mov	bx,cmrptr		; get read pointer into the buffer
	mov	ah,[bx]			; read the next char
	inc	bx
	mov	cmrptr,bx		; where to read next time
	pop	bx
	call	subst			; examine for text substitution
	jnc	cmgtc1			; nc = no substitutions done
	jmp	repars			; reparse line with new material

cmgtc1:	cmp	ah,' '			; space?
	jne	cmgtc3			; ne = no
	cmp	bracecnt,0		; are we within braces?
	jne	cmgtc3			; ne = yes, treat space as literal
	cmp	cmsflg,0		; space flag, was last char a space?
	jne	cmgtch			; ne = yes, get another char
	mov	cmsflg,0FFH		; Set the space(s)-seen flag
	mov	ah,' '			; character for caller
	stc				; set carry for terminator
	ret				; return space as a terminator
cmgtc3: mov	cmsflg,0		; clear the space-seen flag
	cmp	ah,braceop		; opening brace?
	jne	cmgtc3b			; ne = no
	inc	bracecnt		; count it
	jmp	short cmgtc3c
cmgtc3b:cmp	ah,bracecl		; closing brace?
	jne	cmgtc3c			; ne = no
	sub	bracecnt,1		; count down and get a sign bit
	jns	cmgtc3c			; ns = no underflow
	mov	bracecnt,0		; catch underflows
cmgtc3c:cmp	ah,escape		; terminators remain in buffer but
	je	cmgtc4			;  are ready to be overwritten
	cmp	ah,'?'			; is the user curious?
	jne	cmgtc3a			; ne = no
	cmp	taklev,0		; in a Take file?
	jne	cmgtc3d			; ne = yes, make query ordinary char
	je	cmgtc4
cmgtc3a:cmp	ah,cr
	je	cmgtc4
	cmp	ah,lf
	je	cmgtc4
	cmp	ah,ff
	je	cmgtc4
cmgtc3d:clc				; carry clear for non-terminator
	ret
cmgtc4: dec	cmrptr			; point at terminating char
	stc				; set carry to say it is a terminator
	ret
cmgtch	endp

; Reset comand.cmdbuf write pointer (cmwptr) to where the read pointer
; (cmrptr) is now. Discards material not yet read.
bufreset proc	near
	push	cmrptr			; where next visible char is read
	push	ax			; count removed curly braces
	push	si
	mov	si,cmrptr		; where to look
	mov	cx,cmwptr		; last place being removed
	sub	cx,si			; length to examine
	cld
bufres1:lodsb
	cmp	al,braceop		; opening brace, counted already?
	jne	bufres2			; ne = no
	dec	bracecnt		; uncount it
	jmp	short bufres3
bufres2:cmp	al,bracecl		; closing brace, counted already?
	jne	bufres3			; jne = no
	inc	bracecnt		; uncount it
bufres3:loop	bufres1
	cmp	bracecnt,0		; negative?
	jge	bufres4			; ge = no
	mov	bracecnt,0
bufres4:pop	si
	pop	ax
	pop	cmwptr			; where new char goes in buffer
	ret
bufreset endp

; Delete character from screen and adjust buffer. Returns carry clear if
; no erasure, carry set otherwise.
bufdel	proc	near
	push	ax
	push	si
	mov	si,cmrptr
	mov	al,[si]
	cmp	al,braceop		; opening brace, counted already?
	jne	bufdel1			; ne = no
	dec	bracecnt		; uncount it
	jmp	short bufdel2
bufdel1:cmp	al,bracecl		; closing brace?
	jne	bufdel2			; ne = no
	inc	bracecnt		; uncount it
bufdel2:cmp	bracecnt,0		; negative?
	jge	bufdel3			; ge = no
	mov	bracecnt,0
bufdel3:pop	si
	pop	ax
	dec	cmrptr			; remove previous char from buffer
	cmp	cmrptr,offset cmdbuf	; back too far?
	jae	bufde2			; ae = no, material can be erased
	mov	cmrptr,offset cmdbuf	; set to start of buffer
	call	bufreset		; reset buffer
	mov	bracecnt,0		; ensure this is now cleared
	clc				; say no erasure
	ret
bufde2:	call	bufreset		; reset buffer
	stc				; say did erasure
	ret
bufdel	endp

; Come here is user types ^W when during input. Remove word from buffer.
cntrlw	proc	near
	push	ax
	push	cx
	push	dx
	call	bufreset		; truncate buffer at cmrptr
	mov	cx,cmrptr		; read pointer
	sub	cx,offset cmdbuf	; compute chars in buffer
	clc				; say have not yet modified line
	jcxz	ctlw2			; z = nothing to do, exit no-carry
	push	es
	std				; scan backward
	mov	ax,ds
	mov	es,ax			; point to the data are
	mov	di,cmwptr		; looking from here
	dec	di
	mov	al,' '
	repe	scasb			; look for non-space
	je	ctlw1			; all spaces, nothing to do
	inc	di			; move back to non-space
	inc	cx
	repne	scasb			; look for a space
	jne	ctlw1			; no space, leave ptrs alone
	inc	di
	inc	cx			; skip back over space
ctlw1:	inc	di
	pop	es
	cld				; reset	direction flag
	mov	cmwptr,di		; update pointer
	stc				; set carry to say modified line
ctlw2:	pop	dx
	pop	cx
	pop	ax
	ret
cntrlw	endp

; Jump to REPARS to do a rescan of the existing buffer.
; Jump to PRSERR on a parsing error (quits command, clears old read material)

PRSERR	PROC NEAR
	mov	cmwptr,offset cmdbuf	; initialize write pointer
	mov	ah,prstr
	mov	dx,offset crlf		; leave old line, start a new one
	int	dos
	call	rprompt			; restore master prompt level
					; reparse current line
REPARS:	mov	cmrptr,offset cmdbuf	; reinit read pointer
	mov	comand.cmper,0		; reset to variable recognition
	mov	cmsflg,0ffh		; strip leading spaces
	mov	subcnt,0		; clear substitution state variables
	mov	subtype,0
	mov	bracecnt,0
	cmp	taklev,0		; in Take cmd?
	je	prser2			; e = no
	cmp	flags.takflg,0		; echo contents of Take file?
	je	prser3			; e = no
prser2:	call	fctlu			; clear display's line, reuse it
	mov	dx,comand.cmprmp	; display the asciiz prompt
	call	fprtasz
prser3:	mov	bx,0ffffh		; returned keyword value
	mov	sp,comand.cmostp	; set new sp to old one
	jmp	dword ptr comand.cmrprs	; jump to just before the prompt call
PRSERR	ENDP

; Restore prompt material to that of the master prompt. This removes settings
; of local PROMPT calls so we can reprompt at the main Kermit level.
RPROMPT	proc	near
	push	ax
	mov	ax,mcmprmp		; address of prompt string
	or	ax,ax			; any address given yet?
	jz	rprompt1		; z = none, not inited yet
	mov	comand.cmprmp,ax	; set current address ptr
	mov	ax,word ptr mcmrprs	; offset of reparse address
	mov	word ptr comand.cmrprs,ax
	mov	ax,word ptr mcmrprs+2	; segment of reparse address
	mov	word ptr comand.cmrprs+2,ax
	mov	ax,mcmostp		; stack ptr at reparse time
	mov	comand.cmostp,ax
rprompt1:pop	ax
	ret
RPROMPT	endp



; write \v(ARGC) contents to ds:di
wrtargc	proc	near
	xor	ax,ax
	cmp	taklev,0		; in a Take/Macro?
	je	wrtarg1			; e = no
	mov	bx,takadr		; current Take structure
	mov	ax,[bx].takargc		; get ARGC
wrtarg1:call	fdec2di			; write as ascii
	mov	word ptr [di],0020h	; space and null terminator
	inc	di
	ret
wrtargc	endp

; write \v(COUNT) text to ds:di
wrtcnt	proc	near
	xor	ax,ax
	cmp	taklev,0		; in a Take/Macro?
	je	wrtcnt1			; e = no
	mov	bx,takadr		; current Take structure
	mov	ax,[bx].takctr		; get COUNT
wrtcnt1:call	fdec2di			; write as ascii
	mov	word ptr [di],0020h	; space and null terminator
	inc	di
	ret
wrtcnt	endp

; write \v(DATE) text to ds:di
wrtdate	proc	near
	push	cx
	push	dx
	mov	ah,getdate		; DOS date (cx= yyyy, dh= mm, dl= dd)
	int	dos
	xor	ah,ah
	cmp	tdfmt,0			; USA standard mm/dd/yyyy?
	jne	wrtdat1			; ne = no
	xor	ah,ah
	mov	al,dh			; month
	call	wrtdat5			; output
	mov	byte ptr [di],'/'	; separate
	inc	di
	xor	ah,ah
	mov	al,dl			; day
	call	wrtdat5
	mov	byte ptr [di],'/'
	inc	di
	mov	ax,cx
	jmp	short wrtdat3

wrtdat1:cmp	tdfmt,1			; European standard dd/mm/yyyy?
	jne	wrtdat2			; ne = no
	xor	ah,ah
	mov	al,dl			; day
	call	wrtdat5
	mov	byte ptr [di],'/'
	inc	di
	xor	ah,ah
	mov	al,dh			; month
	call	wrtdat5
	mov	byte ptr [di],'/'
	inc	di
	mov	ax,cx
	jmp	short wrtdat3

wrtdat2:mov	ax,cx			; Japan yyyy:mm:dd,   year
	call	wrtdat5
	mov	byte ptr [di],':'
	inc	di
	xor	ah,ah
	mov	al,dh			; month
	call	wrtdat5
	mov	byte ptr [di],':'
	inc	di
	xor	ah,ah
	mov	al,dl			; day
wrtdat3:call	wrtdat5
	mov	word ptr [di],0020h	; space and null terminator
	inc	di
	pop	dx
	pop	cx
	ret
	ret

wrtdat5:cmp	ax,10			; leading tens digit present?
	jae	wrtdat6			; ae = yes
	mov	byte ptr [di],'0'	; insert leading 0
	inc	di
wrtdat6:call	fdec2di			; write decimal asciiz to buffer
	ret

wrtdate	endp

; write \v(ERRORLEVEL) text to ds:di
wrterr	proc	near
	mov	al,errlev		; current Errorlevel
	xor	ah,ah
	call	fdec2di			; write as ascii
	mov	word ptr [di],0020h	; space and null terminator
	inc	di
	ret
wrterr	endp

; write \v(KEYBOARD) text to ds:di
wrtkbd	proc	near
	mov	ax,keyboard		; 88 or 101 keyboard keys
	call	fdec2di			; write as ascii
	mov	word ptr [di],0020h	; space and null terminator
	inc	di
	ret
wrtkbd	endp

; write \v(NDATE) text to ds:di
; where NDATE is YYYYMMDD
wrtndate proc	near
	mov	ah,getdate		; DOS date (cx= yyyy, dh= mm, dl= dd)
	int	dos
	push	dx			; save dx
	mov	ax,cx			; year
	call	fdec2di			; convert it
	pop	dx			; get mm:dd
	push	dx
	mov	al,dh			; months are next
	xor	ah,ah
	cmp	al,10			; less than 10?
	jae	wrtndat1		; ae = no
	mov	byte ptr [di],'0'	; leading 0
	inc	di
wrtndat1:call	fdec2di
	pop	dx
	mov	al,dl			; get days
	xor	ah,ah
	cmp	al,10			; less than 10?
	jae	wrtndat2		; ae = no
	mov	byte ptr [di],'0'	; leading 0
	inc	di
wrtndat2:call	fdec2di
	mov	word ptr [di],0020h	; space and null terminator
	inc	di
	ret
wrtndate endp

; write \v(DIRECTORY) text to ds:di
wrtdir	proc	near
	push	si
	mov	ah,gcurdsk		; get current disk
	int	dos
	add	al,'A'			; make al = 0 == 'A'
	mov	[di],al
	mov	word ptr [di+1],'\:'
	mov	si,di			; work buffer
	add	si,3			; end with a colon and backslash
	mov	ah,gcd			; get current directory
	xor	dl,dl			; use current drive
	int	dos			; get ds:si = asciiz path (no drive)
	mov	dx,di
	call	fstrlen
	add	di,cx
	cmp	cx,3			; directory added?
	je	wrtdir1			; e = no
	mov	byte ptr [di],'\'	; add slash terminator so filenames
	inc	di			;  can be appended easily
wrtdir1:mov	word ptr [di],0020h	; space and null terminator
	inc	di
	pop	si
	ret
wrtdir	endp

fwrtdir	proc	far
	call	wrtdir
	ret
fwrtdir	endp

; write \v(PLATFORM) text to ds:di
wrtplat	proc	near
	push	si
	mov	si,offset machnam	; machine name in sys dep file
	cld
wrtplat1:lodsb				; get a char
	cmp	al,'$'			; terminator?
	je	wrtplat2		; e = yes
	mov	[di],al			; store char
	inc	di
	jmp	short wrtplat1		; keep going
wrtplat2:mov	word ptr [di],0020h	; space and null terminator
	inc	di
	mov	temp,di			; place for additional text
	pop	si
	ret
wrtplat	endp

; write \v(PORT) text to ds:di
wrtport	proc	near
	push	bx
	push	si
	mov	al,flags.comflg		; get coms port indicator
	mov	bx,offset comptab	; table of comms ports
	mov	cl,[bx]			; number of entries
	xor	ch,ch
	inc	bx
wrtpor3:mov	dx,[bx]			; length of this entry
	mov	si,bx
	add	si,2			; points to entry text string
	add	si,dx			; point to qualifier
	cmp	[si],al			; our port?
	je	wrtpor4			; e = yes
	add	bx,[bx]			; add text length
	add	bx,4			; plus count and qualifier
	loop	wrtpor3			; next entry
	jmp	short wrtpor5		; no match, curious
wrtpor4:mov	si,bx			; point at entry
	add	si,2			; point at string
	mov	cx,[bx]			; length of string
	push	es
	mov	ax,ds
	mov	es,ax
	cld
	rep	movsb			; copy to DS:DI
	pop	es
wrtpor5:mov	word ptr [di],0020h	; space and null terminator
	inc	di
	pop	si
	pop	bx
	ret
wrtport	endp

; write \v(PROGRAM) text to ds:si
wrtprog	proc	near
	push	si
	mov	si,offset progm		; source string
	cld
wrtprg1:lodsb
	cmp	al,'$'			; terminator?
	je	wrtprg2			; e = yes
	mov	[di],al			; store the char
	inc	di
	jmp	short wrtprg1
wrtprg2:mov	word ptr [di],0020h	; space and null terminator
	inc	di
	mov	temp,di			; place for additional text
	pop	si
	ret
wrtprog	endp

; write \v(STATUS) text to ds:di
wrtstat	proc	near
	mov	ax,kstatus		; Kermit status word
	call	fdec2di
	mov	word ptr [di],0020h	; space and null terminator
	inc	di
	mov	temp,di			; place for additional text
	ret
wrtstat	endp

; write \v(SYSTEM) text to ds:di
wrtsystem proc	near
	push	si
	mov	si,offset system	; system string "MS-DOS", dollar sign
	cld
	jmp	wrtplat1		; use some common code
wrtsystem endp

; write \v(TIME) text to ds:di
wrttime	proc	near
	mov	ah,gettim		; DOS tod (ch=hh, cl=mm, dh=ss, dl=.s)
	int	dos
	push	dx			; save dx
	xor	ah,ah
	mov	al,ch			; Hours
	cmp	al,10			; leading digit?
	jae	wrttim1			; ae = yes
	mov	byte ptr [di],'0'	; make our own
	inc	di
wrttim1:push	cx
	call	fdec2di			; write decimal asciiz to buffer
	pop	cx
	mov	byte ptr [di],':'
	inc	di
	xor	ah,ah
	mov	al,cl			; Minutes
	cmp	al,10			; leading digit?
	jae	wrttim2			; ae = yes
	mov	byte ptr [di],'0'	; make our own
	inc	di
wrttim2:call	fdec2di			; write decimal asciiz to buffer
	mov	byte ptr [di],':'
	inc	di
	pop	dx
	xor	ah,ah
	mov	al,dh			; Seconds
	cmp	al,10			; leading digit?
	jae	wrttim3			; ae = yes
	mov	byte ptr [di],'0'	; make our own
	inc	di
wrttim3:call	fdec2di			; write decimal asciiz to buffer
	mov	word ptr [di],0020h	; space and null terminator
	inc	di
	mov	temp,di			; place for additional text
	ret
wrttime	endp

; write \v(Version) text to ds:di
wrtver	proc	near
	mov	si,offset verident	; MS Kermit version string in mssker
	cld
wrtver1:lodsb
	mov	[di],al
	inc	di
	cmp	al,'$'			; end of string?
	jne	wrtver1			; ne = no, continue copying
	dec	di
	mov	word ptr [di],0020h	; space and null terminator
	inc	di
	mov	temp,di			; place for additional text
	ret
wrtver	endp

; write \v(TERMINAL) text to ds:di
wrtterm	proc	near
	mov	ax,flags.vtflg		; current terminal type
	mov	bx,offset termtb	; terminal type table msx...
	mov	cl,[bx]			; number of entries in our table
	xor	ch,ch
	inc	bx			; point to the data
wrtter1:mov	si,[bx]			; length of keyword
	cmp	ax,[bx+si+2]		; value fields match?
	je	wrtter2			; e = yes
	add	bx,si			; add word length
	add	bx,4			; skip count and value fields
	loop	wrtter1			; keep searching
	jmp	short wrtter4		; no match, use just a space
wrtter2:mov	cx,[bx]			; get length of counted string
	mov	si,bx
	add	si,2			; look at text
	cld
wrtter3:lodsb
	mov	[di],al			; from ds:si to ds:di
	inc	di
	loop	wrtter3
wrtter4:mov	word ptr [di],0020h	; space and null terminator
	inc	di
	mov	temp,di			; place for additional text
	ret
wrtterm	endp

code1	ends

code	segment
	assume	cs:code

; Set master prompt level. Enter with DX = offset of prompt string
MPROMPT	proc	near
	mov	mcmprmp,dx		; offset of prompt string
	pop	ax			; get the return address
	mov	word ptr mcmrprs,ax 	; offset to go to on reparse
	mov	mcmostp,sp		; stack pointer at reparse time
	push	ax			; put it on the stack again
	mov	ax,cs			; our current code segment
	mov	word ptr mcmrprs+2,ax 	; segment of reparse address
	jmp	short prompt		; now set the active prompt material
MPROMPT	endp

; This routine prints the prompt and specifies the reparse address.
; Enter with pointer to prompt string in dx. 
PROMPT	PROC  NEAR
	mov	comand.cmprmp,dx	; save the prompt
	pop	ax			; get the return address
	mov	word ptr comand.cmrprs,ax ; offset to go to on reparse
	mov	comand.cmostp,sp	; save for later restoration
	push	ax			; put it on the stack again
	mov	ax,cs			; our current code segment
	mov	word ptr comand.cmrprs+2,ax ; segment of reparse address
	mov	ax,offset cmdbuf
	mov	cmwptr,ax		; reset buffer read/write pointers
	mov	cmrptr,ax
	xor	al,al
	mov	comand.cmper,al		; allow substitutions
	mov	cmsflg,0ffh		; remove leading spaces
	cmp	flags.takflg,al		; look at Take flag, zero?
	jne	promp1			; ne=supposed to echo, skip this check
	cmp	taklev,al		; inside a take file?
	je	promp1			; no, keep going
	ret				; yes, return
promp1:	mov	ah,prstr
	mov	dx,offset crlf
	int	dos
	mov	dx,comand.cmprmp	; prompt pointer
	call	prtasz			; show asciiz prompt string
	clc
	ret
PROMPT	ENDP

ISDEV	PROC	NEAR			; Set carry if STDIN is non-disk
	push	ax
	push	bx
	push	dx
	xor	bx,bx			; handle 0 is stdin
	xor	al,al			; get device info
	mov	ah,ioctl
	int	dos
	rcl	dl,1			; put ISDEV bit into the carry bit
	pop	dx			; carry is set if device
	pop	bx
	pop	ax
	ret				; carry set if device
ISDEV	ENDP

ISEOF	PROC	NEAR			; Set carry if STDIN is at EOF
	push	ax			;  but only if stdin is a non-device
	push	bx
	push	dx
	xor	bx,bx			; handle 0 is stdin
	xor	al,al			; get device info
	mov	ah,ioctl
	int	dos
	mov	ah,ioctl
	mov	al,6			; get handle input status, set al
	test	dl,80h			; bit set if handle is for a device
	jnz	iseof1			; nz = device, always ready (al != 0)
	int	dos
iseof1:	or	al,al			; EOF?
	pop	dx
	pop	bx
	pop	ax
	jnz	iseof2			; nz = no
	stc				; set carry for eof
	ret
iseof2:	clc				; clear carry for not-eof
	ret
ISEOF	ENDP

; Convert ascii characters in al and ah to lowercase.
; All registers are preserved except AX, of course.

TOLOWR PROC NEAR
	cmp	ah,'A'			; less that cap A?
	jl	tolow1			; l = yes. leave untouched
	cmp	ah,'Z'+1		; more than cap Z?
	jns	tolow1			; ns = yes
	or	ah,20H			; convert to lowercase
tolow1:	cmp	al,'A'			; less that cap A?
	jl	tolow2			; l = yes. leave untouched
	cmp	al,'Z'+1		; more than cap Z?
	jns	tolow2			; ns = yes
	or	al,20H			; convert to lowercase
tolow2:	ret
TOLOWR	endp
code	ends

code1	segment
	assume	cs:code1

; Parse control sequences and device control strings.
; Expect CSI, Escape [, or DCS lead-in characters to have been read.
; Puts numerical Parameters in array param (16 bits, count is nparam) and
;  a single letter Parameter in lparam, (Parameters are all ASCII column 3)
;  Intermediate characters in array inter (count is ninter), (ASCII column 2)
;  Final character in AL (ASCII columns 4-7).
; Invoke by setting state to offset atparse, set pardone to offset of
; procedure to jump to after reading Final char (0 means do just ret)
; and optionally setting parfail to address to jump to if parsing failure.
; When the Final char has been accepted this routine jumps to label held in
; pardone for final action. Before the Final char has been read successful
; operations return carry clear.
; Failure exits are carry set, and an optional jump through parfail (if 
; non-zero) or a return.

atparse	proc	near
	mov	bx,parstate		; get parsing state
	or	bx,bx			; have any state?
	jnz	atpars1			; nz = have a state
	call	atpclr			; do initialization
	mov	bx,parstate		; get initial state
atpars1:call	bx			; execute it
	jc	atpfail			; c = failure
	cmp	parstate,offset atpdone	; parsed final char?
	je	atpdone			; e = yes
	ret				; no, wait for another char

				; successful conclusion, final char is in AL
atpdone:mov	parstate,0		; reset parsing state
	cmp	pardone,0		; separate return address defined?
	jne	atpdon1			; ne = yes
	clc
	ret				; else just return
atpdon1:clc
	jmp	pardone			; jmp to supplied action routine

atpfail:mov	parstate,0		; failed, reset parser to normal state
	cmp	parfail,0		; jump address specified?
	je	atpfail1		; e = no
	jmp	parfail			; yes, exit this way
atpfail1:stc
	ret
					; parsing workers
atparm:	cmp	ninter,0		; Parameter, started intermediate yet?
	jne	atinter			; ne = yes, no more parameters
	cmp	al,';'			; argument separator?
	jne	atparm3			; ne = no
	mov	ax,nparam		; number of Parameters
	inc	ax			; say a new one
	cmp	ax,maxparam		; too many?
	jb	atparm2			; b = no, continue
	stc				; set carry to say failed
	ret				; too many, ignore remainder
atparm2:mov	nparam,ax		; say doing another Parameter
	clc
	ret

atparm3:mov	ah,al			; copy char
	and	ah,not 0fh		; ignore low nibble
	cmp	ah,30h			; column 3, row 0? (30h='0')
	jne	atparm6			; ne = no, check Intermediate/Final
	cmp	al,'9'			; digit?
	ja	atparm5			; a = no, check letter Parameters
	sub	al,'0'			; ascii to binary
	mov	bx,nparam		; current parameter number
	shl	bx,1			; convert to word index
	mov	cx,param[bx]		; current parameter value
	shl	cx,1			; multiply by 10.  2 * cl
	push	bx
	mov	bx,cx			; save 2 * cl
	shl	cx,1			; 4 * cl
	shl	cx,1			; 8 * cl
	add	cx,bx			; 10 * cl
	pop	bx
	add	cl,al			; add new digit
	adc	ch,0
	jnc	atparm4			; nc = no carry out (65K or below)
	mov	cx,0ffffh		; set to max value
atparm4:mov	param[bx],cx		; current Parameter value
	clc
	ret
					; check non-numeric Parameters
atparm5:cmp	al,'?'			; within column 3?
	ja	atfinal			; a = no, check Final char
	mov	lparam,al		; store non-numeric Parameter
	clc
	ret

atparm6:cmp	nparam,0		; started a parameter yet?
	jne	atparm7			; ne = yes
	cmp	param,0			; got anything for param[0]?
	je	atinter			; e = no
atparm7:inc	nparam			; yes, say finished with another

atinter:mov	parstate,offset atinter	; next state (intermediate)
	cmp	al,';'			; argument separator?
	jne	atinte1			; ne = no
	cmp	ninter,maxinter		; too many intermediates?
	jb	atinte2			; b = no, continue
	stc				; carry = failed
	ret				; too many, ignore remainder
atinte1:test	al,not 2fh		; column two = 20h - 2fh?
	jnz	atfinal			; nz = not an Intermediate, try Final
	cmp	ninter,maxinter		; too many intermediates?
	jb	atinte1a		; b = no, continue
	stc				; carry = failed
	ret				; too many, ignore remainder
atinte1a:mov	bx,ninter		; current Intermediate slot number
	mov	inter[bx],al		; current Intermediate value
atinte2:inc	ninter			; say doing another Intermediate
	clc
	ret

atfinal:cmp	al,40h			; Final character, range is 40h to 7fh
	jb	atfina1			; b = out of range
	cmp	al,7fh
	ja	atfina1			; a = out of range
	mov	parstate,offset atpdone	; next state is "done"
	clc				; success, final char is in AL
	ret
atfina1:stc				; c = failed
	ret
atparse	endp

; Clear Parameter, Intermediate arrays in preparation for parsing
atpclr	proc	near
	push	ax
	push	cx
	push	di
	push	es
	xor	ax,ax			; get a null
	mov	parstate,offset atparm	; init parser state
	mov	lparam,al		; clear letter Parameter
	mov	nparam,ax		; clear Parameter count
	mov	cx,maxparam		; number of Parameter slots
	mov	di,offset param		; Parameter slots
	push	ds
	pop	es			; use data segment for es:di below
	cld				; set direction forward
	rep	stosw			; clear the slots
	mov	ninter,ax		; clear Intermediate count
	mov	cx,maxinter		; number of Intermediate slots
	mov	di,offset inter		; Intermediate slots
	rep	stosb			; clear the slots
	pop	es
	pop	di
	pop	cx
	pop	ax
	ret
atpclr	endp

; Dispatch table processor. Enter with BX pointing at table of {char count,
; address of action routines, characters}. Jump to matching routine or return.
; Enter with AL holding received Final char.
atdispat proc near
	mov	cl,[bx]			; get table length from first byte
	xor	ch,ch
	mov	di,bx			; main table
	add	di,3			; point di at first char in table
	push	es
	push	ds
	pop	es			; use data segment for es:di below
	cld				; set direction forward
	repne	scasb			; find matching character
	pop	es
	je	atdisp2			; e = found a match, get action addr
	ret				; ignore escape sequence
atdisp2:sub	di,bx			; distance scanned in table
	sub	di,4			; skip count byte, address word, inc
	shl	di,1			; convert to word index
	inc	bx			; point to address of action routines
	mov	bx,[bx]			; get address of action table
	jmp	word ptr [bx+di]	; dispatch to the routine
atdispat endp
code1	ends
	end
