	.TITLE "A poor person's spelling checker"
	.PAGE 96,84
;
	.EPOP
	.ZOP
	.PABS
	.PHEX
	.XLINK
	ASEG
;
;	version 1.1  (04/30/82)  (Jim Byram)
;	Changed last instruction of GBYTE1 from OR A to AND 7FH
;	to clear high bit of text character as well as to reset
;	carry.  Necessary to scan WordStar files.
;	Changed all unconditional JR instructions to JP to speed
;	execution.  Moved BDOS calls in-line.
;	Added file output using routines from SD-42.ASM.  Words
;	not matched are written to console and (optionally) to
;	the printer and/or to a file named SPELL.LEX.  The file
;	is created on the default drive if it did not previously
;	exist.  If it did exist, the new list of unmatched words
;	is appended to the file.  This feature allows generation
;	of word lists which can be sorted and edited and then
;	added to your MASTER.LEX.
;	Added command line options for file and printer output.
;
;	version 1.0  (Alan Bomberger)
;
;	Bomberger, Alan.  1982.  A poor person's spelling
;	checker. Dr. Dobb's Journal 7(4):42-53. (DDJ #66)
;
;	Released for NON COMMERCIAL USE ONLY
;	   (c)  1981  Alan Bomberger
;
;	USAGE:  [d:]spell [d:]filename.typ [fp]
;
;		spell filename.typ    --> output to console
;
;		spell filename.typ f  --> ..and to file
;
;		spell filename.typ p  --> ..or to printer
;
;		spell filename.typ fp --> output to all three
;
;	The input file is checked using the lexicon files and
;	misspelled words (i.e., unmatched words) are printed in
;	the order they appear in the text.
;
;	The input file is broken down into a word list and the
;	user is prompted to enter the name of each lexicon to
;	be scanned.
;
;	Note -- a lexicon is a list of words usually separated
;	by <crlf>.  The words comprising a lexicon may be in
;	any order, but program execution is much faster if all
;	lexicon words are UPPER CASE.
;
;	The word list will fill all available memory so only
;	very large documents will require more than one pass
;	of the lexicons.
;
BOOT	EQU	0
BDOS	EQU	5
;
PCHAR	EQU	2
LISTC	EQU	5
PSTRING	EQU	9
RSTRING	EQU	10
OPENF	EQU	15
CLOSEF	EQU	16
SRCHF	EQU	17
READF	EQU	20
WRITEF	EQU	21
MAKEF	EQU	22
SETDMA	EQU	26
;
FCB	EQU	5CH
FCB2	EQU	6CH
BUFF1	EQU	80H
;
CR	EQU	0DH
LF	EQU	0AH
BELL	EQU	7
;
	ORG	100H
;
SPELIT:
	LD	SP,STACK	; a new stack pointer
	LD	A,(FCB2+1)	; check for output options
	CP	" "		; any options?
	JR	Z,NOOPT
	CALL	CHKOPT		; yes, determine which
	LD	A,(FCB2+2)	; check for second option
	CP	" "		; another option?
	JR	Z,NOOPT
	CALL	CHKOPT		; yes, determine which
NOOPT:
	LD	DE,COPYR
	LD	C,PSTRING
	CALL	BDOS
	CALL	OPENIN		; open input files
	CALL	ZCHN		; zap chains
BUILDL:
	CALL	GWORD		; get the next word of text
	JR	C,ENDIN		; no more left, check spelling
	CALL	SEARCH		; see if in word list
	JR	NC,BUILDL	; yes, it is
	CALL	ADDW		; no, so add it in
;
;	"WORK" contains the address of the last word put into
;	the word list.  See if this word is past the threshold
;	of memory.
;
	CALL	COMPARE
	JR	NC,BUILDL	; no, so continue
	LD	HL,NUMWDC+2	; mark as incomplete
	LD	(HL),"*"
	CALL	SPELL		; check the current list
	CALL	PTABLE		; print the misspelled words
	LD	IX,COMWDL	; the last of the common words
	SET	WFLGSL,(IX+WFLGS) ; mark this as the last in list
	LD	C,6
	LD	B,0
	LD	DE,NUMWD
	LD	HL,ZCOUNT	; zero counter
	LDIR
	CALL	ZCHN		; zap chains
	JP	BUILDL		; and get next word
