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