	PAGE 59, 132

	TITLE MsScript -- Module to perform script files for MS-Kermit

; Update 25 Jan 86

IF1
 %OUT >> Starting pass 1
ELSE
 %OUT >> Starting pass 2
ENDIF

    PUBLIC Script_File, Do_Script, Get_a_char
    PUBLIC Our_entry_point, Do_next_script_command

;***************************************************************************

; *Definitions*

; Global defs

	INCLUDE MsDefs.H

;***************************************************************************

; *Data*

DataS	SEGMENT PUBLIC 'DataS'

    EXTRN Comand:BYTE, Flags:BYTE, FCB:BYTE, MccTab:BYTE, TakLev:BYTE
    EXTRN MacTab:WORD, TakAdr:WORD, PortVal:WORD, Allow_blast:BYTE

; Fixed data, unchanging

erms30	DB '? Passed maximum nesting level',Cr,Lf,'$'
erms31	DB '? Script file not found',Cr,Lf,'$'
FilMsg	DB ' File specification with optional path name $'

    PUBLIC SCR_Send, SCR_SendCr, SCR_SendBr, SCR_Sleep, SCR_Timer
    PUBLIC SCR_Wait

Script_keywords DB 27
	mkeyw 'ASK', SCR_Ask
	mkeyw 'BACKTO', SCR_Backto
	mkeyw 'CALL', SCR_Call
	mkeyw 'CHAIN', SCR_Chain
	mkeyw 'CLOSE', SCR_Close
	mkeyw 'COMMAND-MODE', SCR_Command_Mode
	mkeyw 'CR', SCR_SendCr
	mkeyw 'EXIT-TO-DOS', SCR_Exit_to_DOS
	mkeyw 'GOTO', SCR_Goto
	mkeyw 'HSLEEP', SCR_HSleep
	mkeyw 'HTIMER', SCR_HTimer
	mkeyw 'IF', SCR_If
	mkeyw 'LET', SCR_Let
	mkeyw 'LISTEN', SCR_Listen
	mkeyw 'LWAIT', SCR_LWait
	mkeyw 'NASK', SCR_NAsk
	mkeyw 'OPEN', SCR_Open
	mkeyw 'PRINT', SCR_Print
	mkeyw 'PRINTCR', SCR_PrintCr
	mkeyw 'SEND', SCR_Send
	mkeyw 'SENDBR', SCR_SendBr
	mkeyw 'SENDCR', SCR_SendCr
	mkeyw 'SLEEP', SCR_Sleep
	mkeyw 'TERMINAL-MODE', SCR_Terminal_Mode
	mkeyw 'TIMER', SCR_Timer
	mkeyw 'WAIT', SCR_Wait
	mkeyw 'WRITE', SCR_Write

CrLf	DB Cr, Lf, '$'

; Changable data

	EVEN

Error_flags DW 0		; Place to store some flags
 Noparse EQU 1			;  Flag for parse error in script file
 Eof	 EQU 2			;  Flag for end-of-file on script file

Reentry	DW ?			; Address at which to reenter script processor
Hold_bx	DW ?			; Place to save bx
Hold_di	DW ?			; Place to save di
Ret_addr DW ?			; Place to hold a return address
Jmp_addr DW ?			; Place to hold a jump address

Hold_ptr DW ?			; Place to hold a pointer
Which_item DW ?			; Which item are we looking at?
Which_macro DW ?		; Number of the macro we are working on
Which_routine DW ?		; Address of routine to call with char
				;  from string (SEND, PRINT, etc.)

HrMn	DW ?			; Hours and minutes
ScHn	DW ?			; Seconds and hundredths

Fname_ptr DW ?			; Ptr into filename
File_handle DW 0		; Handle of file we are writing to, or zero
Var_ptr	DW 0			; Address of where in variable we are

SCR_CMD	CmdInfo <>		; Our own command processor

Fname	DB 80 DUP (?)		; Place to store filename

Script_nesting_level DB 0	; Not nested yet
Listening DB 0			; We are recording host chars
Waiting DB 0			; We are waiting on sequences of host chars
Label_flag DB 0			; The token we just read was a label
Hundredths DB 0			; Flag that timing is in hundredths of seconds

Token	DB 32 DUP (?)		; Place to put a parsed token
Target	DB 32 DUP (?)		; Place to put a parsed target of a Goto
What_satisfied_wait DB 0	; Number of item which satisfied wait
Ask_echo_flag DB ?		; Supposed to echo what is typed?
Var_cnt	DB ?			; Number of characters remaining in variable
Backup	DB 0FFh			; Character to be reread, if not 0FFh
Timer_is_set DB 0		; Flag to say the timer is set
In_LWAIT DB 0			; We are in LWAIT (not WAIT)
Equal	DB 0FFh			; Variable equals string
Num_seconds DB ?		; Number of seconds for SLEEP and TIMER cmds
Last_char DB ?			; Most recently seen char from script file
Delimiter DB ?			; Place to save working string delimiter
Stay_on_line DB ?		; Flag for line lock in Do_string
Block_on_CR_only DB ?		; Flag that we want to mostly blast lines out
Kb_char	DB ?			; Character from keyboard
Tmp	DB ?			; Scratch byte

Max_vars EQU 20			; How many variables we are allowed
Var_size EQU 128		; Size of a variable entry ...
				;  32 bytes for name, 1 for len, 95 for value

Num_vars DB 0			; How many vars are already defined

Script_flags DB 0		; Internal flags

Var_database DB (Max_vars * Var_size) DUP (?) ; Where we keep the variables

Max_waiting_items EQU 10
Max_len_wait_item EQU 31

Num_Waiting_items DW	0	; Count (*2) of texts we are waiting on

WAIT_addr	DW	Max_waiting_items DUP (?)
WAIT_len	DW	Max_waiting_items DUP (?)

WAIT_table	DB	(Max_waiting_items*Max_len_wait_item)/2 DUP (?)
Size_of_ring EQU 255
Ring		DB	Size_of_ring DUP (?)

DataS	ENDS

;***************************************************************************

; *Code*

RetSkp	MACRO			; Return skip
	jmp RSkp
	ENDM

RetNop	MACRO			; Return from subroutine, take up 3 bytes
	 ret
	 nop
	 nop
	ENDM


Code	SEGMENT PUBLIC		; Code segment

    EXTRN Comnd:NEAR, Set_up_script_processor:NEAR, Telnet2:NEAR
    EXTRN TakRd:NEAR, OutChr:NEAR, Beep:NEAR, SendBr:NEAR
    EXTRN Simulate_port_char:NEAR, Type_to_screen:NEAR
    EXTRN Setup_take_file:NEAR, Quit_Emulator:BYTE

    ASSUME cs:Code, ds:DataS, es:DataS


; Handle script files

Script_File PROC
	mov Script_nesting_level, 0 ; Flag that we are not nested yet

	cmp TakLev, MaxTak	; Room in take level?
	 jl SCF_1		;  Yes, continue

	mov dx, OFFSET erms30	; Else complain
	jmp RetErr

SCF_1:	mov di, TakAdr
	add di, SIZE TakInfo
	mov Hold_di, di		; Hold di

	mov ah, CMTXT
	lea bx, [di].TakBuf	; Convenient place to parse name into
	mov dx, OFFSET FilMsg	; Help in case user types "?"
	call Comnd
	 jmp RSkp

	mov di, Hold_di		; Restore di
	call Setup_script_file	; Call routine to do the work
	 jnc SCF_Got_it		;  No error

	mov dx, OFFSET erms31
	jmp RetErr		; Type error message and return

SCF_Got_it:
	jmp Perform_the_script	; All is ok, go perform the script

Setup_script_file:
	call Setup_take_file	; Call routine to do the work
	 jc SSF_ret		;  Error

	inc Script_nesting_level ; Mark new script level

SSF_ret: ret			; Done here


Script_File ENDP