ENDIN:
	CALL	SPELL		; check spelling of words in list
	LD	DE,FCB
	LD	C,CLOSEF
	CALL	BDOS		; close up input file
	CALL	OUTPUT		; print the words not in lexicon
	JP	CLZOUT		; close output file and exit
;
;	chkopt
;
;	determine whether file and/or printer output selected
;	any unrecognized options will be ignored
;
CHKOPT:
	CP	"F"		; file output wanted?
	JR	NZ,NOTF		; no, what about printer?
	LD	A,0
	LD	(FOPFLG),A	; set flag
	RET
NOTF:
	CP	"P"		; printer output wanted?
	RET	NZ		; no
	LD	A,0
	LD	(POPFLG),A	; set flag
	RET
;
;	compare
;
;	compare the value in work with "endmem"
;
COMPARE:
	LD	HL,WORK		; address of last word
	LD	A,(ENDMEM)	; end of memory
	SUB	(HL)
	LD	A,(ENDMEM+1)
	INC	HL
	SBC	A,(HL)		; double precision subtract
	RET
;
;	openin
;
;	open input file and locate end of memory
;
OPENIN:
	LD	DE,FCB		; input file
	LD	C,OPENF
	CALL	BDOS
	LD	DE,NINPUT	; in case not there
	INC	A
	JR	Z,FAILED	; no file
	LD	A,128
	LD	(IBP),A		; set so 1st call gets disk record
;
;	find end of memory
;
	LD	HL,(6)		; address of bdos
	LD	BC,64		; a margin
	OR	A		; clear carry
	SBC	HL,BC		; subtract margin
	LD	(ENDMEM),HL
	RET
FAILED:
	LD	C,PSTRING
	CALL	BDOS
	JP	BOOT		; quit now
;
;	gword
;
;	get next word in text into cword
;	carry flag on means end of input
;
GWORD:
	LD	A,128
	LD	(CFLAGS),A	; set this word as last
	LD	DE,0		; length of word
GWORDL:
	CALL	GBYTE		; get next byte of text
	JR	C,GWORDE	; end of input
	LD	BC,(DELIML)	; length of delimiter table
	LD	HL,DELIMT	; the table
	CPIR			; is it a delimiter?
	JR	Z,DELIM		; yes
	LD	BC,(ALPHAL)
	LD	HL,ALPHA	; is it alphabetic?
	CPIR
	JR	NZ,GWORDL	; no, skip it
	CP	"a"		; is it lower case
	JR	C,GWORDU	; no
	CP	"{"		; lower
	JR	NC,GWORDU
	AND	5FH		; make all upper case
GWORDU:
	LD	HL,CWORD+4	; place to build word
	ADD	HL,DE
	LD	(HL),A		; put byte in word
	INC	E		; new length
	LD	A,E
	LD	(CLEN),A	; update in word entry
	CP	30		; how long is word?
	JR	Z,GWORDT	; too long a word
	JP	GWORDL		; loop
DELIM:
	LD	A,E		; current length
	CP	0
	JR	Z,GWORDL	; skip leading delimiters
	OR	A		; zero carry
GWORDE:
	RET
GWORDT:
	LD	DE,LNGWD1	; first part of text
	LD	C,PSTRING
	CALL	BDOS
	LD	DE,CWORD+4
	LD	C,PSTRING
	CALL	BDOS
	LD	DE,LNGLX2	; second part
	LD	C,PSTRING
	CALL	BDOS
	OR	A
	JP	GWORDE
;
;	getbyte
;
;	get next byte of text
;	carry flag on for end of file
;
GBYTE:
	PUSH	DE
	LD	A,(IBP)
	CP	128		; do we need another buffer full?
	JR	NZ,GBYTE1	; no
	LD	DE,FCB
	LD	C,READF
	CALL	BDOS		; read a block
	CP	0		; did it ok?
	SCF			; in case not
	JR	NZ,GBYTER	; end of file return
GBYTE1:
	LD	E,A		; has current byte index to fetch
	LD	D,0		; double precision
	LD	HL,BUFF1
	ADD	HL,DE
	INC	A		; next index
	LD	(IBP),A
	LD	A,(HL)		; get byte
	CP	1AH		; check for end
	SCF			; in case it is
	JR	Z,GBYTER	; yes
	AND	7FH		; clear carry and set bit 7 to 0
GBYTER:
	POP	DE
	RET
;
;	search
;
;	search word list for match with cword
;
;	on return ix will point to matched entry or last in list
;	carry on if no match
;
;	searc1 is the entry when searching on a chain
;
SEARCH:
	LD	IX,WORDS	; start of list
SEARC1:				; entry if starting with chain
SLOOP:
	LD	A,(CLEN)	; length of current word
	CP	(IX+WLEN)	; must be same as list entry
	JR	NZ,NEXTW	; try next entry
	CALL	CLC		; compare
	JR	Z,MATCH		; it is a match
NEXTW:
	BIT	WFLGSL,(IX+WFLGS) ; is this the last entry?
	JR	NZ,NMATCH	; yes, then no match
	LD	A,(IX+WCHN)	; get chain pointer
	LD	(WORK),A
	LD	A,(IX+WCHN1)	; both parts
	LD	(WORK+1),A
	CP	0		; this is high order (zero only if end)
	JR	Z,NMATCH	; end of chain
	LD	IX,(WORK)
	JP	SLOOP
MATCH:
	OR	A		; clear carry
	JP	SRET
NMATCH:
	SCF			; set carry
SRET:
	RET
;
;	clc
;
;	compare logical character
;	cword with list entry pointed to by ix
;	a contains length
;
CLC:
	PUSH	IX
	LD	C,A		; length for down count
	LD	HL,CWORD+4	; compare here
CLCL:
	LD	A,(IX+WORD)	; first character
	CP	(HL)		; is it?
	JR	NZ,CLCE		; no, stop
	INC	HL
	INC	IX
	DEC	C
	JR	NZ,CLCL		; not end so continue
CLCE:
	POP	IX
	RET
;
;	addw
;
;	add word to list
;	word is in cword and ix points to last entry
;
ADDW:
	LD	(WORK),IX	; save
	LD	IY,(WORK)	; old position
	LD	A,0
	LD	(CCHN),A
	LD	(CCHN1),A	; zero chain pointer
	RES	WFLGSL,(IX+WFLGS) ; clear this is last entry flag
	LD	B,0
	LD	A,(IX+WLEN)	; get length of last word
	ADD	A,4
	LD	C,A		; include chain and stuff
	ADD	IX,BC		; skip over last entry
	LD	(WORK),IX
	LD	A,(WORK)	; get low byte
	LD	(IY+WCHN),A	; to chain
	LD	A,(WORK+1)
	LD	(IY+WCHN1),A	; to chain
	LD	A,(CLEN)
	ADD	A,4
	LD	C,A
	LD	HL,CWORD	; source
	LD	(WORK),IX
	LD	DE,(WORK)	; can't get there from here
	LDIR			; move it
	CALL	COUNTW		; bump count
	RET
;
;	spell
;
;	check each lexicon word with list entries
;	mark correct (found) words in list
;
SPELL:
	LD	DE,NUMWD
	LD	C,PSTRING
	CALL	BDOS		; inform of number of words
	CALL	SETCHN		; set up chains
	LD	DE,BUFF2	; switch buffers
	LD	C,SETDMA
	CALL	BDOS
NEXTLEX:
	CALL	GETLEX		; get a lexicon file
	JR	C,SPELLR	; none, so return
	LD	DE,LFCB		; get lexicon file
	LD	C,OPENF
	CALL	BDOS
	LD	DE,NOLEX	; in case not there
	INC	A
	JR	NZ,GOTLEX	; it is a valid lexicon
	LD	C,PSTRING
	CALL	BDOS		; it is not a valid lexicon
	JP	NEXTLEX		; try again
GOTLEX:
	LD	DE,LFCB		; lexicon fcb
	LD	C,READF
	CALL	BDOS		; read first record
	CP	0		; did it
	JR	NZ,ENDL		; quick exit
	LD	DE,CHECKM	; tell customer
	LD	C,PSTRING
	CALL	BDOS		; that we begin
	LD	A,0
	LD	(IBPL),A
	LD	(COMP),A	; say not compacted
	LD	A,(BUFF2)	; first of compacted
	CP	0FFH
	JR	NZ,SPELLL
	LD	A,1
	LD	(COMP),A	; set compacted
	LD	(IBPL),A	; skip ff
SPELLL:
	CALL	LWORD		; get a word in cword
	JR	C,ENDL		; end of lexicon
	LD	IX,CWORD
	CALL	GETCHN		; get correct chain for this word
	LD	E,(HL)		; low order byte
	INC	HL
	LD	D,(HL)		; high order byte
	LD	(WORK),DE	; get first word in list
	LD	IX,(WORK)	; place to start
	LD	A,(WORK+1)
	CP	0
	JR	Z,SPELLL	; if zero no words this letter
	CALL	SEARC1		; look for word in chain
	JR	C,SPELLL	; did not find it
	SET	WFLGSC,(IX+WFLGS) ; mark spelled correctly
	JP	SPELLL		; and loop