; Handle script macros

Do_Script PROC
	mov Script_nesting_level, 0 ; Flag that we are not nested yet

	cmp TakLev, MaxTak	; Room in take level?
	 jl DOS_1		;  Yes, continue

	mov dx, OFFSET erms30	; Else complain
	jmp RetErr

DOS_1:	mov dx, OFFSET MccTab	; Address of macro table
	mov bx, 0		; Use COMND type help (instant menu)
	mov ah, CMKEY		; Want to parse for a keyword
	call Comnd		; Do it
	 RetNop			;  NOP at the moment

	mov Hold_bx, bx		; Save this
	mov ah, CMCFM
	call Comnd		; Get a confirm
	 RetNop
	mov bx, Hold_bx		; Restore this
	
	inc TakLev		; Increment take level (overflow)
	add TakAdr, SIZE TakInfo ; Increment ptr to next frame

	mov Which_macro, bx	; Save the number of the macro
	call Set_up_macro	; Use subroutine to set it up

	inc Script_nesting_level ; Mark new script level

	jmp Perform_the_script	; Go do the script

Set_up_macro:
	shl bx, 1		; Convert to word offset
	mov si, MacTab[bx]	; Point to macro
	mov cl, [si]		; Get size of macro
	mov ch, 0		; Zero high half
	inc si			; Point to actual definition

	mov bx, TakAdr		; Point to current buffer
	mov [bx].TakFcb, 0FFh	; Flag as a macro
	mov [bx].TakPtr, si	; Point to beginning of def
	mov [bx].TakChl, cl	; # of chars left in buffer
	mov [bx].TakCnt, cx	;  and in definition
	mov WORD PTR [bx].TakCnt+2, 0 ; Zero high order...
	ret

Do_Script ENDP

 
Perform_the_script PROC
	mov Error_flags, 0	; Zero out any previous error flags
	mov Num_vars, 0		; Erase all variable definitions
	mov Last_char, Cr	; Note that we are at the start of a line

	mov Script_flags, 0	; No flags to start
	mov bx, portval		; Pick up ptr to port values
	cmp [bx].ecoflg, 0	; See if we are set up for echoing
	 je PTS_0		;  No

	or Script_flags, lclecho ; Flag that we are in local echo