ENDL:
	LD	DE,LFCB		; close
	LD	C,CLOSEF
	CALL	BDOS
	JP	NEXTLEX		; get another lexicon
SPELLR:
	LD	DE,BUFF1	; reset dma
	LD	C,SETDMA
	CALL	BDOS		; in case more input
	RET
;
;	getlex
;
;	get a lexicon file from the customer
;	if none requested (null input) return with carry flag on
;
GETLEX:
	LD	DE,ASKLEX
	LD	C,PSTRING
	CALL	BDOS		; type prompt
	CALL	ANSWER		; get answer
	JR	C,GETLXR	; return, no lexicon
	CALL	BLDFCB		; build a new fcb
	OR	A		; clear carry
GETLXR:
	RET
;
;	answer
;
;	get answer to question in buff2
;
ANSWER:	LD	DE,BUFF2
	LD	A,80
	LD	(BUFF2),A
	LD	C,RSTRING
	CALL	BDOS		; get answer
	LD	A,(BUFF2+1)	; get length of answer
	CP	0		; see if any
	SCF			; none
	JR	Z,ANSWRT	; quit now
	OR	A		; clear carry
ANSWRT:
	RET
;
;	bldfcb
;
;	build an fcb from information in buff2
;	assumes file type of .LEX
;
BLDFCB:
	LD	HL,DEFFCB	; the default fcb
	LD	DE,LFCB		; goes here
	LD	BC,16		; move this much
	LDIR			; move it
	XOR	A		; get a zero
	LD	(LFCBCR),A	; zero this as well
	LD	HL,BUFF2+2
	LD	A,(BUFF2+1)	; get number of bytes in name
	LD	C,A		; b is zero from block above
BLLOOP:
	LD	A,(HL)		; get a byte
	CP	" "		; is it a blank?
	JR	NZ,NOBLK	; no
	INC	HL
	DEC	C
	JR	NZ,BLLOOP	; skip leading blanks
	JP	BLDRET		; return with bad fcb
NOBLK:
	INC	HL		; skip disk name if present
	LD	A,(HL)		; get suspected ":"
	DEC	HL		; back to first character
	CP	":"		; is it a disk name?
	JR	NZ,NODSK	; no, just a name
	LD	A,(HL)		; get disk name
	AND	0FH		; to cp/m standards
	LD	(LFCBDN),A	; to fcb
	INC	HL
	INC	HL		; skip name and ":"
	DEC	C
	JR	Z,BLDRET	; quit with bad fcb
	DEC	C
	JR	Z,BLDRET	; quit with bad fcb
NODSK:
	LD	DE,LFCBFN	; place for name
	LD	A,8		; max length at this point
	CP	C		; are we ok?
	JR	Z,BLDRET	; no, so leave blank
FILELP:
	LD	A,(HL)
	CP	"."		; this is end (we ignore)
	JR	Z,BLDRET
	CP	" "		; also end
	JR	Z,BLDRET	; and this
	CP	"a"		; lower case alpha?
	JR	C,FILEL1	; no
	AND	5FH		; make upper
FILEL1:
	LD	(DE),A		; put in fcb
	INC	DE
	INC	HL
	DEC	C
	JR	NZ,FILELP	; loop
BLDRET:
	RET
;
;	lword
;
;	get a lexicon word
;	carry flag on if end of lexicon
;
LWORD:
	LD	DE,0		; length of word
LWORDL:
	CALL	LCHAR		; get char from file
	JR	C,LWORDR	; if end
	CP	LF		; skip these if present
	JR	Z,LWORDL
	CP	" "
	JR	Z,LWORDL	; skip blanks in lexicon
	CP	CR		; end of word
	JR	Z,LWORDE	; done
	CP	1AH		; end
	JR	Z,LWORDF	; set carry and return
	CP	"a"		; lower case?
	JR	C,LWORDU	; no, upper
	CP	"{"
	JR	NC,LWORDU
	AND	5FH		; make sure upper case
LWORDU:
	LD	HL,CWORD+4	; place to put it
	ADD	HL,DE
	LD	(HL),A		; build word
	INC	E		; bump count
	LD	A,E
	LD	(CLEN),A
	CP	30		; how long?
	JR	Z,LWORDT	; too long
	JP	LWORDL		; get more bytes
LWORDE:
	LD	A,E		; check for null word
	CP	0		; any so far?
	JR	Z,LWORDL	; no, so continue
	OR	A		; clear carry
LWORDR:
	RET
LWORDF:
	SCF
	JP	LWORDR		; return
LWORDT:
	LD	DE,LNGLX1	; first part
	LD	C,PSTRING
	CALL	BDOS
	LD	DE,CWORD+4
	LD	C,PSTRING
	CALL	BDOS
	LD	DE,LNGLX2	; second part
	LD	C,PSTRING
	CALL	BDOS
	OR	A
	JP	LWORDR
;
;	lchar
;
;	get a character from lexicon (compacted or not)
;
LCHAR:
	LD	A,(COMP)	; is it a compacted lexicon?
	CP	0		; well?
	JR	NZ,LCHARC	; yes
	CALL	LBYTE		; no, get a byte
	RET			; and return
;
LCHARC:
	CALL	GNIB		; get a nibble
	JR	C,LCHARE	; end already
	CP	0FH		; is it a flag?
	JR	Z,LCHARS	; yes, second set of letters
	LD	C,16		; size of table
	LD	HL,T1		; in table one
LCHAR1:
	CP	C
	JR	NC,LCHARE	; too big
	LD	B,0
	LD	C,A
	ADD	HL,BC
	JP	LCHARG		; got it
LCHARE:
	LD	DE,BADLEX
	LD	C,PSTRING
	CALL	BDOS
	SCF
	RET			; say end of lexicon
LCHARS:
	CALL	GNIB
	JR	C,LCHARE
	LD	C,14		; search length
	LD	HL,T2
	JP	LCHAR1		; loop here
LCHARG:
	LD	A,(HL)
	OR	A		; clear carry
	RET
;
;	gnib
;
;	get a nibble from compacted lexicon
;
GNIB:
	LD	A,(LRNIB)
	CP	1		; left or right?
	JR	Z,GNIBR		; right
	LD	A,1
	LD	(LRNIB),A
	CALL	LBYTE		; get a byte
	JR	C,GNIBR		; report carry
	LD	(BYTE),A
	SRL	A
	SRL	A
	SRL	A
	SRL	A		; put left in lower
	OR	A		; clear carry
	RET
GNIBR:
	LD	A,0
	LD	(LRNIB),A
	LD	A,(BYTE)
	AND	0FH
	RET
;
;	lbyte
;
;	get a byte from lexicon file
;	carry flag on for end of file
;
LBYTE:
	PUSH	DE
	LD	A,(IBPL)	; get buffer pointer
	CP	128		; at end?
	JR	NZ,LBYTE1	; no
	LD	DE,LFCB		; fcb for lexicon
	LD	C,READF
	CALL	BDOS
	CP	0		; did it work?
	SCF			; in case not
	JR	NZ,LBYTER	; return with carry if end
LBYTE1:
	LD	E,A		; position in buffer
	LD	D,0
	LD	HL,BUFF2
	ADD	HL,DE		; correct byte
	INC	A		; for next time
	LD	(IBPL),A
	LD	A,(HL)		; get the byte
	OR	A		; clear carry
LBYTER:
	POP	DE
	RET
;
;	count words
;
COUNTW:
	LD	HL,NUMWDC	; get lowest byte
	LD	A,":"		; a test for too large
COUNTL:
	INC	(HL)
	CP	(HL)		; see if too big
	RET	NZ		; no
	LD	(HL),"0"	; yes, set to 0
	DEC	HL
	JP	COUNTL		; backup and try again
;
;	zchn
;
;	zero chain headers
;
ZCHN:
	LD	A,0		; get a zero
	LD	C,54		; number
	LD	HL,ALPHC	; place
ZCHNL:
	LD	(HL),0
	INC	HL
	DEC	C
	JR	NZ,ZCHNL
	RET
;
;	getchn
;
;	get address of chain head of word pointed to by ix
;
GETCHN:
	LD	A,(IX+WORD)	; first char
	LD	B,0
	LD	HL,ALPHC	; first chain head
	CP	"A"		; first
	JR	C,CHNOTH	; lower use other
	CP	"["
	JR	NC,CHNOTH	; greater use other