PTS_0:	mov Reentry, OFFSET cs:Do_next_script_command ; The first rtn to call

	mov ax, OFFSET cs:Our_entry_point ; Get address of our entry point
	call Set_up_script_processor ; Tell emulator to call us (and where)

	call Telnet2		; Enter terminal emulator
	 RetNop			;  Return non-skip if he does (he doesn't)

	RetSkp			; All done


;  *****      Enter here on exit hooks from Terminal Emulator     *****

Our_entry_point:
	cld			; Forwards
	or ah, ah		; Is the code 0 (keyboard)?
	 jnz OEP_1		;  No
	mov Kb_char, al		; Save keyboard character for later
	call Reentry		; Yes, go do our stuff
	cmp Kb_char, 3		; ^C?
	 jne OEP_3		;  No, do nothing special

	jmp Shutdown		; User hit ^C, kill him

OEP_1:	cmp ah, 1		; Code 1 (host character)?
	 jne OEP_2		;  No
	jmp Handle_character	; Yes, go store it or ignore it

OEP_2:	cmp ah, 2		; Code 2 (shutdown)?
	 jne OEP_3		;  No
	jmp Shutdown		; Yes, close files and be ready to be gone

OEP_3:	ret			; Unknown, do nothing for now

Perform_the_script ENDP


;           *****       Do next command       *****

Do_next_script_command PROC
	cmp Script_nesting_level, 0 ; Do we have at least one script file open?
	 je Shutdown		;  No, shutdown the works

	call Parse_script_keyword ; Try to get a new keyword from the script
	 jmp DNS_Noparse	;  No luck

	mov Reentry, bx		; Save as address to run next time here
	ret			; Go back to emulator for a moment

DNS_Noparse:
	test Error_flags, Eof	; Hit eof?
	 jnz DNS_done		;  Yes, quit now

	cmp Label_flag, 0	; Is this a label
	 je Do_next_script_command ;  Yes, just read again

	test Error_flags, Noparse ; Is this the problem?
	 jz DNS_done		;  Maybe eof, return it

	cmp Last_char, Cr	; Already hit Cr?
	 je DNS_eol		;  Already there

DNS_Skip:
	call Get_a_char		; Get another char
	 jc DNS_done		;  Skip to eol or eof on parse error

	cmp al, Cr		; Hit return yet?
	 jne DNS_Skip		;  No

DNS_eol:
	test Error_flags, Eof	; Are we at the end?
	 jnz DNS_done		;  Eof is on, just return

	jmp Do_next_script_command ; Try for another

    PUBLIC DNS_done, SCR_Call, DNS_close

DNS_done:
	cmp Script_nesting_level, 1 ; Now finishing outermost script?
	 ja DNS_close		;  No, just close this one file

Shutdown:
	sub ax, ax		; Make a zero
	call Set_up_script_processor ; Make emulator forget us

	cmp File_handle, 0	; Is there an open file?
	 jz SHU_0		;  No

	mov ah, 3Eh		; Code to close a file
	mov bx, File_handle	; Handle
	int Dos			; Close the file

	mov File_handle, 0	; Clear it out

SHU_0:	cmp Script_nesting_level, 0 ; Any files left to close?
	 je DNS_ret		;  No, already finished

	call DNS_close		; Close another one
	jmp SHORT SHU_0		; Shut down the rest

DNS_close:
	dec Script_nesting_level ; Drop nesting level by 1

	mov bx, TakAdr		; Addr of TAKE frame
	mov al, BYTE PTR [bx].TakFcb ; Get first byte of fcb
	cmp al, 0FFh		; Is it really a macro?
	 je DNS_4		;  Yes, better not try to close it

	cmp al, 0FEh		; Or maybe a file handle?
	 je DNS_3		;  Yes, close w/2.0 call

	mov ah, CLOSF		; Close file the FCB way
	lea dx, [bx].TakFcb
	int Dos

	jmp SHORT DNS_4		; Skip over alternate close

DNS_3:	mov ah, CLOSE2		; Close file the file handle way
	mov bx, WORD PTR [bx].TakFcb+1 ; This is where file handle is stored
	int Dos

DNS_4:	dec TakLev
	sub TakAdr, SIZE TakInfo
	and Error_flags, NOT Eof ; Clear the EOF flag in case nested

DNS_ret: ret

Do_next_script_command ENDP


Other_rtns PROC

; Script file functional routines

SCR_Command_Mode:
	mov Quit_Emulator, 0FFh	; Tell emulator to shut down
	jmp Shutdown		; Shut down the works

SCR_Exit_to_DOS:
	mov Quit_Emulator, 0FFh	; Tell emulator to shut down
	mov Flags.ExtFlg, 0FFh	; Tell Command Mode to Exit
	jmp Shutdown		; Shut down the works

SCR_Call:
	mov Which_routine, OFFSET cs:SCC_Store_char ; Store chars for filename
	mov Fname_ptr, OFFSET Fname ; Store initial filename ptr
	call Do_string_but_stay_on_line ; Call routine to do the work

	call Load_token_on_same_line ; See if there is a requested label

	mov cx, SIZE Token	; Number of bytes to copy
	mov si, OFFSET Token
	mov di, OFFSET Target
	rep movsb		; Copy the token to a safe storage place

SCR_New_script_file:
	mov cx, Fname_ptr	; Ptr to first space after file name
	sub cx, OFFSET Fname	; Subtract starting ptr, gives length
	mov ah, cl		; Set up length in ah also

	mov di, TakAdr
	add di, SIZE TakInfo
	mov Hold_di, di		; Hold di
	mov si, OFFSET Fname	; Ptr to source
	lea di, [di].TakBuf	; Pick up addr of buffer
	rep movsb		; Copy file name into Take buffer
	mov di, Hold_di		; Get back saved di

	call Setup_script_file	; Set up the file, if possible
	cmp Target, ' '		; Was a target set up?
	 je SNS_end		;  No

	call Do_GoTo		; Move to that place in the file

SNS_end: jmp Set_up_next_command ; Do this win or lose

SCC_Store_char:
	mov di, Fname_ptr	; Pick up filename pointer
	stosb			; Store this character
	mov Fname_ptr, di	; Store updated ptr
	ret			; That's it

SCR_Chain:
	mov Which_routine, OFFSET cs:SCC_Store_char ; Store chars for filename
	mov Fname_ptr, OFFSET Fname ; Store initial filename ptr
	call Do_string_but_stay_on_line ; Call routine to do the work

	call Load_token_on_same_line ; See if there is a requested label

	mov cx, SIZE Token	; Number of bytes to copy
	mov si, OFFSET Token
	mov di, OFFSET Target
	rep movsb		; Copy the token to a safe storage place

	call DNS_close		; Close out the current file
	jmp SCR_New_script_file	; Go do a new script file

SCR_Ask:
	call Load_token		; Pick up a variable name
	cmp Token, "%"		; Does var name begin with "%"?
	 jne Flush_command	;  No, don't do this command

	mov Which_routine, OFFSET cs:Type_to_screen
				; Want chars to go to the screen
	mov Block_on_CR_only, 1	; Flag that we want to mostly blast lines out
	call Do_string		; Call routine to do the work
	mov Block_on_CR_only, 0	; Clear it
	mov Ask_echo_flag, 0FFh	; Flag that we want echo
	call Do_ask		; Get user's response to what we typed
	jmp Set_up_next_command

SCR_NAsk:
	call Load_token		; Pick up a variable name
	cmp Token, "%"		; Does var name begin with "%"?
	 jne Flush_command	;  No, don't do this command

	mov Which_routine, OFFSET cs:Type_to_screen
				; Want chars to go to the screen
	mov Block_on_CR_only, 1	; Flag that we want to mostly blast lines out
	call Do_string		; Call routine to do the work
	mov Block_on_CR_only, 0	; Undo it
	mov Ask_echo_flag, 0	; Flag that we don't want echo
	call Do_ask		; Get user's response to what we typed
	jmp Set_up_next_command

Flush_command:
	call Skip_to_EOL	; Skip rest of command
	jmp Set_up_next_command

; Find_var_in_table -- try to find a variable name in the Var_database
;
; Returns with:
;	carry/ on if entry could NOT be found in table
;
;	     / off if entry was found, with ...
;	bx/ address of length byte for var's value (value follows it)

Find_var_in_table:
	sub dl, dl		; Make a zero

FVT_1:	inc dl			; Move to next var
	cmp dl, Num_vars	; Do we have this many?
	 jg FVT_fail		;  No, didn't find it

	mov al, dl		; Copy to al
	dec al			; Drop by one to make index
	mov bl, Var_size	; Get the size of a variable
	mul bl			; Index to the start of the entry
	add ax, OFFSET Var_database ; Create real addr of this var slot
	mov si, OFFSET Token	; Compare the token
	mov di, ax		;  to the slot we just found
	mov cx, SIZE Token	; This many chars
	repe cmpsb		; Compare the var name with the table
	 jne FVT_1		;  Not the one, try the next

	mov bx, di		; Save address of length byte
	clc			; Mark that we found it
	ret			; Done here

FVT_fail:
	stc			; Flag that var name was NOT found
	ret			; Return

	PUBLIC SCR_Ask, Do_Ask, New_var, FVT_fail, VAR_merge, Find_var_in_table

Do_Ask:	call Find_var_in_table	; First see if this one was already defined
	 jc New_var		;  Not found, try to create it

	mov di, bx		; Copy address of length byte to di
	jmp SHORT Var_merge	; Join common code	

New_var:
	cmp Num_vars, Max_vars	; Defined all we are allowed?
	 jge R5			;  Yes, just give up

	mov al, Num_vars	; Pick up current value
	inc Num_vars		; Advance to next one
	mov bl, Var_size	; Get the size of a variable
	mul bl			; Index to the start of the entry
	add ax, OFFSET Var_database ; Create real addr of this var slot
	mov si, OFFSET Token	; Copy from the token
	mov di, ax		;  to the slot we just found
	mov cx, SIZE Token	; This many chars
	rep movsb		; Copy the var name (% and all) into our table

	mov bx, di		; Save address of length byte

Var_merge:
	sub al, al		; Make a zero
	stosb			; Store it as the length of the value

; This is the ASK READ loop, where we wake up with or without a character,
;  process the character if any, and then go back to sleep

DAS_0:	pop Ret_addr		; Get back our return address
	mov Hold_bx, bx		; Save important registers
	mov Hold_di, di
	mov Reentry, OFFSET cs:DS2_reenter ; Set up to try for another char
R5:	ret			; Return to emulator now

DS2_reenter:
	push Ret_addr		; Put stack back the way we need it
	mov bx, Hold_bx		; Restore inportant registers ...
	mov di, Hold_di

	cmp al, 0FFh		; Got a character to work with?
	 je DAS_0		;  No

	cmp al, BS		; User hit backspace?
	 je Hit_BS		;  Yes

	cmp al, Del		; User hit delete?
	 je Hit_BS		;  Yes

	cmp al, Cr		; User hit return?
	 je Hit_Cr		;  Yes

	cmp al, 3		; Control-C?
	 jne DAS_1		;  No

	mov al, Cr		; Now, pretend Cr
	jmp SHORT Hit_Cr

DAS_1:	cmp BYTE PTR [bx], 95	; All allowed chars?
	 jae DAS_bad		;  Yes, beep and ignore this one

	stosb			; Char fits, put it in our buffer
	inc BYTE PTR [bx]	; Account for this char

	cmp Ask_echo_flag, 0	; Supposed to echo this?
	 je DAS_0		;  No, go try for another char

DAS_typeit:
	call Type_to_screen	; Send this character to the screen
	jmp DAS_0		; Go read another character from user

	PUBLIC Hit_BS, DAS_bad, HBS_1, DAS_0, DAS_typeit

Hit_BS:	cmp BYTE PTR [bx], 0	; Any chars to delete?
	 jg HBS_1		;  Yes

DAS_bad:
	push bx			; Beep kills bx
	call Beep		; Make a rude noise
	pop bx			; Get it back
	jmp DAS_0		; Try again

HBS_1:	dec BYTE PTR [bx]	; Decrement the count of chars
	dec di			;  and the ptr into the value

	cmp Ask_echo_flag, 0	; Are we echoing?
	 jz DAS_0		;  No, so don't erase anything either!

	mov al, BS		; Load up a backspace
	call Type_to_screen	; Send it to the screen

	mov al, ' '		; A space
	call Type_to_screen	; Send it to the screen

	mov al, BS		; Another backspace
	jmp DAS_typeit		; Join common code

Hit_Cr:	jmp Type_to_screen	; User finally hit return, done with this ASK
				;  Be sure to echo the Cr(Lf) to the screen


; Routines to write to disk files

SCR_Open:
	cmp File_handle, 0	; Is there an open file?
	 jz SOP_Tag		;  No

	mov ah, 3Eh		; Code to close a file
	mov bx, File_handle	; Handle
	int Dos			; Close the file

	mov File_handle, 0	; Clear it out

SOP_Tag:
	mov Which_routine, OFFSET cs:SCC_Store_char ; Store chars for filename
	mov Fname_ptr, OFFSET Fname ; Store initial filename ptr
	call Do_string_but_stay_on_line ; Call routine to do the work

	mov ah, 3Ch		; Code to create a file
	mov dx, OFFSET Fname	; Ptr to filename
	int Dos			; Create the file
	 jc SOP_Failed		;  No

	mov File_handle, ax	; Save handle for later

SOP_Failed:
	jmp Set_up_next_command

SCR_Close:
	cmp File_handle, 0	; Is there an open file?
	 jz SCL_Tag		;  No

	mov ah, 3Eh		; Code to close a file
	mov bx, File_handle	; Handle
	int Dos			; Close the file

	mov File_handle, 0	; Clear it out

SCL_Tag:
	jmp Set_up_next_command

SCR_Write:
	mov Which_routine, OFFSET cs:SWR_Write_char ; Write chars to file
	call Do_string		; Call routine to do the work
	jmp Set_up_next_command

SWR_Write_char:
	cmp File_handle, 0	; Make sure there is a file open
	 jz SWR_Bye		;  None, quit

	cmp al, Cr		; Is this a Cr?
	 jne SWR_Not_Cr		;  No

	mov cx, 2		; Want to type CrLf for Cr
	mov dx, OFFSET CrLf	; Where one is
	jmp SHORT SWR_Do_it	; Write it

SWR_Not_Cr:
	mov Tmp, al		; Store char in memory
	mov cx, 1		; Just one character
	mov dx, OFFSET Tmp	; Where it is

SWR_Do_it:
	mov ah, 40H		; Code to write to a file handle
	mov bx, File_handle	; Handle itself
	int Dos			; Write the character

SWR_Bye:
	ret			; Done here

SCR_Print:
	mov Block_on_CR_only, 1	; Flag that we want to mostly blast lines out
	mov Allow_blast, 1	; Flag that we want to mostly blast lines out
	mov Which_routine, OFFSET cs:Type_to_screen
				; Want chars to go to the screen
	call Do_string		; Call routine to do the work
	mov Block_on_CR_only, 0	; Back to normal
	mov Allow_blast, 0	; Back to normal
	jmp Set_up_next_command

SCR_PrintCr:
	mov Block_on_CR_only, 1	; Flag that we want to mostly blast lines out
	mov Allow_blast, 1	; Flag that we want to mostly blast lines out
	mov Which_routine, OFFSET cs:Type_to_screen
				; Want chars to go to the screen
	call Do_string		; Call routine to do the work
	mov al, Cr		; Get a Cr
	call Type_to_screen	; Put it on the screen
	mov Block_on_CR_only, 0	; Back to normal
	mov Allow_blast, 0	; Back to normal
	jmp Set_up_next_command

SCR_Send:
	mov Which_routine, OFFSET cs:Send_out_port ; Sending out the port
	call Do_string		; Call routine to do the work
	jmp Set_up_next_command

SCR_SendCr:
	mov Which_routine, OFFSET cs:Send_out_port ; Sending out the port
	call Do_string		; Use other routine to do most of the work
	mov ah, Cr		; Get a Cr
	call OutChr		; Send it
	 nop
	 nop
	 nop

	jmp Set_up_next_command

R1:	ret

Send_out_port:
	test Script_flags, lclecho ; Local echo on?
	 jz SOP_Noecho		;  No

	call Type_to_screen	; Echo every character immediately

SOP_Noecho:
	jmp Outchr		; Send char to the port

    PUBLIC Do_string, Do_string_but_stay_on_line

Do_string:
	mov Stay_on_line, 0	; Don't lock on this line
	jmp Do_string_common	; Join common code

Do_string_but_stay_on_line:
	mov Stay_on_line, 1	; Lock onto this line
;	jmp Do_string_common	; Join common code

Do_string_common:
	call Is_EOL		; Is this the end of the line?
	 je R1			;  Yes, means we didn't get a string to send

	call Get_a_char		; Get the next character
	 jc R1			;  Can't, bomb

	call Is_terminator	; Is this a terminator?
	 je Do_string_but_stay_on_line ;  Yes, try for another

	mov Delimiter, al	; Remember it

DST_lp:	call Get_a_char
	 jc R1

	cmp al, Delimiter	; The ending character?
	 je DST_Skip_CrLf	;  Yes, done here

; Special handling, ^E, ^e, etc.

	cmp al, '^'		; Up arrow?
	 jne DST_not_up_arrow	;  No

	call Get_a_char		; Acts as control- quoting, read a 2nd char
	 jc R1

	cmp al, '^'		; Second up-arrow?
	 je DST_Not_up_arrow	;  Yes, don't controlify

	and al, 1Fh		; Mask anything else into control-char range

DST_not_up_arrow:
	cmp al, "%"		; Possible start of variable?
	 jne DST_not_percent	;  No

	mov Backup, al		; Set up to reread this char
	call Load_token		; Use standard routine to read var name

	call Find_var_in_table	; See if there is such an entry
	 jc DST_percent_only	;  None, type a percent and move on

	mov al, [bx]		; Pick up byte count
	or al, al		; Any chars?
	 je DST_lp		;  No

	sub ah, ah		; Full word
	mov cx, ax		; Copy to loop counter
	mov si, bx		; Copy address to si
	inc si			; Make it point at the text

DST_lp2:
	lodsb			; Pick up the next byte
	mov ah, al		; Copy to ah (for OutChr)
	push cx			; Save some regs ...
	push si
	call Which_routine	; Send it along, either to port or screen
	 nop			;  Ignore error
	 nop
	 nop

	pop si			; Restore regs
	pop cx
	loop DST_lp2		; Do the next char in the value, if any
	jmp DST_lp		; See if more chars in string

DST_percent_only:
	mov al, "%"		; Keep going, but just type a percent sign

DST_not_percent:
	mov ah, al		; Copy to ah (for OutChr)
	push ax			; Save reg
	call Which_routine	; Send char along, either to port or screen
	 nop			;  Ignore error
	 nop
	 nop

	pop ax			; Restore reg
	cmp Block_on_CR_only, 0 ; CRLF only flag set?
	 jz DST_Set_up_for_reenter

	cmp al, Cr		; Hit that magic char?
	 je DST_Set_up_for_reenter ;  Yes, go back to emulator

	jmp DST_lp		; In CRLF-only mode, not a CRLF, so loop

DST_Set_up_for_reenter:
	pop Ret_addr		; Get back our return address
	mov Reentry, OFFSET cs:DST_reenter ; Set up to try for another char
	ret			; Return to emulator now

DST_reenter:
	push Ret_addr		; Put stack back the way we need it
	jmp DST_lp		; Go send another character

DST_Skip_CrLf:
	cmp Stay_on_line, 0	; Ok to span lines?
	 je DST_Skip_CrLf_OK	;  Yes

	ret			; No

DST_Skip_CrLf_OK:
	jmp Skip_to_EOL		; Use special routine to skip to EOL

SCR_SendBr:
	call SendBr		; Someone already wrote a routine to send a
				;  break, hope it works
	call Skip_to_EOL	; Get to end of line
	jmp Set_up_next_command	; Do the next command

Skip_to_EOL:
	mov al, Last_char	; Pick up last char seen
STE_0:	call Is_EOL		; Are we at the end of the line?
	 je R			;  Yes

	call Get_a_char		; Get the next character
	 jnc STE_0		;  Got one, go try it

	ret			; Done here

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

RSkp:	pop bp
	add bp,3
	push bp

R:	ret			; A popular labeled RET


; If command -- test for completion code, or compare a variable

	PUBLIC SCR_Goto, Do_Goto, DOG_eof, Skip_to_EOL, SCR_If, DO_If

SCR_If:	mov Block_on_CR_only, 1	; Flag that we want to mostly blast lines out
	call Do_If		; Call routine to do it
	mov Block_on_CR_only, 0	; Clear it
	jmp Set_up_next_command


Do_If:	call Get_a_char		; Get the next character on the line
	 jc R

	call Is_EOL		; Hit end of line?
	 je R			;  Nothing to do

	call Is_terminator	; Hit anything substantive yet?
	 je Do_If		;  No, waste time here

	mov Backup, al		; Make sure we reread this character
	cmp al, "%"		; Is it percent? (for a variable)
	 jne DIF_Nu		;  No, try it as a number

	call Load_token		; Pick up the variable name
	call Find_var_in_table	; Does it exist?
	 jc DIF_NG		;  No, assume not equal (!)

	mov al, BYTE PTR [bx]	; Pick up the length
	mov Var_cnt, al		; Save it
	inc bx			; Point past length byte
	mov Var_ptr, bx		; Save the pointer into the variable

DIF_0:	call Get_a_char		; Get the next character on the line
	 jc R

	call Is_EOL		; Hit end of line?
	 je R			;  Nothing to do

	call Is_terminator	; Hit anything substantive yet?
	 je DIF_0		;  No, waste time here

	cmp al, '='		; Equal sign?
	 jne DIF_NG		;  No, trash this line

	mov Which_routine, OFFSET cs:Compare_variable
	mov Equal, 0FFh		; Assume equal (so far)
	call Do_string		; Go do the comparison

	cmp Equal, 0FFh		; Still equal?
	 jne DIF_NG		;  Not equal

	ret			; Variable matches string, execute this command


DIF_Nu:	call Get_a_number	; Go get a number
	 jc R			;  Hit EOF

	cmp dl, What_satisfied_wait ; Is this the one?
	 je R			;  Yes!  Just return without skipping the
				;  rest of the command

DIF_NG:	jmp Skip_to_EOL		; Not our number, skip the conditional cmd


    PUBLIC Compare_variable, Equal, Var_cnt, Var_ptr

Compare_variable:
	cmp Var_cnt, 0		; Any chars left to check?
	 jle CVR_NE		;  Not equal

	dec Var_cnt		; Account for this letter

	mov bx, Var_ptr		; Pick up the pointer
	inc Var_ptr		; Move the pointer along
	cmp al, BYTE PTR [bx]	; See if these characters are equal
	 je R7			;  This one is equal, don't clear flag	

CVR_NE:	mov Equal, 0		; Strings are not the same
R7:	ret			; Done here


; Let command -- set a variable

SCR_Let:
	mov Block_on_CR_only, 1	; Flag that we want to mostly blast lines out
	call Do_Let		; Call routine to do it
	mov Block_on_CR_only, 0	; Clear it
	jmp Set_up_next_command


Do_Let:	call Get_a_char		; Get the next character on the line
	 jc R7

	call Is_EOL		; Hit end of line?
	 je R7			;  Nothing to do

	call Is_terminator	; Hit anything substantive yet?
	 je Do_Let		;  No, waste time here

	mov Backup, al		; Make sure we reread this character
	cmp al, "%"		; Is it percent? (for a variable)
	 jne DLT_NG		;  No, give up

	call Load_token		; Pick up the variable name
	call Find_var_in_table	; Does it exist?
	 jc DLT_New_var		;  Not found, try to create it

	mov di, bx		; Copy address of length byte to di
	jmp SHORT DLT_Var_merge	; Join common code	

DLT_New_var:
	cmp Num_vars, Max_vars	; Defined all we are allowed?
	 jge DLT_NG		;  Yes, just give up

	mov al, Num_vars	; Pick up current value
	inc Num_vars		; Advance to next one
	mov bl, Var_size	; Get the size of a variable
	mul bl			; Index to the start of the entry
	add ax, OFFSET Var_database ; Create real addr of this var slot
	mov si, OFFSET Token	; Copy from the token
	mov di, ax		;  to the slot we just found
	mov cx, SIZE Token	; This many chars
	rep movsb		; Copy the var name (% and all) into our table

	mov bx, di		; Save address of length byte

DLT_Var_merge:
	sub al, al		; Make a zero
	stosb			; Store it as the length of the value

	mov Var_cnt, al		; Save it here also
	mov Hold_bx, bx		; Save for later

	inc bx			; Bump to next byte
	mov Var_ptr, bx		; Save the pointer into the variable

DLT_0:	call Get_a_char		; Get the next character on the line
	 jc R7

	call Is_EOL		; Hit end of line?
	 je R7			;  Nothing to do

	call Is_terminator	; Hit anything substantive yet?
	 je DLT_0		;  No, waste time here

	cmp al, '='		; Equal sign?
	 jne DLT_NG		;  No, trash this line

	mov Which_routine, OFFSET cs:Set_variable
	call Do_string		; Go do the comparison

	mov bx, Hold_bx		; Get back saved value
	mov al, Var_cnt		; Get count of bytes in variable
	mov BYTE PTR [bx], al	; Save it for later

	ret			; Done here


DLT_NG:	jmp Skip_to_EOL		; Skip the rest of this line


;    PUBLIC Set_variable

Set_variable:
	cmp Var_cnt, 95		; Any room left?
	 jge SVA_full		;  No

	inc Var_cnt		; Account for this letter

	mov bx, Var_ptr		; Pick up the pointer
	mov BYTE PTR [bx], al	; Store this character in the buffer
	inc Var_ptr		; Move the pointer along

SVA_full:
	ret			; Done here


SCR_Goto:
	call Load_token		; Pick up the label we are to jump to

	mov cx, SIZE Token	; Number of bytes to copy
	mov si, OFFSET Token
	mov di, OFFSET Target
	rep movsb		; Copy the token to a safe storage place

	call Do_Goto		; Use subr to do it
	jmp Set_up_next_command

SCR_BackTo:
	call Load_token		; Pick up the label we are to jump to

	mov cx, SIZE Token	; Number of bytes to copy
	mov si, OFFSET Token
	mov di, OFFSET Target
	rep movsb		; Copy the token to a safe storage place

	call Go_to_start_of_file ; Reset pointers to start of macro or file

	call Do_Goto		; Use subr to do the GoTo
	jmp Set_up_next_command

Go_to_start_of_file:
	mov di, TakAdr		; Get addr of take frame
	mov al, [di].TakFcb	; Pick up possible macro flag
	cmp al, 0FFh		; Is it a macro?
	 jne GTS_0		;  No, its a file

	mov bx, Which_macro	; Get the number of the macro
	jmp Set_up_macro	; Restart it

GTS_0:	cmp al, 0FEh		; Is it a file handle type?
	 jne GTS_1		;  No, so use FCB method

	mov ax, (LSeek*256) + 2	; Seek 0 bytes from end
	mov bx, WORD PTR [di].TakFcb+1 ; Need file descriptor
	sub cx, cx
	sub dx, dx
	int Dos

	mov [di].TakCnt, ax	; Store length
	mov [di].TakCnt+2, dx	;  (doubleword)

	mov ax, (LSeek*256) + 0	; Seek back to the beginning
	sub cx, cx
	sub dx, dx
	int Dos

	jmp TakRd		; Get a buffer full of data

GTS_1:	mov WORD PTR [bx+12].TakFcb, 0 ; Clear block number
	mov BYTE PTR [bx+32].TakFcb, 0 ; Clear relative record within block
	mov ax, WORD PTR [bx+16].TakFcb
	mov [bx].TakCnt, ax
	mov ax, WORD PTR [bx+18].TakFcb
	mov [bx].TakCnt+2, ax	; Copy size into takinfo
	jmp TakRd		; Use other routine to read from the file

Do_Goto: call Skip_to_EOL	; Go try another (virtual) line
	call Load_token		; Try to pick up a new label

	test Error_flags, Eof	; Hit end-of-file?
	 jnz DOG_eof		;  Yes, done here

	cmp Label_flag, 0	; Is the token a label?
	 je DO_Goto		;  No, flush it, try again

	mov cx, SIZE Token	; Number of bytes to compare
	mov si, OFFSET Token
	mov di, OFFSET Target
	repe cmpsb		; See if the two strings are identical
	jne DO_Goto		;  Not equal, try next line

DOG_eof: ret			; Stop skipping forwards

SCR_Sleep:
	mov Hundredths, 0	; Timing in seconds
	mov Jmp_addr, OFFSET cs:DSL_lp2 ; Address to jump to after doing time

	call Do_Sleep		; Use other routine to do it
	mov Timer_is_set, 0	; Flag that the TIMER is not is use
	jmp Set_up_next_command

SCR_HSleep:
	mov Hundredths, 0FFh	; Timing in hundredths of seconds
	mov Jmp_addr, OFFSET cs:DSL_lp2 ; Address to jump to after doing time

	call Do_Sleep		; Use other routine to do it
	mov Timer_is_set, 0	; Flag that the TIMER is not is use
	jmp Set_up_next_command

	PUBLIC Do_Sleep, SCR_Sleep, SCR_HSleep, Hundredths
	PUBLIC Set_up_next_command, Get_a_number

Do_Sleep:
	call Get_a_number	; Go get a number
	 jc R3			;  Hit EOF

	mov Num_seconds, dl	; Save number of seconds

	mov ah, 2Ch		; Code to Get Time
	int Dos			; Get it

	cmp Hundredths, 0	; Doing hundredths of seconds?
	 jz DSL_do_seconds	;  No

	add dl, Num_seconds	; Add our number into current hundredths
	mov Num_seconds, 0	; Assume no overflow to seconds field
DSL_1:	cmp dl, 99		; Too many?
	 jbe DSL_do_seconds

	sub dl, 100		; Pull down the hundredths
	inc Num_seconds		; Bump the seconds
	jmp DSL_1		; See if it needs to be pulled down again

DSL_do_seconds:
	add dh, Num_seconds	; Bump up the seconds
DSL_2:	cmp dh, 59		; Too many?
	 jbe DSL_3

	sub dh, 60		; Pull down the seconds
	inc cl			; Bump the minutes
	jmp DSL_2		; See if it needs it again

DSL_3:	cmp cl, 59		; Too many?
	 jbe DSL_doit		;  No

	sub cl, 60		; Pull down the minutes
	inc ch			; Bump the hours

DSL_doit:
	mov HrMn, cx		; Save hours and minutes
	mov ScHn, dx		; Save seconds and hundredths
	jmp Jmp_addr		; Go to predetermined location


; This is the SLEEP loop, where we wake up, check the time, and then go back to
;  sleep

DSL_lp2:
	pop Ret_addr		; Get back our return address
	mov Reentry, OFFSET cs:DSL_reenter ; Set up to try for another char
	ret			; Return to emulator now

DSL_reenter:
	push Ret_addr		; Put stack back the way we need it
	call Carry_if_expired	; Call routine to set Carry Flag when timer
				;  has expired

	jnc DSL_lp2		; Not yet time, go hang out

DSL_Wakeup:
R3:	ret


 %OUT >> About half way through source file


; Routine to set the Carry Flag if the SLEEP timer has expired, clear it if not

Carry_if_expired:
	mov ah, 2Ch		; Code to Get Time
	int Dos			; Get it

	cmp cx, HrMn		; Is the hour/minute too early?
	 jb CIE_Not_expired	;  Yes
	 ja CIE_Expired		;  NO!

				;  Maybe ...

	cmp dx, ScHn		; How about the seconds?
	 jb CIE_Not_expired	;  Too early

CIE_Expired:
	stc			; Set the carry flag
	ret			; Return

CIE_Not_expired:
	clc			; Clear the carry flag
	ret			; Return

SCR_Terminal_Mode:
	jmp Shutdown		; Shut down the works

SCR_Timer:
	mov Hundredths, 0	; Timing in seconds
	mov Jmp_addr, OFFSET cs:DSL_Wakeup ; Addr to jump to after doing time

	call Do_Sleep		; Use other routine to do it
	mov Timer_is_set, 0FFh	; Flag this
	jmp Set_up_next_command

SCR_HTimer:
	mov Hundredths, 0FFh	; Timing in hundredths of seconds
	mov Jmp_addr, OFFSET cs:DSL_Wakeup ; Addr to jump to after doing time

	call Do_Sleep		; Use other routine to do it
	mov Timer_is_set, 0FFh	; Flag this
	jmp Set_up_next_command

SCR_Wait:
	mov In_LWAIT, 0		; Flag which routine we are in
	call Do_Wait		; Use another routine to do it
	jmp Set_up_next_command

SCR_LWait:
	mov In_LWAIT, 0FFh	; Flag which routine we are in
	call Do_Wait		; Use another routine to do it
	call Do_Listen		; Then turn on listening
	jmp Set_up_next_command

Do_Wait:
	call Carry_if_expired	; See if the timer is running
	 jnc Maybe_timer	;  Doesn't look expired, don't clear it

	mov What_satisfied_wait, 0 ; Mark that wait has not been satisfied
	mov Timer_is_set, 0	; Clear timer flag

	PUBLIC Do_Wait, Maybe_Timer, WAI_1

Maybe_timer:
	mov ax, OFFSET WAIT_table ; Address of the table we use
	mov Hold_ptr, ax	; Save as initial ptr for storing strings

	mov Which_item, 0	; Start defining item 0
	mov Num_waiting_items, 0 ; Not waiting on anything yet

WAI_1:	call Get_a_char		; Get the next character
	 jnc WAI_2		;  Can't, bomb

R2:	ret			; Another ret instr

WAI_2:	cmp al, Cr		; A return?
	 je WAI_Abort		;  Yes, this means we didn't get a string
				;  to wait for, which is a problem

	call Is_terminator	; Is this a terminator?
	 je Do_Wait		;  Yes, try for something better

	mov Delimiter, al	; Remember it

WAI_New_item:
	mov ax, Which_item	; Which item are we on
	cmp ax, Max_waiting_items*2 ; Too many?
	 jge WAI_Skip_CrLf	;  Yes, ditch this command!

	inc Num_waiting_items	; Bump our count
	inc Num_waiting_items	;  twice
	mov ax, Hold_ptr	; Pick up the current ptr
	mov bx, Which_item	; Pick up which item we are working on
	mov WAIT_addr[bx], ax	; Store the starting address for this item
	mov WAIT_len[bx], 0	; Zero the char count for the item

	PUBLIC WAI_New_item, WAI_lp, WAI_not_up_arrow

WAI_lp:	call Get_a_char
	 jc R2

	cmp al, Delimiter	; The ending character?
	 je WAI_Look_for_item	;  Yes, try for another item, or EOL

; Special handling, ^E, ^e, etc.

	cmp al, '^'		; Up arrow?
	 jne WAI_not_up_arrow	;  No

	call Get_a_char		; Acts as control- quoting, read a 2nd char
	 jc R2

	cmp al, '^'		; Up arrow?
	 je WAI_not_up_arrow	;  Yes, leave it alone

	and al, 1Fh		; Mask anything else into control-char range

WAI_not_up_arrow:
	mov bx, Which_item	; Remember which item we are working on

	mov di, Hold_ptr	; Pick up saved ptr
	stosb			; Store the char
	mov Hold_ptr, di	; Save updated ptr
	inc WAIT_len[bx]	; Bump count of bytes in this item

	jmp WAI_lp		; Go do the next character

	PUBLIC WAI_Look_for_item, WAI_Skip_crlf

WAI_Look_for_item:
	call Get_a_char		; Get the next character
	 jc R2			;  Can't, bomb

	call Is_EOL		; At end of line?
	 je WAI_Cr		;  Yes, done here

	call Is_terminator	; Is it a terminator?
	 je WAI_Look_for_item	;  Yes, keep looking for delimiter

	mov Delimiter, al	; Save the delimiter
	inc Which_item		; Bump this
	inc Which_item		;  twice
	jmp WAI_New_item	; Go store the next item

WAI_Skip_crlf:
	jmp Skip_to_EOL		; Use other routine

	PUBLIC WAI_Abort, WAI_Cr, WAI_lp2

WAI_Abort:
	ret			; Return without doing the wait

WAI_Cr:	
	cmp Listening, 0	; Are we already listining?
	 jne WAI_Already_listening

	call Do_Listen		; Use other routine to do the work

WAI_Already_listening:
	mov Listening, 0FFh	; Flag that we are listening
	mov Waiting, 0FFh	;  and waiting ...
	call Check_for_wait_satisfied ; See if we are already there
	cmp Waiting, 0		; Did we already get these chars?
	 je WAI_done		;  Yes, go finish up

; This is the WAIT TIMER loop, where we wake up, check the time, and then go
;  back to sleep

WAI_lp2:
	mov Listening, 0FFh	; Flag that we are listening
	mov Waiting, 0FFh	;  and waiting ...
	pop Ret_addr		; Get back our return address
	mov Reentry, OFFSET cs:WAI_reenter ; Set up to try for another char
	ret			; Return to emulator now

	PUBLIC WAI_reenter, WAI_done

WAI_reenter:
	push Ret_addr		; Put stack back the way we need it
	cmp Waiting, 0		; Are we still waiting?
	 je WAI_done		;  No, host chars broke us out of it

	cmp Timer_is_set, 0	; Is the timer set?
	 je Wai_lp2		;  No, don't try to test it

	call Carry_if_expired	; Call routine to set Carry Flag when timer
				;  has expired

	jnc WAI_lp2		; Not yet time, go hang out

	mov Waiting, 0		; Flag that we are not waiting anymore
	mov What_satisfied_wait, 255 ; Mark that the timer got us out

WAI_done:
	ret			; Done with this WAIT


SCR_Listen:
	call Do_Listen		; Use another routine to do it
	jmp Set_up_next_command

Do_Listen:
	mov Ring, 0FFh		; Put an obsurd value in the first position
	mov si, OFFSET Ring	; Source ptr
	mov di, OFFSET Ring+1	; Destination ptr
	mov cx, Size_of_ring-1	; Number of times to copy
	rep movsb		; Zap the whole ring

	mov Listening, 0FFh	; Flag that we are spying on host output
	ret			; That's it

	PUBLIC Handle_character, Waiting

; Handle character from host -- either store it in a ring buffer, or pitch it

Handle_character:
	cmp Listening, 0	; Are we listening to host chars?
	 jne We_are_listening	;  Yes, go check it out

	ret			; Done here

We_are_listening:
	mov si, OFFSET Ring+1	; Source
	mov di, OFFSET Ring	; Dest is one byte earlier
	mov cx, Size_of_ring-1	; Number of bytes to copy
	rep movsb		; BLT the ring backwards by 1 byte

	stosb			; Store the new char

	cmp Waiting, 0		; Are we waiting for host chars?
	 jne Check_for_wait_satisfied ;  Yes, go check it out

	ret			; Done here

    PUBLIC Check_for_wait_satisfied

Check_for_wait_satisfied:

	mov Which_item, 0	; Start with the first item
	mov What_satisfied_wait, 0 ; Start with this value

HAN_lp:	inc What_satisfied_wait	; Bump to next item
	mov bx, Which_item	; See which item we are on
	cmp bx, Num_waiting_items ; Compare with number we have in our table
	 jl HAN_check		;  We have another, go check it

	ret			; We have checked them all, no matches

HAN_check:
	mov si, WAIT_addr[bx]	; Pick up the item address
	mov cx, WAIT_len[bx]	; Pick up the length
	mov di, OFFSET Ring+Size_of_ring ; Start up here
	sub di, cx		; Move back by the length
	repe cmpsb		; See if we have a match
	je HAN_match		; We have one!

	inc Which_item		; Bump this
	inc Which_item		;  twice
	jmp HAN_lp		; Go check the next item, if any


	PUBLIC HAN_lp, HAN_check, HAN_match

; Finally found a match with something in our table!!

HAN_match:
	mov Waiting, 0		; Flag that we are done here

	cmp In_LWAIT, 0		; Are we supposed to clear this?
	 jne HAN_Keep_listening	;  No, so just return now

	mov Listening, 0	; Not listening either

HAN_Keep_listening:
	ret			; That is it!

	PUBLIC Load_token, Parse_script_keyword, CmAmbg

Set_up_next_command:
	mov Reentry, OFFSET cs:Do_next_script_command ; Do another command
	ret			; Return to emulator

Other_rtns ENDP

; Try to parse the next line(s) of the script

Parse_script_keyword PROC

	and Error_flags, NOT Noparse ; Turn off the no-parse flag
	call Load_token		; Pick up the first token on the line,
				;  skipping over terminators first

	cmp Label_flag, 0	; Is this a label?
	 jne Parse_script_keyword ;  Yes, skip it

	mov bx, OFFSET Script_keywords ; Addr of table
        mov ch, [bx]		; Get number of entries in table
        inc bx			; Point at first keyword

Ky1:	or ch, ch		; Any commands left to check?
	 jz Ky_NG		;  No, ran out of possibilities

; Check the next keyword .. first, check its first letter

	dec ch			; Reduce count of commands still to be checked
	mov si, OFFSET Token	; Scan the token
	lodsb			; Pick up the next byte
	or al, al		; Is it zero?
	 je Ky_Eof		;  Must have hit eof
	inc bx                  ; Point to first letter of keyword
	cmp al, [bx]		; Does this keyword match so far?
         jl Ky_NG		;  Fail if already past any possible match
	 jg Ky_Bot		;  Token char comes after this keyword, so
				;   keep searching the table

; We match this keyword so far ...

Ky2:	inc bx                  ; We match here, how 'bout next char?
        mov al, [bx]
        cmp al, '$'		; End of keyword?
	 je Ky6			;  Yes

	lodsb			; Pick up the next byte
	or al, al		; Is it zero?
	 je Ky3			;  Must have hit eof
	cmp al, [bx]		; Does this keyword match so far?
         jne Ky_Bot		;  Fail this keyword if no match
	jmp Ky2			; Go do the next character in each

; Hit terminator, see whether we are ambiguous

Ky3:	mov SCR_CMD.cmkptr,bx	; Save bx here
	call cmambg		; See if input is ambiguous
	 jmp Ky_NG		;  Ambiguous, flag it
	mov bx, SCR_CMD.cmkptr	; Our place in the keyword table.

Ky4:	inc bx			; Point to next char
	mov ah, [bx]		; Pick it up
	cmp ah, '$'		; Hit dollar sign yet?
	 jne Ky4		;  Not yet, try again
	jmp SHORT Ky_AOK	; Success!


; Hit end of keyword, match so far, now make sure that the input keyword
;  ends here too

Ky6:	lodsb			; Pick up the next character
	or al, al		; Hit the end just now?
	 je Ky_AOK		;  Yes

	dec bx			; Back off our pointer


; This keyword didn't work out, try the next one

Ky_Bot:	inc bx                  ; Find end of keyword
        mov al, [bx]
        cmp al, '$'
	 jne Ky_Bot             

        add bx, 3		; Get to the beginning of the next keyword
        jmp Ky1			; Keep trying


; We have an unambigous match for a table entry

Ky_AOK:	inc bx                  ; Get necessary data
        mov bx, [bx]		; Load up the value
	RetSkp			; Return successfully


; Can't properly parse this one

Ky_NG:	or Error_flags, Noparse	; Flag the parse error

Ky_Eof:	ret			; Done here


    PUBLIC Load_token, Load_token_on_same_line

; First, load up an input token to parse (all chars until delimiter)

Load_token:
	call Load_token_on_same_line ; Load any token on this line

	test Error_flags, Eof	; Hit EOF?
	 jnz LOT_quit		;  Yeah, give up

	cmp Token, ' '		; Find anything?
	 je Load_token		;  No, try again

LOT_quit:
	ret			; Go home

Load_token_on_same_line:
	mov Token, " "		; Set first char to space
	mov cx, SIZE Token - 1
	mov si, OFFSET Token
	mov di, OFFSET Token + 1
	rep movsb		; Clear out the token for consistency

	mov Label_flag, 0	; Assume not a label
	mov di, OFFSET Token	; Place to put token we pick up
P_lp1:	call Get_a_char		; Pick up a char
	 jc P_eof		;  Hit eof

	call Is_EOL		; Check for end of line
	 je LOT_quit		;  None on this line, give up

	call Is_terminator	; See if this char is a terminator
	 je P_lp1		;  Skip leading terminators
	call Upcase		; Make it upper case
	stosb			; Store it away

P_lp2:	call Get_a_char		; Get another char
	 jc P_eof		;  Hit eof

	cmp al, ":"		; Is it a colon?
	 je P_colon		;  Yes

	cmp al, "!"		; Is it an exclamation?
	 jne P_tag		;  No

	call Skip_to_Cr		; Skip the rest of this line

P_tag:	call Is_terminator	; See if this char is a terminator
	 je P_eof		;  Hit end of token, same as end-of-file
	call Upcase		; Make it upper case
	stosb			; Store it
	jmp P_lp2		; Do the next non-terminator

P_colon:
	mov Label_flag, 0FFh	; It is a label

P_eof:	sub al, al		; Make a zero
	stosb			; Tie off the token we just built
	ret			; Done here

; See if this char is a terminator

Is_Terminator:
	cmp al, ' '		; A space?
	 je Exit_it		;  Yes, terminator
	cmp al, 'I'-100q	; A tab?
	 je Exit_it		;  Yes, terminator
	cmp al, Cr		; A return?
	 je Exit_it		;  Yes, terminator
	cmp al, ';'		; A semicolon, virtual return?
	 je Exit_it		;  Yes, terminator
	cmp al, Lf		; A linefeed?
	 je Exit_it		;  Yes, terminator
	cmp al, 'L'-100q	; A formfeed?
	 je Exit_it		;  Yes, terminator
	cmp al, "!"		; An exclamation?
	 jne Exit_it		;  No, so this is not an EOL

	jmp Skip_to_Cr		; Go skip the rest of this line

Is_EOL:	mov al, Last_char	; Start in right place
	cmp al, ";"		; A semicolon (= virtual return)
	 je Exit_it
	cmp al, Cr		; A return?
	 je Exit_it
	cmp al, "!"		; An exclamation?
	 jne Exit_it		;  No, so this is not an EOL

;  Hit an exclamation, skip all chars to true Cr, then return EOL

Skip_to_Cr:
	call Get_a_char		; Get the next char
	 jc Hit_EOF_instead_of_Cr

	cmp al, Cr		; Finally hit our Cr?
	 jne Skip_to_Cr		;  No

	ret			; Done here (zero flag is now set)

Hit_EOF_instead_of_Cr:
	cmp al, al		; Set zero flag
	ret			; Done here


; UPCASE a single character

Upcase:	cmp al, 'a'		; Less than a?
	 jl Exit_it		;  If so, don't capitalize
        cmp al, 'z'		; More than z?
	 jg Exit_it
        and al, 137q		; Capitalize the letter.

Exit_it: ret

Parse_script_keyword ENDP


; See if keyword is unambiguous from what the user has typed in

CmAmbg:	or ch, ch		; Any keywords left to check?
	 jz AmOk		;  No, can hardly be ambiguous

cmamb0:	inc bx			; Go to end of keyword ...
	mov ah, [bx]		; So we can check the next one
	cmp ah, '$'
	 jne cmamb0

	add bx,4		; Point to start of next keyword
	mov dx, OFFSET Token	; Buffer with input typed by user

cmamb1:	mov ah, [bx]		; Keyword char
	mov di, dx		; Copy to di
	mov al, [di]		; Token char
	or al, al		; Hit end of token?
	 jz Ambig		;  Yes, and it matched, so token is ambiguous
	cmp ah, al		; Keyword bigger than input (alphabetically)?
	 jne AmOk		;  Yes, not ambiguous
	inc bx			; Advance one char
	inc dx
	jmp cmamb1

AmOk:	pop bp			; Do our own RetSkp
	add bp,3
	push bp
Ambig:	ret			; Just return


; Get_a_number -- Read chars with Get_a_char and return a number < 256
;
; Returns with ...
;	dl/ the number

Get_a_number PROC

	call Get_a_char		; Get the next character
	 jc GAN_ret		;  Can't, bomb

	sub dl, dl		; Default to zero
	call Is_Eol		; Hit end of line?
	 je GAN_ret		;  Yes

	call Is_terminator	; Is this a terminator?
	 je Get_a_number	;  Yes, try for something better

GAN_lp:	cmp al, '0'		; Make sure this is a digit
	 jb GAN_ret		;  No
	cmp al, '9'
	 ja GAN_ret		;  No
	sub al, '0'		; Convert digit from ASCII to integer
	xchg al, dl		; Swap al and dl
	mov bl, 10		; Get a ten
	mul bl			; Multiply old number by ten
	add dl, al		; Add in the new digit
	call Get_a_char		; See if more digits
	 jc GAN_ret		;  Oh well ...
	call Is_terminator	; End of digits?
	 je GAN_ok		;  Yes, use what we have
	jmp GAN_lp		; Go get another char

GAN_ok:	clc			; No error

GAN_ret:
	ret			; Done here

Get_a_number ENDP


; Routine to get a character

Get_a_char:
	test Error_flags, Eof	; Already hit EOF?
	 jz GAC_1		;  No, see if there are more chars

	stc			; Set carry flag for EOF error
	ret			; Already hit EOF, this is an error

GAC_1:	mov al, Backup		; Pick up backup char if any
	cmp al, 0FFh		; Is there a backup char?
	 je GAC_1a		;  No, move on

	mov Backup, 0FFh	; Clobber old char
	clc			; No error
	ret

GAC_1a:	push bx			; Save some regs
	push si

	mov bx, TakAdr		; Addr of our TAKE frame
	mov ax, [bx].TakCnt	; Get low half of doubleword char count
	or ax, [bx].TakCnt+2	; Merge with high half of doubleword
	 jz GAC_EOF		;  No chars left, flag as end-of-file

	mov al, BYTE PTR [bx].TakFcb ; Get first byte of fcb
	cmp al,0FFh		; Is it really a macro?
	 je GAC_2		;  Yes, don't check "buffer"

	cmp [bx].TakChl, 0	; Disk file, any chars left in buffer?
	 jne GAC_2		;  Yes. don't need more yet

	call TakRd		; Reload buffer from disk file

GAC_2:	dec [bx].TakChl		; Decrement for the char
	sub [bx].TakCnt, 1	; DEC doesn't set carry!!
	sbb [bx].TakCnt+2, 0

	mov si, [bx].TakPtr	; Pick up ptr into buffer
	lodsb			; Pick up next char
	mov [bx].TakPtr, si	; Store the updated ptr
	cmp al,CtlZ		; Maybe control-z?
	 je GAC_EOF		;  Yes, bomb out

	jmp SHORT GAC_OK	; Go finish up

GAC_EOF:
	or Error_flags, Eof	; Set this bit

	mov al, Cr		; Pretend we just saw a carriage return
	cmp al, Last_char	; But did we already see one?
	 je GAC_Err		;  Yes, we did, so call this an error

GAC_OK:	pop si
	pop bx

	cmp al, Lf		; Linefeed?
	 je Get_a_char		;  Yes, ignore it

	mov Last_char, al	; Remember last char seen
	clc			; No error
	ret

GAC_Err:
	pop si
	pop bx
	stc			; Error
	ret


; Routine to print an error message, then RetSkp
;  Expects message ptr in dx

RetErr	PROC

	mov ah, PrStr
	int Dos

	jmp RSkp

RetErr	ENDP

Code	ENDS

	END