GETCHA:
	AND	1FH		; mask
	DEC	A
	SLA	A		; double it
	LD	C,A		; displacement
	ADD	HL,BC
	RET
CHNOTH:
	LD	A,"["
	JP	GETCHA		; use last chain
;
;	setchn
;
;	scans word list and rechains it by letter
;
SETCHN:
	LD	IX,WORDS	; place to start
SETCH0:
	CALL	GETCHN		; get the correct header
	LD	A,0		; get a zero
SETCHL:
	INC	HL		; to high order byte
	CP	(HL)
	JR	NZ,NXTCHN	; not this one
	LD	(WORK),IX	; goes here
	LD	DE,(WORK)	; get it
	LD	(HL),d
	DEC	HL
	LD	(HL),E
	LD	(IX+WCHN),A	; zero forward
	LD	(IX+WCHN1),A
	JP	SETCHW		; next word
NXTCHN:
	LD	D,(HL)
	DEC	HL
	LD	E,(HL)
	EX	DE,HL
	INC	HL
	INC	HL		; to chain portion of word
	JP	SETCHL
SETCHW:
	BIT	WFLGSL,(IX+WFLGS)
	JR	NZ,SETCHR	; return
	LD	A,(IX+WLEN)
	ADD	A,4
	LD	C,A
	LD	B,0
	ADD	IX,BC
	JP	SETCH0
SETCHR:
	RET
;
;	output
;
;	create or open output file for unmatched words
;
OUTPUT:
	LD	A,(FOPFLG)	; is file output active?
	OR	A
	JP	NZ,PTABLE	; no, begin console output
	LD	DE,OUTBUF	; set dma for output buffer
	LD	C,SETDMA
	CALL	BDOS
;
;	first pass on file append
;	prepare SPELL.LEX to receive new or appended output
;
	LD	DE,OUTFCB	; does file already exist?
	LD	C,SRCHF
	PUSH	DE
	CALL	BDOS
	POP	DE
	INC	A
	JR	NZ,OPENIT	; yes, open it for processing
	LD	C,MAKEF
	CALL	BDOS		; no, create the output file
;
	INC	A
	JP	NZ,PTABLE	; continue if open successful
;
;	if make or open fails, declare error
;
OPNERR:
	CALL	ERXIT
	DB	CR,LF,"OPEN$"
;
WRTERR:
	CALL	ERXIT
	DB	CR,LF,"WRITE$"
;
;	openit
;
;	output file already exists - open it and position to
;	the last record of the last extent
;
OPENIT:
	LD	C,OPENF
	PUSH	DE
	CALL	BDOS		; open 1st extent of output file
	POP	DE
	INC	A
	JR	Z,OPNERR	; bad deal if 1st won't open
OPNMOR:
	LD	A,(OUTFCB+15)
	CP	128
	JR	C,RDLAST	; if rc <128, this is last extent
	LD	HL,OUTFCB+12
	INC	(HL)		; else, bump to next extent
	LD	C,OPENF
	PUSH	DE
	PUSH	HL
	CALL	BDOS		; and try to open it
	POP	HL
	POP	DE
	INC	A
	JR	NZ,OPNMOR	; open extents until no more
	DEC	(HL)		; then, reopen preceding extent
	LD	C,OPENF
	PUSH	DE
	CALL	BDOS
	POP	DE
	LD	A,(OUTFCB+15)	; get rc for the last extent
;
;	rdlast
;
;	at this point, outfcb is opened to the last extent of
;	the file, so read in the last record of the last extent
;
RDLAST:
	OR	A		; is this extent empty?
	JR	Z,PTABLE	; yes, start a clean slate
	DEC	A		; normalize record count
	LD	(OUTFCB+32),A	; set record number to read
	LD	C,READF
	PUSH	DE
	CALL	BDOS		; and read last record of file
	POP	DE
	OR	A		; was read successful?
	JR	Z,RDOK		; yes, go scan for eof mark
;
;	if read or append fails, declare error
;
APERR:
	CALL	ERXIT
	DB	CR,LF,"APPEND$"
;
;	rdok
;
;	we now have the last record of the file in our buffer
;
;	scan the last record for the eof mark, indicating where
;	we can start adding data
;
RDOK:
	LD	HL,OUTBUF	; point to start of output buffer
	LD	B,128		; get length of output buffer
SCAN:
	LD	A,(HL)
	CP	"Z"-40H		; have we found end of file?
	JR	Z,RESCR		; yes, save pointers and reset cr
	INC	HL
	DEC	B
	JR	NZ,SCAN		; no, keep looking til end of buffer
;
;	rescr	reset current record
;
;	if we find an explicit eof mark in the last buffer (or an
;	implied eof if the last record is full), move the fcb record
;	and extent pointers back to correct for the read operation
;	so that our first write operation will effectively replace
;	the last record of the spell.lex file
;
RESCR:
	PUSH	HL		; save eof buffer pointer
	PUSH	BC		; save eof buffer remaining
	LD	HL,OUTFCB+32	; get current record again
	DEC	(HL)		; dock it
	JP	P,SAMEXT	; if cr >=0, still in same extent
	LD	HL,OUTFCB+12	; else, move to previous extent
	DEC	(HL)
	LD	C,OPENF
	CALL	BDOS		; then, reopen the previous extent
	INC	A
	JR	Z,APERR		; append error if we can't reopen
	LD	A,(OUTFCB+15)	; position to last record of extent
	DEC	A
	LD	(OUTFCB+32),A
SAMEXT:
	POP	AF		; recall where eof is in buffer
	LD	(BUFCNT),A	; and set buffer counter
	POP	HL		; recall next buffer pointer
	LD	(BUFPNT),HL	; set pointer for first addition
;
;	ptable
;
;	print misspelled words from list
;
PTABLE:
	LD	B,0
	LD	IX,WORDS	; start
PTLOOP:
	BIT	WFLGSC,(IX+WFLGS) ; is this one correct?
	JR	NZ,PNEXT	; yes, don't print it
	CALL	PWORD		; print the word
PNEXT:
	BIT	WFLGSL,(IX+WFLGS)
	JR	NZ,PTABR
	LD	A,(IX+WLEN)	; get length this entry
	ADD	A,4
	LD	C,A
	ADD	IX,BC
	JP	PTLOOP		; try again
PTABR:
	RET
;
;	pword
;
;	print word pointed to by ix
;
PWORD:
	PUSH	IX
	LD	B,(IX+WLEN)
PWLOOP:
	LD	E,(IX+WORD)	; a character
	CALL	TYPE
	DEC	B
	JR	Z,CRLF
	INC	IX		; next character
	JP	PWLOOP
CRLF:
	LD	E,CR
	CALL	TYPE
	LD	E,LF
	CALL	TYPE
	POP	IX
	RET
;
;	type
;
;	output character in e to console and (optionally) to
;	output file and/or to printer
;
TYPE:
	PUSH	BC
	PUSH	DE		; save the character to output
	LD	C,PCHAR
	CALL	BDOS		; send it to console
	POP	DE		; restore the output character
	LD	B,E		; save character to b
	LD	A,(FOPFLG)	; is file output active?
	OR	A
	JR	NZ,NOWRIT	; no, bypass file output
;
;	file output mode active
;
;	make sure we have room in buffer to add next character
;
;	if buffer full, write out current record first and then
;	start a new record with current character
;
	LD	HL,(BUFPNT)	; get current buffer pointer
	LD	A,(BUFCNT)	; get buffer capacity remaining
	OR	A
	JR	NZ,PUTBUF	; continue if buffer not full
	LD	DE,OUTFCB	; otherwise, write current buffer
	LD	C,WRITEF
	PUSH	BC
	CALL	BDOS		; (call must save character in b)
	POP	BC
	OR	A
	JP	NZ,WRTERR	; error exit if disk full or r/o
	LD	HL,OUTBUF	; reset buffer pointer
	LD	A,128		; reset buffer capacity
;
PUTBUF:
	LD	(HL),B		; shove char to next buffer position
	INC	HL		; bump buffer pointer
	LD	(BUFPNT),HL	; and save it
	DEC	A		; dock count of chars left in buffer
	LD	(BUFCNT),A	; and save it
NOWRIT:
	LD	E,B
	LD	C,LISTC		; set up list output call
	LD	A,(POPFLG)	; is printer output active?
	OR	A
	CALL	Z,BDOS		; yes, list character on printer
	POP	BC
	RET
;
;	clzout
;
;	we've finished all of our outputting
;	flush the remainder of the output buffer and close the
;	file before making our exit
;
CLZOUT:
	LD	A,(FOPFLG)	; is file output active?
	OR	A
	JP	NZ,BOOT		; no, exit from program
	LD	HL,BUFCNT
	LD	A,(HL)		; get # of unflushed chars in buffer
	OR	A		; if bufcnt=128, empty so set sign bit
	JP	M,CLOZE		; close spell.lex if buffer is empty
	JR	Z,FLUSH		; write last record if buffer full
;
	LD	HL,(BUFPNT)	; else, pad unused buffer with ctrl-zs
PUTAGN:
	LD	(HL),"Z"-40H
	INC	HL
	DEC	A
	JR	NZ,PUTAGN	; continue until buffer filled out
;
FLUSH:
	LD	DE,OUTFCB	; flush the last output buffer
	LD	C,WRITEF
	CALL	BDOS
	OR	A
	JP	NZ,WRTERR
CLOZE:
	LD	DE,OUTFCB	; close the output file
	LD	C,CLOSEF
	CALL	BDOS
	JP	BOOT		; exit
;
;	erxit
;
;	abort program on output file error and define error
;
ERXIT:
	POP	DE		; get pointer to message string
	LD	C,PSTRING
	CALL	BDOS		; print it
	LD	DE,DSKERR	; print " ERROR"
	LD	C,PSTRING
	CALL	BDOS
	JP	BOOT		; exit
;
;
	DS	64
STACK:	DS	1
ENDMEM:	DS	2
DEFFCB:	DB	0,"        LEX",0,0,0,0
LFCB:	DS	33
LFCBCR	EQU	LFCB+32
LFCBEX	EQU	LFCB+12
LFCBS1	EQU	LFCB+13
LFCBS2	EQU	LFCB+14
LFCBRC	EQU	LFCB+15
LFCBDN	EQU	LFCB+0
LFCBFN	EQU	LFCB+1
LFCBFT	EQU	LFCB+9
IBP:	DS	1
IBPL:	DS	1
WORK:	DS	2
BYTE:	DS	1
LRNIB:	DB	0
COMP:	DB	0
BUFF2:	DS	128
ZCOUNT:	DB	"0000  "
NUMWD:	DB	"0000   distinct words in text.",CR,LF,"$"
NUMWDC	EQU	NUMWD+3
LNGLX1:	DB	"Lexicon word '$"
LNGLX2:	DB	"' longer than 29 characters.",CR,LF,"$"
LNGWD1:	DB	"Text word '$"
BADLEX:	DB	"Error in compacted lexicon.",CR,LF,"$"
NINPUT:	DB	"Input file not specified or non-existant.",CR,LF,"$"
NOLEX:	DB	CR,LF,"Lexicon file not specified or non-existant."
	DB	CR,LF,"$"
CHECKM:	DB	CR,LF,"Begin spelling check pass...",CR,LF,"$"
ASKLEX:	DB	"Enter lexicon file name (.LEX assumed) or 'return' "
	DB	BELL,CR,LF,"$"
COPYR:	DB	CR,LF,"Poor Person Speller (c) 1981, Alan Bomberger"
	DB	CR,LF,CR,LF,"$"
CWORD:	DS	34
	DB	"$"
CFLAGS	EQU	CWORD
CLEN	EQU	CWORD+1
CCHN	EQU	CWORD+2
CCHN1	EQU	CWORD+3
WFLGS	EQU	0
WLEN	EQU	1
WCHN	EQU	2
WCHN1	EQU	3
WORD	EQU	4
WFLGSL	EQU	7
WFLGSC	EQU	6
WFLGSP	EQU	5
;
FOPFLG:	DB	"F"		; file output option flag
POPFLG:	DB	"P"		; printer output option flag
;
BUFPNT: DW	OUTBUF		; next location in output buffer
BUFCNT: DB	128		; number bytes left in output buffer
OUTFCB:	DB	0,"SPELL   LEX"
	DB	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
OUTBUF:	DS	128		; output file buffer
DSKERR: DB	" ERROR",CR,LF,"$"
;
DELIMT:	DB	" .,:;'""-?!/()[]{}",CR,LF,9
	DB	0,0,0,0,0,0,0,0
DELIML:	DB	DELIML-DELIMT-8,0
ALPHA:	DB	"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
	DB	"abcdefghijklmnopqrstuvwxyz"
	DB	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
ALPHAL:	DB	ALPHAL-ALPHA-20,0
T1:	DB	"EISNATR"
	DB	"OLDCUGP",CR
T2:	DB	"MHBYFVW"
	DB	"KZXQJ",1AH
ALPHC:	DS	54
WORDS:
COMWDL:	DB	192,1,0,0,"A"

	END	100H
