PUBLIC _TEXT SEGMENT BYTE PUBLIC ;------------------------------------------------; ; ; ; MACROS ; ; ; ;------------------------------------------------; ;------------------------------------------------; ; macro to devour blanks from string pointed to ; by SI eat_blanks MACRO M0: cmp b [si], ' ' ; pointing to blank ? jne >M1 ; no -- get out inc si ; yes -- advance pointer jmp M0 ; and loop M1: #em ;------------------------------------------------; ; ; ; DATA ; ; ; ;------------------------------------------------; DOS equ 021 STDIN equ 0 STDOUT equ 1 MAXLINELEN equ 514 MAX_STATE equ 5320 TAPE_LENGTH equ 04000 DIRECTIVELEN equ 20 TAPE_ROW equ 5 SCREEN_SEG equ 0B900 ; force all directly-written output to page 1 UP_ARROW equ 048 or bit 7 DOWN_ARROW equ 050 or bit 7 LEFT_ARROW equ 04B or bit 7 RIGHT_ARROW equ 04D or bit 7 INSERT equ 052 or bit 7 DELETE equ 053 or bit 7 ;------------------------------------------------; ; sign-on text sign_on_t: db 0D, 0A db 'TURING v1.0.', 0D, 0A db 'Turing machine simulator; by Ben North. 21st August, 1993.' db 0D, 0A, 0D, 0A, 0 ;------------------------------------------------; ; messages messages: db 'TURING: ', 0 ; 0 db 'invalid delimiter in input file', 0 ; 1 db 'state number out of range', 0 ; 2 db 'bad symbol_read', 0 ; 3 db 'bad symbol_to_write', 0 ; 4 db 'bad direction_to_move', 0 ; 5 db 'bad simulator directive', 0 ; 6 db 'bad #move directive', 0 ; 7 db 'bad #input tape -- bad or no initial head position', 0 ; 8 db 'bad #input tape -- bad symbol', 0 ; 9 db 'bad #startstate -- state number out of range', 0 ; 10 db 'premature input termination', 0 ; 11 db 'tape overrun.', 0D, 0A, 0 ; 12 db 'input parsing complete.', 0D, 0A, 0 ; 13 db 'starting tape configuration read.', 0D, 0A, 0 ; 14 db 'movement directive processed.', 0D, 0A, 0 ; 15 db 'starting machine state read.', 0D, 0A, 0 ; 16 db 'reading input....', 0D, 0A, 0 ; 17 db 'batch mode selected.', 0D, 0A, 0 ; 18 db 'tape dump filename read.', 0D, 0A, 0 ; 19 db 'bad #hexinput symbol', 0 ; 20 report_t: db 'TURING: lines read.', 0D, 0A, 0 states_t: db 'TURING: state table lines read.', 0D, 0A, 0 at_line_t: db ' at line ' at_line_number: db ' ', 0D, 0A, 0 ;------------------------------------------------; ; message proclaiming ready to rock and roll ready_to_go_t: db 0D, 0A, 'Initialisation complete. Press a key to begin ' db 'simulation.', 0D, 0A, 0 ;------------------------------------------------; ; messages for batch mode running batch_running_t: db 'simulating Turing machine....', 0D, 0A, 0 batch_halted_t: db 'Turing machine halted; dumping tape data....', 0D, 0A, 0 batch_dumped_t: db 'tape data dumped.', 0D, 0A, 0 batch_interrupted_t: db 'interrupted by user; tape data not dumped.', 0D, 0A, 0 batch_cant_t: db 'cannot run in batch mode -- no #output filename given.', 0D, 0A, 0 ;------------------------------------------------; ; 'Halted' text halted_t: db 'Halted.', 0 ;------------------------------------------------; ; mode display text s_s_mode_t: db 'Single-step mode', 0 cont_mode_t: db 'Continuous mode ', 0 ;------------------------------------------------; ; directives directives: db 'move', 0 db 'input', 0 db 'startstate', 0 db 'batchmode', 0 db 'output', 0 db 'hexinput', 0 db 0 directive_vectors: dw set_movement dw start_tape dw start_state dw set_batch_mode dw set_output_filename dw load_hex_tape ;------------------------------------------------; ; text to mark end of input end_of_input: db 'END' ;------------------------------------------------; ; texts for #move directive head_text: db 'head', 0 tape_text: db 'tape', 0 ;------------------------------------------------; ; labels for the screen screen_labels: ; in format row, column, asciiZ text db 1, 34 db 'TURING v1.0', 0 db 2, 10 db 'Turing machine simulator; by Ben North. 21st August, 1993.', 0 db 8, 6 db 'Current state:', 0 db 10, 6 db 'State table:', 0 db -1 ;------------------------------------------------; ; ascii signature for the start of dump file file_sig: db 'BNTURING' ;------------------------------------------------; ; error message for failure to write dump file dump_error_t: db 'error trying to write dump file.', 0D, 0A, 0 ;------------------------------------------------; ; sign off text bye_bye_t: db 'program run completed.', 0D, 0A, 0 ;------------------------------------------------; ; message for dumping tape dumping_t: db 'Dumping tape to file....', 0 ;------------------------------------------------; ; up to 32 blanks for erasing things db ' ', 0 end_of_blanks: ; to get n blanks, load ptr with (end_of_blanks-(n+1)) ;------------------------------------------------; ; ; ; VARIABLES ; ; ; ;------------------------------------------------; ;------------------------------------------------; DATA SEGMENT ORG 0 ;------------------------------------------------; ;------------------------------------------------; ; the tape itself tape: db TAPE_LENGTH/8 dup ? ; room for a HUGE tape end_of_tape: ;------------------------------------------------; ; current head position head_posn dw ? ;------------------------------------------------; ; current state of machine Turing_state dw ? ;------------------------------------------------; ; whether the machine has halted halted db ? ;------------------------------------------------; ; how many cycles we have executed cycles_done: db 6 dup ? ;------------------------------------------------; ; whether we are moving the tape or the head move_tape db ? ;------------------------------------------------; ; whether we are running in batch mode batch_mode db ? ;------------------------------------------------; ; which type of interactive mode we are in single_step_mode db ? ;------------------------------------------------; ; how long to pause for in continuous mode pause_length dw ? ;------------------------------------------------; ; state table display status top_of_display dw ? bot_of_display dw ? highlit_line db ? ;------------------------------------------------; ; buffer for input from STDIN line_buffer: db MAXLINELEN dup ? ;------------------------------------------------; ; buffer to hold a read directive directive_buffer: db DIRECTIVELEN+1 dup ? ; +1 to leave room for NUL ;------------------------------------------------; ; general purpose buffer scrap_buffer: db 50 dup ? ;------------------------------------------------; ; buffer for the initial configuration of the ; tape, where in that buffer the head should ; start, and how long the start tape is start_tape_buffer: ; stored as 0, 1, 0, ... not as '0', '1', '0', ... db 500 dup ? start_head_posn dw ? start_tape_len dw ? found_head_start db ? ; this is 0 for neither of () found yet, 1 for ; previous char was (, 2 for previous but one char ; was (, 3 for found both () ;------------------------------------------------; ; filename, etc. for tape dump at conclusion ; of run filename: db 128 dup ? filename_given db ? ; flag for whether a filename given in input file_handle dw ? dump_length dw ? ; how long the dump is dump_start dw ? ; where it starts as an offset into the tape array dump_head dw ? ; where the head is as an offset into the dumped ; section of the tape sig_start dw ? ; where the first '1' is sig_end dw ? ; where the last '1' is ;------------------------------------------------; ; the program for the machine ; EVEN ; each entry starts with a word giving state to enter program: db 4*MAX_STATE dup ? db 4*MAX_STATE dup ? ;------------------------------------------------; ; line number of input file line_number dw ? ;------------------------------------------------; ; how many actual state table lines read states_read dw ? ;------------------------------------------------; ; the parser in_state dw ? read_symbol db ? enter_state dw ? write_symbol db ? direction db ? error_code dw ? ;------------------------------------------------; ; keyboard reading routine echo_x db ? echo_y db ? insert_mode db ? ;------------------------------------------------; ;--OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO--; ;------------------------------------------------; CODE SEGMENT _TEXT SEGMENT BYTE PUBLIC ;------------------------------------------------; ; ; ; DISPLAY ; ; ; ;------------------------------------------------; ; CONTAINS: ; ; show_tape ; ; show_tape_round_head ; ; set_up_screen ; ; write_text ; ; clear_screen ; ; show_current_state ; ; show_state_table ; ; show_state_line ; ; show_mode ; ; show_halted ; ;------------------------------------------------; ;------------------------------------------------; ; SHOW_TAPE 18-08-1993 ; ; Displays a portion of the tape. ; ; ; ; INPUT: ; ; AX - cell to display from ; ;------------------------------------------------; show_tape: push ax, bx, cx, dx, di, es mov bx, ax ; save this for in a minute mov ax, SCREEN_SEG ; set up ES for screen buffer mov es, ax mov ax, bx ; retrieve pointer mov cl, 3 shr bx, cl ; get byte containing start add bx, tape and al, 7 ; pick off position within byte of start cell mov cl, al mov al, bit 7 ; get bit position for masking shr al, cl mov di, (TAPE_ROW*80+1)*2 ; get destination for pictures mov cx, 78 ; number of cells to display L0: mov dl, 1 ; this is the char to display for a 0 test [bx], al ; is it a 0 ? if nz inc dl ; no -- get correct character mov es:[di], dl ; write it to screen add di, 2 ; point to next char posn (skipping attrib) rcr al, 1 ; change mask -- has it overflowed ? jnc >L1 ; no -- loop rcr al, 1 ; yes -- set to bit 7 inc bx ; and increase the byte cmp bx, end_of_tape ; have we reached the end of the tape ? je >E0 ; yes -- go home L1: loop L0 E0: pop es, di, dx, cx, bx, ax ret ;------------------------------------------------; ; SHOW_TAPE_ROUND_HEAD 22-08-1993 ; ; Displays a portion of the tape with the head ; ; at the centre. ; ;------------------------------------------------; show_tape_round_head: push ax mov ax, head_posn sub ax, 39 call show_tape pop ax ret ;------------------------------------------------; ; SET_UP_SCREEN 23-08-1993 ; ; Initialises the screen, ie. clears it, puts ; ; two blocks ready to be the head, and puts ; ; the appropriate pieces of text in the right ; ; places. ; ;------------------------------------------------; set_up_screen: push ax, cx, si, di, ds, es mov ax, 5 by 1 ; select page 1 for the interactive stuff int 010 call clear_screen call no_cursor mov ax, SCREEN_SEG ; set up ES for screen buffer segment mov es, ax mov al, 254 ; ascii code for a block mov di, (80*(TAPE_ROW-1)+40)*2 mov es:[di], al mov es:[di+2*2*80], al mov b es:[di+2*80+1], 0F ; set bright attribute for cell under head mov al, ':' ; column of colons for state table mov di, 2*(80*12+14) mov cx, 12 L0: stosb add di, 2*80-1 loop L0 mov al, ',' mov di, 2*(80*12+10) mov cx, 12 L0: stosb mov es:[di+13], al mov es:[di+19], al add di, 2*80-1 loop L0 mov si, screen_labels push cs pop ds L0: mov al, [si] cmp al, -1 ; finished ? je >L1 ; yes mov ah, [si+1] add si, 2 call write_text jmp L0 L1: pop es, ds, di, si, cx, ax ret ;------------------------------------------------; ; WRITE_TEXT 23-08-1993 ; ; Writes the given text at the given coords. ; ; ; ; INPUT: ; ; AL - row ; ; AH - column ; ; DS:SI - pointer to asciiZ text ; ; ; ; OUTPUT: ; ; SI - left pointing past string written ; ;------------------------------------------------; write_text: push ax, bx, di, es mov es, SCREEN_SEG cld mov bl, ah ; save column mov bh, 80 ; multiply row by 80 mul bh xor bh, bh ; and add in column add ax, bx shl ax, 1 ; allow for skipping attribute bytes mov di, ax L0: lodsb test al jz >L1 stosb inc di ; skip attribute byte jmp L0 L1: pop es, di, bx, ax ret ;------------------------------------------------; ; CLEAR_SCREEN 23-08-1993 ; ; Clears the screen. ; ;------------------------------------------------; clear_screen: push ax, cx, di, es mov ax, SCREEN_SEG mov es, ax xor di, di cld mov ax, 7 by ' ' ; attribute 7 and ascii space mov cx, 80*25 ; this many words -- character plus attribute rep stosw pop es, di, cx, ax ret ;------------------------------------------------; ; SHOW_CURRENT_STATE 23-08-1993 ; ; Shows which state the machine is in. ; ;------------------------------------------------; show_current_state: push ax, cx, dx mov ax, 21 by 8 mov dx, Turing_state mov cx, 4 call write_number pop dx, cx, ax ret ;------------------------------------------------; ; SHOW_STATE_TABLE 23-08-1993 ; ; Shows the relevant section of the state ; ; table, highlighting the line about to be ; ; used. ; ;------------------------------------------------; show_state_table: push ax, bx, cx, dx, di, es xor bl, bl ; will will hold 0 if the current state is already on ; display, 1 if we need to re-draw display with current ; at top, 2 if ... at bottom, and 3 if ... in middle mov ax, Turing_state cmp ax, top_of_display if b inc bl cmp ax, bot_of_display if a add bl, 2 test bl ; current state already on screen ? jz >L7 ; yes cmp bl, 1 jne >L0 mov top_of_display, ax ; recall AX holds Turing_state add ax, 5 mov bot_of_display, ax jmp >L2 L0: cmp bl, 2 jne >L1 mov bot_of_display, ax sub ax, 5 mov top_of_display, ax jmp >L2 L1: sub ax, 3 mov top_of_display, ax add ax, 5 mov bot_of_display, ax sub ax, 2 call read_tape ; have we picked the right one ? test al jz >L2 ; yes inc top_of_display ; no -- adjust inc bot_of_display L2: test top_of_display ; check in range jns >L3 mov top_of_display, 0 mov bot_of_display, 5 L3: mov ax, bot_of_display cmp ax, MAX_STATE jb >L4 mov bot_of_display, MAX_STATE-1 mov top_of_display, MAX_STATE-6 L4: mov cx, 12 mov ax, top_of_display xor dx, dx L5: call show_state_line inc dh inc dl cmp dl, 2 jne >L6 xor dl, dl inc ax L6: loop L5 L7: mov ax, SCREEN_SEG mov es, ax mov al, highlit_line test al ; any line highlit ? js >L9 ; no -- skip this bit add al, 12 ; make row absolute mov bh, 80 ; multiply row by 80 mul bh add ax, 6 ; add in column shl ax, 1 ; allow for two bytes per screen location mov di, ax mov cx, 20 mov al, 7 ; normal attribute L8: inc di ; point to attribute byte stosb loop L8 L9: mov ax, Turing_state sub ax, top_of_display shl ax, 1 push ax mov ax, head_posn call read_tape test al pop ax if nz inc ax mov highlit_line, al add al, 12 ; make row absolute mov bh, 80 ; multiply row by 80 mul bh add ax, 6 ; add in column shl ax, 1 ; allow for two bytes per screen location mov di, ax mov cx, 20 mov al, 15 ; normal attribute L10: inc di ; point to attribute byte stosb loop L10 pop es, di, dx, cx, bx, ax ret ;------------------------------------------------; ; SHOW_STATE_LINE 24-08-1993 ; ; Displays one line of the state table. ; ; ; ; INPUT: ; ; AX - state number ; ; DL - symbol read ; ; DH - line of the display to write to ; ;------------------------------------------------; show_state_line: push ax, bx, cx, dx, si push dx, ax ; save symbol read, state number mov bx, ax shl bx, 1 mov al, dl xor ah, ah add bx, ax shl bx, 1 shl bx, 1 ; we now have offset into state table array add bx, program mov ax, 6 by 12 add al, dh ; add row offset pop dx ; retrieve state number mov cx, 4 call write_number add ah, 6 ; move to next display location pop dx xor dh, dh mov cx, 1 call write_number add ah, 4 ; where the symbol_to_write goes mov si, scrap_buffer+10 ; room for some string manipulation ; recall that write_number clobbers first few chars cmp b [bx+3], -2 ; is this line undefined ? jne >L0 ; no -- proceed as usual mov w [si], 0 by '-' call write_text inc ah mov si, end_of_blanks-10 push ds, cs pop ds call write_text pop ds jmp >L2 L0: mov dl, [bx+2] call write_number inc ah mov w [si], 0 by ',' ; replace comma in case this line had shown undefined call write_text add ah, 5 ; move to location for target state test b [bx+3] ; is this a halt condition ? js >L0 ; yes -- don't write the target state number mov w [si], ',L' ; this MOV doesn't affect the flags from TEST B [BX+3] if nz mov w [si], ',R' ; so this should work mov cx, 4 mov dx, [bx] ; now write the state to enter call write_number jmp >L1 L0: mov w [si], ' H' push si, ds, cs mov si, end_of_blanks-5 ; blank out the target state pop ds call write_text pop ds, si L1: sub ah, 3 ; location of direction_to_move mov b [si+2], 0 call write_text L2: pop si, dx, cx, bx, ax ret ;------------------------------------------------; ; SHOW_MODE 23-08-1993 ; ; Displays which mode the simulator is in at ; ; the moment, single-step or continuous. ; ;------------------------------------------------; show_mode: push ax, si, ds mov si, s_s_mode_t test single_step_mode if z mov si, cont_mode_t mov ax, 44 by 8 push cs pop ds call write_text pop ds, si, ax ret ret ;------------------------------------------------; ; SHOW_HALTED 23-08-1993 ; ; Writes 'Halted' where the current state is ; ; shown. ; ;------------------------------------------------; show_halted: push ax, si, ds mov ax, 21 by 8 mov si, halted_t push cs pop ds call write_text pop ds, si, ax ret ;------------------------------------------------; ; ; ; INITIALISATION ; ; ; ;------------------------------------------------; ; CONTAINS: ; ; initialise ; ; init_prog ; ; init_tape ; ;------------------------------------------------; ;------------------------------------------------; ; INITIALISE 21-08-1993 ; ; Sets up various variables. ; ;------------------------------------------------; initialise: call init_prog call init_tape mov move_tape, 0 ; the default is to move the head mov batch_mode, 0 ; by default we are in interactive mode mov single_step_mode, 1 ; start off in s-s mode mov pause_length, 81 ; wait 81 frames between cycles by default mov filename_given, 0 mov top_of_display, -1 ; this is very big treated as unsigned mov bot_of_display, 0 mov highlit_line, -1 mov w [cycles_done], 0 mov w [cycles_done+2], 0 mov w [cycles_done+4], 0 ret ;------------------------------------------------; ; INIT_PROG 18-08-1993 ; ; Initialises the program for the machine to ; ; consist of HALT for every state/symbol. ; ; The machine is also put in state 1. ; ;------------------------------------------------; init_prog: push ax, bx, cx mov ax, -2 by 0 mov bx, program+2 mov cx, MAX_STATE*2 ; there are 2 entries per state L0: mov [bx], ax add bx, 4 loop L0 mov Turing_state, 1 ; the default pop cx, bx, ax ret ;------------------------------------------------; ; INIT_TAPE 18-08-1993 ; ; Initialises the tape to all zeros, and the ; ; head to be in the middle of the tape. ; ;------------------------------------------------; init_tape: push ax, bx, cx mov bx, tape mov cx, TAPE_LENGTH/16 ; do it a word at a time xor ax, ax L0: mov [bx], ax add bx, 2 loop L0 mov head_posn, TAPE_LENGTH/4*3 mov halted, 0 pop cx, bx, ax ret ;------------------------------------------------; ; ; ; INPUT / OUTPUT ; ; ; ;------------------------------------------------; ; CONTAINS: ; ; sign_on ; ; get_line ; ; read_dec ; ; string_out ; ; ascii_number ; ; show_error_line ; ; message_out ; ; error_out ; ; report_progress ; ; report_states ; ; get_key ; ; write_con ; ; dump_tape ; ; get_dump_info ; ;------------------------------------------------; ;------------------------------------------------; ; SIGN_ON 21-08-1993 ; ; Displays the sign-on text. ; ;------------------------------------------------; sign_on: push ax, di, ds push cs pop ds mov dx, sign_on_t mov al, 1 call write_con ; make sure this goes to the console, not redirectable pop ds, di, ax ret ;------------------------------------------------; ; GET_LINE 17-08-1993 ; ; Transfers a single line of input from STDIN ; ; and puts it to line_buffer. ; ; ; ; OUTPUT: ; ; Carry - set if premature file termination ; ;------------------------------------------------; get_line: push ax, bx, cx, dx, si mov si, 0 ; count of number of chars read mov dx, line_buffer ; destination for output L0: cmp si, MAXLINELEN ; finished ? je >L1 ; yes -- go home mov ah, 03F ; no -- read another single character from STDIN mov bx, STDIN mov cx, 1 int DOS cmp ax, cx ; did we read the character ? jne >L2 ; no -- file has run out mov bx, dx cmp b [bx], 0A ; read LF character ? je >L1 ; yes -- go home inc si ; no -- update counter inc dx ; and pointer jmp L0 ; loop L1: clc jmp >E0 L2: stc E0: pop si, dx, cx, bx, ax ret ;------------------------------------------------; ; READ_DEC 18-08-1993 ; ; Reads a decimal number from chars pointed ; ; ; to. The number must be < 65536. Leading ; ; blanks are devoured. ; ; ; ; INPUT: ; ; SI - pointer to string ; ; ; ; OUTPUT: ; ; AX - number read ; ; SI - left pointing to first non-dec char ; ; Carry - set if overflow ; ;------------------------------------------------; read_dec: push cx, dx eat_blanks xor ax, ax L0: mov cl, [si] ; get next character cmp cl, '0' ; is it a decimal digit ? jb >L1 ; no cmp cl, '9' ; maybe -- is it ? ja >L1 ; no sub cl, '0' ; convert to its value mov dx, 10 ; denary left shift ax mul dx test dx ; overflow ? jnz >L2 ; yes xor ch, ch ; add in new digit add ax, cx inc si ; update pointer jmp L0 ; loop for next digit L1: clc ; indicate valid result jmp >E0 L2: clc ; indicate invalid result E0: pop dx, cx ret ;------------------------------------------------; ; STRING_OUT 21-08-1993 ; ; Writes the asciiZ string pointed to by DS:DX ; ; to STDOUT ; ; ; ; INPUT: ; ; DS:DX - pointer to string ; ;------------------------------------------------; string_out: push ax, bx, cx, di, es mov cx, -1 ; find length of string, excluding final NUL mov di, dx xor al, al push cs pop es cld repne scasb not cx dec cx mov ah, 040 ; and write it to STDOUT mov bx, STDOUT int DOS pop es, di, cx, bx, ax ret ;------------------------------------------------; ; ASCII_NUMBER 21-08-1993 ; ; Converts a number into a string of ascii ; ; digits '0'-'9'. ; ; ; ; INPUT: ; ; AX - number to convert ; ; ES:DI - where to put ascii characters ; ; ; ; OUTPUT: ; ; DI - left pointing past ascii characters ; ;------------------------------------------------; ascii_number: push ax, bx, cx, dx mov bx, 10 xor cx, cx L0: inc cx ; increase the number of digits xor dx, dx ; zero off high word div bx ; quotient to AX, remainder to DX push dx ; save digit test ax ; finished ? jnz L0 ; no -- loop round cld ; ensure increasing DI L1: pop ax ; retrieve next most sig. digit add al, '0' ; convert to ascii stosb ; and store loop L1 pop dx, cx, bx, ax ret ;------------------------------------------------; ; SHOW_ERROR_LINE 21-08-1993 ; ; Writes 'at line xxxx' to STDOUT. ; ;------------------------------------------------; show_error_line: push ax, dx, di, ds, es mov ax, line_number mov di, at_line_number ; fill in line number to template push cs pop es call ascii_number mov al, '.' ; terminate with a full stop stosb mov dx, at_line_t ; write the completed message push cs pop ds call string_out pop es, ds, di, dx, ax ret ;------------------------------------------------; ; MESSAGE_OUT 21-08-1993 ; ; Sends the given message to STDOUT ; ; ; ; INPUT: ; ; AX - message to output (numbered from 0) ; ;------------------------------------------------; message_out: push ax, cx, dx, di, ds, es push cs ; prepare DS pop ds mov dx, messages ; prefix message with 'TURING: ' call string_out mov dx, ax ; transfer message number -- we need AL inc dx ; and prepare xor al, al ; search for terminating NUL cld push cs pop es mov di, messages L0: dec dx test dx ; found required message ? jz >L1 ; yes -- output it mov cx, -1 ; look for next terminator repne scasb ; this leaves DI just past NUL jmp L0 ; and loop L1: mov dx, di ; transfer pointer call string_out ; and output the text pop es, ds, di, dx, cx, ax ret ;------------------------------------------------; ; ERROR_OUT 21-08-1993 ; ; Gives an error message including the number ; ; of the offending line in the input file. ; ; The error number is taken from error_code ; ;------------------------------------------------; error_out: push ax mov ax, error_code ; this gives the message number to show call message_out call show_error_line pop ax ret ;------------------------------------------------; ; REPORT_PROGRESS 23-08-1993 ; ; Displays a message saying how many lines ; ; have been read. ; ;------------------------------------------------; report_progress: push ax, dx, di, ds, es mov ax, line_number mov di, report_t+8 push cs pop es call ascii_number push cs pop ds mov dx, report_t call string_out pop es, ds, di, dx, ax ret ;------------------------------------------------; ; REPORT_STATES 23-08-1993 ; ; Displays a message saying how many lines ; ; of state table information have been read. ; ;------------------------------------------------; report_states: push ax, dx, di, ds, es mov ax, states_read mov di, states_t+8 push cs pop es call ascii_number push cs pop ds mov dx, states_t call string_out pop es, ds, di, dx, ax ret ;------------------------------------------------; ; GET_KEY 23-08-1993 ; ; Waits for a keypress and returns the ascii ; ; value. The buffer is first flushed. Keys ; ; which return ascii NUL are returned as the ; ; scancode with the top bit set. ; ; ; ; OUTPUT: ; ; AL - ascii value of keypress read ; ;------------------------------------------------; get_key: L0: mov ah, 1 int 016 jz >L1 ; no key waiting xor ah, ah ; key waiting -- discard it int 016 jmp L0 L1: xor ah, ah int 016 test al ; extended keycode ? jnz >L2 ; no -- we are done mov al, ah ; yes -- get scancode instead or al, bit 7 ; and set the top bit L2: ret ;------------------------------------------------; ; WRITE_CON 23-08-1993 ; ; Writes a string to the console. The output ; ; goes to page 0. ; ; ; ; INPUT: ; ; DS:DX - pointer to asciiZ string. ; ; AL - 0 for prefix with 'TURING: ', o/w not ; ;------------------------------------------------; write_con: push ax, bx, si test al ; prefix with 'TURING: ' ? jnz >L1 ; no mov si, messages ; yes -- do so mov ah, 0E xor bh, bh L0: lodsb test al jz >L1 int 010 jmp L0 L1: mov si, dx mov ah, 0E ; prepare to write character in teletype mode xor bh, bh L2: lodsb test al ; finished ? jz >L3 ; yes int 010 ; no -- write char jmp L2 L3: pop si, bx, ax ret ;------------------------------------------------; ; DUMP_TAPE 23-08-1993 ; ; Dumps the output tape to the file given in ; ; filename. Note we shouldn't be asked to do ; ; this if no filename were given. Results are ; ; undefined, probably nasty, in this case. ; ;------------------------------------------------; dump_tape: push ax, bx, cx, dx, si, di mov dx, filename mov ah, 03C ; create file -- overwrite if already exists mov cx, 0 ; normal attributes int DOS jc >E0 ; if error, abort mov file_handle, ax ; save handle returned mov ah, 040 ; write 'BNTURING' signature mov bx, file_handle mov cx, 8 mov dx, file_sig push ds, cs pop ds int DOS pop ds call get_dump_info ; find out what and how much we need to dump mov ah, 040 mov bx, file_handle mov cx, 2 mov dx, offset dump_length int DOS mov ax, head_posn sub ax, dump_start mov dump_head, ax mov ah, 040 mov bx, file_handle mov cx, 2 mov dx, offset dump_head int DOS mov ah, 040 ; write number of cycles executed mov bx, file_handle mov cx, 6 mov dx, cycles_done int DOS mov di, dump_start mov si, dump_length test si ; were there any '1's ? jz >L1 ; no -- the file is finished L0: mov ax, di call read_tape mov b[scrap_buffer], al mov ah, 040 ; write the cells one at a time mov bx, file_handle mov cx, 1 mov dx, scrap_buffer int DOS inc di dec si jnz L0 L1: mov ah, 03E ; close the file mov bx, file_handle int DOS jmp >E1 E0: mov ax, 5 by 0 ; reselect page 0 int 010 mov dx, dump_error_t push cs pop ds xor al, al call write_con jmp dump_to_dos E1: pop di, si, dx, cx, bx, ax ret ;------------------------------------------------; ; GET_DUMP_INFO 23-08-1993 ; ; Determines how long the dumped excerpt must ; ; be and where it should start. See the .DOC ; ; file for details of what this is. ; ; ; ; OUTPUT: ; ; Variables filled in: ; ; dump_length, dump_start ; ;------------------------------------------------; get_dump_info: push ax, bx, cx, si xor si, si ; do this the naive way -- it probably isn't that slow mov cx, TAPE_LENGTH xor bx, bx ; holds 0 before any '1' is found, 1 after the first L0: mov ax, si call read_tape test al jz >L2 test bx jnz >L1 mov sig_start, si ; found the start of the significant bit of tape inc bx ; found a '1' L1: mov sig_end, si ; update pointer to most recent '1' found L2: inc si loop L0 test bx ; were there any '1's ? jz >L5 ; no -- set results and go home mov ax, head_posn ; is the head at or to the left of the end ? cmp ax, sig_end jbe >L3 ; yes -- we are ok mov sig_end, ax ; no -- adjust it so it is L3: mov ax, head_posn ; is the head at or to the right of the start ? cmp ax, sig_start jae >L4 ; yes -- we are ok mov sig_start, ax ; no -- adjust it so it is L4: mov ax, sig_end ; find length of tape to dump sub ax, sig_start inc ax ; we want the length to include both ends mov dump_length, ax mov ax, sig_start ; transfer start of dump from working variables mov dump_start, ax ; to results variables jmp >E0 L5: mov dump_length, 0 E0: pop si, cx, bx, ax ret ;------------------------------------------------; ; WRITE_NUMBER 24-08-1993 ; ; Writes a number to the screen at the given ; ; coords. The number is left-justified and ; ; padded with blanks to the number of figures ; ; given. ; ; ; ; INPUT: ; ; AL, AH - row, column to write to ; ; DX - number to write ; ; CX - number of digits ; ;------------------------------------------------; write_number: push cx, si, di, es push ax ; save coords mov di, scrap_buffer push ds pop es cld mov al, ' ' ; store blanks rep stosb xor al, al ; store terminator stosb mov ax, dx mov di, scrap_buffer call ascii_number pop ax ; retrieve coords mov si, scrap_buffer call write_text pop es, di, si, cx ret ;------------------------------------------------; ; ; ; MAIN ; ; ; ;------------------------------------------------; main: mov ax, DGROUP ; set up DS to point to variables segment mov ds, ax call sign_on call initialise call read_program call simulate jmp quit ;------------------------------------------------; ; ; ; NOTES ; ; ; ;------------------------------------------------; ;------------------------------------------------; ; Programs Storage ; ; ; Each entry in the program array is of the form ; ; +0, +1 state to enter ; +2 symbol to write to tape ; +3 direction to move -- 0 for left, 1 for right, -1 for halt ; -2 for this entry undefined ; ; The array is indexed by state then symbol read, ie entry for ; state 0 reading 0, then state 0 reading 1, then state 1 reading 0, .... ;------------------------------------------------; ; Errors while Parsing Input File ; ; ; This refers to errors in the input file format, ; not system errors. The routine Parse_Line can ; return one of the following error codes. ; ; 0 invalid delimiter (ie. not a comma) ; 1 state number out of range ; 2 bad symbol read (not one of '0', '1') ; 3 bad symbol to write (not one of '0', '1') ; 4 bad direction to move (not one of 'L', 'R', 'H') ; 5 unrecognised directive ; 6 bad operand to #move (not 'tape' or 'head') ; 7 bad or no initial head position in #input tape ; 8 bad symbol in #input tape (not '0', '1', '(', ')') ; 9 #startstate out of range ;------------------------------------------------; ; NB: See also PROG.DOC for more notes ; ;------------------------------------------------; ;------------------------------------------------; ; Glossary of Acronyms ; ; ; DTD - dump to Dos ;------------------------------------------------; ; ; ; PROGRAM INPUT AND TRANSLATION ; ; ; ;------------------------------------------------; ; CONTAINS: ; ; parse_line ; ; store_line ; ; process_directive ; ; check_for_end ; ; read_program ; ; set_movement ; ; start_tape ; ; load_tape ; ; start_state ; ;------------------------------------------------; ;------------------------------------------------; ; PARSE_LINE 18-08-1993 ; ; Parses the line held in line_buffer. The ; ; results are stored to variables. ; ; ; ; OUTPUT: ; ; Carry - clear for succesful parsing ; ; set for error, and in this case ; ; error_code - error code (see notes) ; ;------------------------------------------------; parse_line: push si, ax ; NB: different order to usual mov si, line_buffer ; point to start of line eat_blanks cmp b [si], 0D ; blank line ? if e jmp long >L5 ; yes -- done cmp b [si], ';' ; comment ? if e jmp long >L5 ; yes -- done cmp b [si], '#' ; directive ? jne >L0 ; no call process_directive ; yes -- process it jmp long >E0 ; then go home L0: call read_dec ; get decimal state number to AX mov error_code, 2 cmp ax, MAX_STATE ; valid state number ? if ae jmp long >L4 ; no -- error mov in_state, ax ; yes -- store it eat_blanks mov error_code, 1 cmp b [si], ',' ; comma to separate operands ? if ne jmp long >L4 ; no -- error inc si call read_dec mov error_code, 3 cmp ax, 1 ; the read_symbol must be 0 or 1 -- is it ? if a jmp long >L4 ; no -- error mov read_symbol, al ; yes -- store it eat_blanks mov error_code, 1 cmp b [si], ',' if ne jmp long >L4 inc si call read_dec mov error_code, 4 cmp ax, 1 ja >L4 mov write_symbol, al eat_blanks mov error_code, 1 cmp b [si], ',' jne >L4 inc si eat_blanks mov al, [si] ; this should be 'L', 'R', or 'H' inc si or al, bit 5 ; convert to lower case cmp al, 'l' jne >L0 mov direction, 0 jmp >L2 L0: cmp al, 'r' jne >L1 mov direction, 1 jmp >L2 L1: mov error_code, 5 cmp al, 'h' jne >L4 mov direction, -1 jmp >L3 ; don't need a state_to_enter -- store and all ok L2: eat_blanks mov error_code, 1 cmp b [si], ',' ; comma to separate operands ? jne >L4 ; no -- error inc si call read_dec mov error_code, 2 cmp ax, MAX_STATE jae >L4 mov enter_state, ax L3: inc states_read call store_line ; we have read a complete state line -- store it jmp >L5 L4: stc jmp >E0 L5: clc E0: pop ax, si ret ;------------------------------------------------; ; STORE_LINE 21-08-1993 ; ; Stores the program line given in the ; ; variables used by parse_line to the actual ; ; program area. ; ;------------------------------------------------; store_line: push ax, bx mov bx, in_state shl bx, 1 ; *2 mov al, read_symbol xor ah, ah add bx, ax ; now have entry number shl bx, 1 ; *4 (length of each entry) shl bx, 1 add bx, program ; we now have the address of the entry mov ax, enter_state ; fill in the entry mov [bx], ax mov al, write_symbol mov [bx+2], al mov al, direction mov [bx+3], al pop bx, ax ret ;------------------------------------------------; ; PROCESS_DIRECTIVE 22-08-1993 ; ; Given a line in line_buffer consisting of a ; ; directive to the simulator, processes it. ; ; ; ; INPUT: ; ; SI - points to # in line_buffer ; ; ; ; OUTPUT: ; ; Carry - clear if all ok ; ; set if error, and then in this case ; ; error_code - contains error code ; ;------------------------------------------------; process_directive: push ax, bx, cx, si, di, es inc si ; point to actual directive mov di, directive_buffer push ds pop es mov cx, DIRECTIVELEN ; maximum length of a directive call mve_word push si ; save pointer to first non-alphabetic char xor bx, bx ; count which one we're looking at mov si, directive_buffer mov di, directives push cs pop es L3: test b es:[di] ; reached end of list ? jz >L6 ; yes -- then it doesn't match call cmp_str ; match this one ? test al jz >L5 ; yes L4: inc di test b es:[di-1] jnz L4 inc bx ; increase counter jmp L3 L5: shl bx, 1 ; get offset into dispatch table add bx, directive_vectors ; add base pop si ; retrieve pointer to operand call cs:[bx] ; call service routine -- it sets Carry and error_code jmp >E0 L6: pop si ; discard pointer to operand mov error_code, 6 ; unrecognised directive stc E0: pop es, di, si, cx, bx, ax ret ;------------------------------------------------; ; CHECK_FOR_END 21-08-1993 ; ; Tests whether the lines read into line_ ; ; buffer starts with (after blank eating) the ; ; characters 'END'. ; ; ; ; OUTPUT: ; ; Carry - set if 'END' encountered ; ; clear if not ; ;------------------------------------------------; check_for_end: push cx, si, di, es mov si, line_buffer eat_blanks mov di, end_of_input push cs ; set up ES:DI for end-of-input pattern pop es mov cx, 3 ; do they match ? cld repe cmpsb jcxz >L1 ; maybe L0: clc ; no -- set result and go home jmp >E0 L1: jne L0 ; the last character failed stc ; it passed -- they match E0: pop es, di, si, cx ret ;------------------------------------------------; ; READ_PROGRAM 21-08-1993 ; ; Reads lines from STDIN, entering the lines ; ; into the program array, until a line ; ; beginning with 'END' is encountered. ; ;------------------------------------------------; read_program: push ax mov ax, 17 ; say 'reading input ....' call message_out mov line_number, 0 ; initialise line counter mov states_read, 0 ; and state table lines counter L0: inc line_number ; next line mov ax, line_number ; have we read another multiple of test ax, 07F ; 128 lines ? if z call report_progress ; if so, say how we are doing call get_line jc >L2 ; premature file termination call check_for_end jc >L1 call parse_line jc >L3 jmp L0 L1: call report_progress ; say how many lines read call report_states mov ax, 13 ; display 'finished' message call message_out jmp >E0 L2: mov error_code, 11 ; display 'unexpected EOF' message call error_out jmp dump_to_dos L3: call error_out jmp dump_to_dos E0: pop ax ret ;------------------------------------------------; ; SET_MOVEMENT 22-08-1993 ; ; If the next word is 'tape' then sets move_ ; ; tape to 1, if 'head', set it to 0. ; ;------------------------------------------------; set_movement: push ax, si, di, es eat_blanks mov di, scrap_buffer push ds pop es call mve_word mov si, scrap_buffer mov di, tape_text push cs pop es call cmp_str test al jne >L0 mov move_tape, 1 jmp >L2 L0: mov di, head_text call cmp_str test al jne >L1 mov move_tape, 0 jmp >L2 L1: mov error_code, 7 stc jmp >E0 L2: mov ax, 15 call message_out clc E0: pop es, di, si, ax ret ;------------------------------------------------; ; START_TAPE 23-08-1993 ; ; Reads in an initial configuration of the ; ; tape. The initial position of the head is ; ; marked by surrounding that symbol with (). ; ; ; ; INPUT: ; ; SI - pointer to operand ; ;------------------------------------------------; start_tape: push ax, cx, dx, si, di, es eat_blanks cld mov di, start_tape_buffer push ds pop es mov found_head_start, 0 ; initialise flag -- we haven't found the () yet mov cx, -1 ; count where the () is -- start one back xor dx, dx ; count how many symbols read L0: lodsb ; get next character cmp al, ' ' ; blank ? je >L9 ; yes -- we are done cmp al, 0D ; CR ? je >L9 ; yes -- we are done cmp al, '0' je >L1 cmp al, '1' je >L1 cmp al, '(' je >L4 cmp al, ')' je >L6 mov error_code, 9 stc jmp >E0 L1: inc dx ; another symbol read sub al, '0' ; convert to value stosb ; and store cmp found_head_start, 0 ; still waiting for '(' ? jne >L2 inc cx ; yes -- update counter of where the head starts jmp L0 ; and loop L2: cmp found_head_start, 3 ; found both '(', ')' ? je L0 ; yes -- loop round cmp found_head_start, 1 ; previous char was '(' ? jne >L3 ; no -- must be 'previous but one was '('' inc found_head_start ; yes -- indicate 'previous but one was '('' inc cx ; and move counter onwards jmp L0 L3: mov error_code, 8 ; this is in error; we have something like '..0(11)0..' stc jmp >E0 L4: cmp found_head_start, 0 ; found neither of '(', ')' yet ? je >L5 ; yes -- we are ok mov error_code, 8 ; error -- something like '..1001(1)00(..' stc jmp >E0 L5: inc found_head_start ; indicate 'previous char was '('' jmp L0 ; and loop L6: cmp found_head_start, 2 ; do we have 'previous but one char was '('' ? je >L8 ; yes -- ok L7: mov error_code, 8 ; error -- something like '..0110(0)11)..' or '..11)..' stc jmp >E0 L8: inc found_head_start ; indicate 'found both '(', ')'' jmp L0 L9: cmp found_head_start, 0 ; are we still waiting for () ? je L7 ; yes -- error mov start_head_posn, cx ; store these counters to variables mov start_tape_len, dx call load_tape mov ax, 14 call message_out clc E0: pop es, di, si, dx, cx, ax ret ;------------------------------------------------; ; LOAD_TAPE 23-08-1993 ; ; Given the data in start_tape_buffer, ; ; start_tape_len and start_head_posn, fills ; ; the tape with these data and sets up head_ ; ; posn correctly. ; ;------------------------------------------------; load_tape: push ax, cx, dx, si mov ax, head_posn ; get where the head is sub ax, start_head_posn ; subtract to get starting cell to write to mov cx, start_tape_len mov si, start_tape_buffer L0: mov dl, [si] inc si call write_tape inc ax loop L0 pop si, dx, cx, ax ret ;------------------------------------------------; ; START_STATE 23-08-1993 ; ; Reads a starting state for the Turing ; ; machine and stores it to Turing_state. ; ;------------------------------------------------; start_state: push ax call read_dec mov error_code, 10 cmp ax, MAX_STATE jae >L0 mov Turing_state, ax mov ax, 16 ; announce success call message_out clc jmp >E0 L0: stc E0: pop ax ret ;------------------------------------------------; ; SET_BATCH_MODE 23-08-1993 ; ; Selects batch mode. ; ;------------------------------------------------; set_batch_mode: push ax mov batch_mode, 1 ; set flag mov ax, 18 call message_out clc ; this always succeeds pop ax ret ;------------------------------------------------; ; SET_OUTPUT_FILENAME 23-08-1993 ; ; Reads up to the next blank from the line_ ; ; buffer and transfers to filename. Then sets ; ; filename_given to 1. ; ;------------------------------------------------; set_output_filename: push ax, si, di, es mov di, filename push ds pop es eat_blanks L0: lodsb cmp al, ' ' je >L1 cmp al, 0D je >L1 stosb jmp L0 L1: xor al, al ; write NUL terminator stosb mov filename_given, 1 ; indicate that we have been given a filename mov ax, 19 call message_out clc ; this always succeeds pop es, di, si, ax ret ;------------------------------------------------; ; LOAD_HEX_TAPE 12-09-1993 ; ; Reads in an initial configuration for the ; ; tape in the form of hex digits, each giving ; ; the next four symbols on the tape as its ; ; binary representation. These symbols are ; ; written from the head posn rightwards. The ; ; head is first aligned to an eight-symbol ; ; boundary, ie. to a byte in the storage ; ; array. There must be an even number of hex ; ; digits, ie. a whole number of bytes. ; ;------------------------------------------------; load_hex_tape: push ax, cx, si, di, es push ds ; prepare for string manipulation pop es cld and head_posn, 0FFF8 ; round head position off to a multiple of 8 mov di, head_posn ; get destination for bytes read mov cl, 3 shr di, cl add di, tape eat_blanks L0: lodsb ; read next char cmp al, 0D ; done ? je >L3 ; yes cmp al, ' ' ; done ? je >L3 ; yes cmp al, 'A' ; convert to lower case if upper jb >L1 cmp al, 'Z' ja >L1 or al, bit 5 L1: sub al, '0' cmp al, 10 if ae sub al, 'a'-'0'-10 cmp al, 16 jae >L4 ; error -- not in hex range mov ah, [si] ; read next char inc si cmp ah, 'A' ; convert to lower case if upper jb >L2 cmp ah, 'Z' ja >L2 or ah, bit 5 L2: sub ah, '0' cmp ah, 10 if ae sub ah, 'a'-'0'-10 cmp ah, 16 jae >L4 ; error -- not in hex range mov cl, 4 ; combine two nibbles shl al, cl or al, ah stosb jmp L0 L3: clc jmp >E0 L4: mov error_code, 20 stc E0: pop es, di, si, cx, ax ret ;------------------------------------------------; ; ; ; SIMULATE ; ; ; ;------------------------------------------------; ; CONTAINS: ; ; do_one_cycle ; ; execute_program ; ; execute_batch ; ; simulate ; ; wait_cycle ; ; pause ; ; wait_key ; ; process_keypress ; ;------------------------------------------------; ;------------------------------------------------; ; DO_ONE_CYCLE 22-08-1993 ; ; Performs one cycle of the machine, ie reads ; ; the tape, gets its instructions indexed on ; ; current state and symbol read, then writes a ; ; new symbol, moves left or right, and enters ; ; a new state. ; ; ; ; OUTPUT: ; ; halted - non-zero if the machine has reached ; ; a HALT state ; ;------------------------------------------------; do_one_cycle: push ax, bx, dx inc w [cycles_done] ; update counter of number of cycles executed if z inc w [cycles_done+2] if z inc w [cycles_done+4] mov bx, Turing_state shl bx, 1 ; get major index to the program array mov ax, head_posn ; read symbol under head call read_tape xor ah, ah ; and add in minor index add bx, ax shl bx, 1 ; multiply by length of each entry shl bx, 1 add bx, program ; and add to base of array mov dh, [bx+3] ; get direction to move cmp dh, -2 ; is this state defined ? je >L2 ; no -- go to HALT state mov dl, [bx+2] ; get symbol to write mov ax, head_posn call write_tape ; and write it test dh ; enter HALT condition ? js >L2 ; yes -- do so test move_tape ; are we supposed to be moving the tape or the head ? if nz dec dh ; if tape, left (0) becomes -1, right (1) becomes 0 test dh jz >L0 call move_right ; move head right / tape left jmp >L1 L0: call move_left ; move head left / tape right L1: push [bx] ; enter new state pop Turing_state jmp >E0 L2: mov halted, 1 E0: pop dx, bx, ax ret ;------------------------------------------------; ; EXECUTE_PROGRAM 23-08-1993 ; ; Performs cycles of the Turing machine as ; ; described in the documentation for inter- ; ; active mode. ; ;------------------------------------------------; execute_program: call show_mode L0: call show_tape_round_head call show_current_state call show_state_table call wait_cycle ; this either waits for a keypress or for a while and ; deals with any keypress which occurs call do_one_cycle test halted jnz >L1 jmp L0 L1: ret ;------------------------------------------------; ; EXECUTE_BATCH 23-08-1993 ; ; Runs the Turing machine in batch mode until ; ; either the machine reaches a HALT condition ; ; or the running is interrupted by the user ; ; pressing Esc. ; ;------------------------------------------------; execute_batch: push ax, dx test filename_given ; first check whether we were given a dump filename jnz >L0 ; yes -- proceed mov dx, batch_cant_t ; no -- give can't continue message push ds, cs pop ds xor al, al call write_con pop ds jmp >L3 L0: mov dx, batch_running_t push ds, cs pop ds xor al, al call write_con pop ds mov ax, Turing_state ; set up values to register variables mov cx, head_posn and cl, 7 mov dl, bit 7 shr dl, cl mov si, head_posn mov cl, 3 shr si, cl add si, tape L0: inc w [cycles_done] if z inc w [cycles_done+2] if z inc w [cycles_done+4] mov bx, ax ; get state number shl bx, 1 ; *2 for major index test [si], dl ; add in minor index if nz inc bx shl bx, 1 ; multiply by length of each entry (4) shl bx, 1 mov dh, [program+bx+3] ; this state cmp dh, -2 ; undefined ? je >L6 ; yes test b [program+bx+2] ; write a 0 ? jz >L1 ; yes or [si], dl ; no -- write a 1 jmp >L2 L1: not dl ; write a 0 and [si], dl not dl L2: test dh ; enter halt condition (dirn = 'H') ? js >L6 ; yes test move_tape ; are we to move the head or the tape ? if nz dec dh ; adjust as nec. test dh ; move the head left/tape right ? jz >L3 ; yes clc ; move head right/tape left rcr dl, 1 jnc >L4 rcr dl, 1 inc si jmp >L4 L3: clc ; move head left/tape right rcl dl, 1 jnc >L4 rcl dl, 1 dec si L4: mov ah, 1 ; interrupted by user ? int 016 jnz >L5 ; maybe mov ax, [program+bx] ; no -- enter new state jmp L0 ; and loop L5: xor ah, ah ; get the key pressed int 016 cmp al, 01B ; was it 'Esc' ? je >L8 ; yes -- interrupt processing mov ax, [program+bx] ; no -- get new state jmp L0 ; and loop L6: sub si, tape ; make a note of the final head position mov cl, 3 shl si, cl xor cx, cx ; convert bit position to range 0-7 L7: shl dl, 1 loopnz L7 not cx add si, cx ; and add it in mov head_posn, si mov dx, batch_halted_t push ds, cs pop ds xor al, al call write_con pop ds call dump_tape mov dx, batch_dumped_t push ds, cs pop ds xor al, al call write_con pop ds jmp >L9 L8: mov dx, batch_interrupted_t push ds, cs pop ds xor al, al call write_con pop ds L9: pop dx, ax ret ;------------------------------------------------; ; SIMULATE 23-08-1993 ; ; Performs the actual simulation. ; ;------------------------------------------------; simulate: push ax, dx, ds test batch_mode ; are we in batch mode ? jnz >L1 ; yes -- skip (message and wait for keypress) mov dx, ready_to_go_t push ds, cs pop ds mov al, 1 call write_con ; we can't use DOS fn 9 as this is redirectable pop ds call get_key ; wait for a keypress call set_up_screen call execute_program call show_halted call get_key cmp al, 'W' je >L0 cmp al, 'w' jne >E0 L0: test filename_given if nz call dump_tape jmp >E0 L1: call execute_batch ; run in batch mode jmp quit ; and exit gracefully E0: pop ds, dx, ax ret ;------------------------------------------------; ; WAIT_CYCLE 23-08-1993 ; ; If we are in single-step mode, waits for a ; ; keypress. If in continuous mode, waits for ; ; pause_length vertical retraces. If a key is ; ; pressed, acts on it. ; ;------------------------------------------------; wait_cycle: push ax test single_step_mode jz >L1 L0: call wait_key jmp >L2 L1: call pause test al ; timed out ? (AL=0 if so) jz >L2 call process_keypress ; deal with the key pressed test single_step_mode ; did we return to single-step mode ? jnz L0 ; yes -- then wait for a keypress L2: pop ax ret ;------------------------------------------------; ; PAUSE 23-08-1993 ; ; Waits for the number of vertical retraces ; ; given in pause_length. The pause can be ; ; cut sort by a keypress. ; ; ; ; OUTPUT: ; ; AL - ascii code of keypress, -1 if timed out ; ;------------------------------------------------; pause: push cx, dx mov cx, pause_length L0: mov dx, 03DA ; wait for vertical retrace in al, dx test al, 1000xB jnz L0 L1: in al, dx test al, 1000xB jz L1 mov ah, 1 int 016 loopz L0 ; loop while no key pressed mov ah, 1 ; did we exit because of a keypress ? int 016 jz >L2 ; no mov ah, 0 ; yes -- read it int 016 test al ; was it an extended code ? jnz >L3 ; no -- ok mov al, ah ; yes -- use scancode instead or al, bit 7 ; with top bit set jmp >L3 L2: mov al, -1 ; indicate timed out L3: pop dx, cx ret ;------------------------------------------------; ; WAIT_KEY 23-08-1993 ; ; Waits for a keypress. If the key pressed ; ; was the spacebar, returns. If one of the ; ; special action keys, acts on it. Keeps ; ; getting keypresses until a valid one is ; ; obtained. The results are only valid for ; ; single-step mode. ; ;------------------------------------------------; wait_key: push ax, si L0: call get_key cmp al, 'A' ; if upper case, change to lower jb >L1 cmp al, 'Z' ja >L1 or al, bit 5 L1: cmp al, ' ' ; space ? je >E0 ; yes -- go home cmp al, 01B ; Esc ? if e jmp quit ; yes -- quit cmp al, 'g' ; Go ? jne >L2 mov single_step_mode, 0 ; enter continuous mode call show_mode jmp >E0 L2: cmp al, 'w' ; Write ? jne L0 ; no -- loop round until valid test filename_given ; were we given a filename ? jz L0 ; if not we can't do this mov ax, 44 by 10 ; give status message mov si, dumping_t push ds, cs pop ds call write_text pop ds call dump_tape push ds, cs ; erase message pop ds mov si, end_of_blanks-25 call write_text pop ds jmp L0 ; and wait for another keypress E0: pop si, ax ret ;------------------------------------------------; ; PROCESS_KEYPRESS 23-08-1993 ; ; Given an ascii code of a keypress, deals ; ; with it, assuming we are in continuous mode. ; ; ; ; INPUT: ; ; AL - ascii code of keypress ; ;------------------------------------------------; process_keypress: push ax cmp al, 01B ; Esc ? jne >L0 mov single_step_mode, 1 ; return to single-step mode call show_mode jmp >E0 L0: cmp al, ' ' jne >L1 call get_key ; pause for a keypress jmp >E0 L1: cmp al, UP_ARROW jne >L2 sub pause_length, 5 test pause_length if s mov pause_length, 1 jmp >E0 L2: cmp al, DOWN_ARROW jne >E0 add pause_length, 5 cmp pause_length, 211 if e mov pause_length, 201 E0: pop ax ret ;------------------------------------------------; ; ; ; STACK ; ; ; ;------------------------------------------------; STACK SEGMENT WORD STACK db 0100 dup ? ; a page's worth of stack space STACK ENDS ;------------------------------------------------; ; ; ; STRINGS ; ; ; ;------------------------------------------------; ; CONTAINS: ; ; cmp_str ; ; mve_word ; ;------------------------------------------------; ;------------------------------------------------; ; CMP_STR 22-08-1993 ; ; Compares two asciiZ strings, ignoring case ; ; for alphabetic characters, and returns -1, ; ; 0, 1 according to whether the first is less ; ; than, equal to, or greater than (lexico- ; ; graphically) the second. ; ; ; ; INPUT: ; ; DS:SI - pointer to first string ; ; ES:DI - pointer to second string ; ; ; ; OUTPUT: ; ; AL - -1, 0, or +1 as above ; ;------------------------------------------------; cmp_str: push si, di cld L0: lodsb cmp al, 'A' ; upper case ? jb >L1 cmp al, 'Z' ja >L1 or al, bit 5 ; if so, convert to lower L1: mov ah, es:[di] cmp ah, 'A' ; upper case ? jb >L2 cmp ah, 'Z' ja >L2 or ah, bit 5 ; if so, convert to lower L2: cmp al, ah jne >L3 test al ; reached terminator and they still match ? jz >L5 ; yes -- conveniently, AL holds correct output inc di ; note SI was incremented by LODSB instrn jmp L0 L3: ja >L4 ; flags still have CMP AL, AH mov al, -1 jmp >L5 L4: mov al, 1 L5: pop di, si ret ;------------------------------------------------; ; MVE_WORD 22-08-1993 ; ; Transfers chars from DS:SI to ES:DI until ; ; a non-alphabetic character is reached in the ; ; source. The destination string is then ; ; terminated with a NUL. ; ; ; ; INPUT: ; ; CX - maximum number of chars to move ; ; DS:SI - source of string ; ; ES:DI - destination of string ; ; ; ; OUTPUT: ; ; DS:SI - points to first non-alphabetic char ; ;------------------------------------------------; mve_word: push ax, cx, di cld L0: lodsb cmp al, 'A' jb >L2 cmp al, 'Z' jbe >L1 cmp al, 'a' jb >L2 cmp al, 'z' ja >L2 L1: stosb loop L0 L2: xor al, al stosb dec si ; adjust to point to first non-alphabetic char pop di, cx, ax ret ;------------------------------------------------; ; ; ; SYSTEM INTERFACE ; ; ; ;------------------------------------------------; ; CONTAINS: ; ; no_cursor ; ; dump_to_dos ; ;------------------------------------------------; ;------------------------------------------------; ; NO_CURSOR 23-08-1993 ; ; Disables the flashing hardware text cursor ; ; for page 1 by setting it to be displayed at ; ; coords (0,25). ; ;------------------------------------------------; no_cursor: push ax, bx, dx mov ah, 2 mov bh, 1 mov dx, 25 by 0 int 010 pop dx, bx, ax ret ;------------------------------------------------; ; DUMP_TO_DOS 21-08-1993 ; ; Returns to Dos with errorlevel set to 1. ; ;------------------------------------------------; dump_to_dos: mov ax, 04C01 int DOS ;------------------------------------------------; ; QUIT 23-08-1993 ; ; Tidies up and goes home. ; ;------------------------------------------------; quit: test batch_mode ; if we are running in batch mode jnz >L0 ; don't reset the screen mov ax, 5 by 0 ; reselect page 0 int 010 L0: mov dx, bye_bye_t push cs pop ds xor al, al call write_con mov ax, 04C00 int DOS ;------------------------------------------------; ; ; ; TAPE OPERATIONS ; ; ; ;------------------------------------------------; ; CONTAINS: ; ; read_tape ; ; write_tape ; ; move_right ; ; move_left ; ;------------------------------------------------; ;------------------------------------------------; ; READ_TAPE 18-08-1993 ; ; Returns the symbol in the given cell of the ; ; tape. ; ; ; ; INPUT: ; ; AX - cell to read ; ; ; ; OUTPUT: ; ; AL - symbol ; ;------------------------------------------------; read_tape: push bx, cx mov bx, ax ; get pointer to byte containing cell mov cl, 3 shr bx, cl add bx, tape mov cl, al ; get position within byte and cl, 7 mov ch, bit 7 ; set up mask shr ch, cl ; and shift to correct position xor al, al ; set up output for symbol zero test [bx], ch ; were we right ? if nz inc al ; if not, adjust pop cx, bx ret ;------------------------------------------------; ; WRITE_TAPE 18-08-1993 ; ; Writes the given symbol to the given cell of ; ; the tape. ; ; ; ; INPUT: ; ; AX - cell to write to ; ; DL - symbol to write ; ;------------------------------------------------; write_tape: push bx, cx mov bx, ax ; get pointer to byte containing cell mov cl, 3 shr bx, cl add bx, tape mov cl, al ; get position within byte and cl, 7 mov ch, bit 7 ; set up mask shr ch, cl ; and shift to correct position test dl ; write a '0' ? jz >L0 ; yes or [bx], ch ; no -- OR a '1' into the tape jmp >L1 L0: not ch and [bx], ch ; AND a '0' into the tape L1: pop cx, bx ret ;------------------------------------------------; ; MOVE_RIGHT 18-08-1993 ; ; Moves the head right one cell, checking for ; ; tape overrun. Causes a fatal error and DTD ; ; if there is a tape overrun. ; ;------------------------------------------------; move_right: inc head_posn ; right one cell cmp head_posn, TAPE_LENGTH ; overrun ? je >L0 ; yes -- error and DTD ret L0: mov ax, 12 ; message 'tape overrun' call error_out jmp dump_to_dos ;------------------------------------------------; ; MOVE_LEFT 18-08-1993 ; ; Moves the head left one cell, checking for ; ; tape overrun. Causes a fatal error and DTD ; ; if there is a tape overrun. ; ;------------------------------------------------; move_left: dec head_posn ; left one cell cmp head_posn, -1 ; overrun ? je >L0 ; yes -- error and DTD ret L0: mov ax, 12 ; message 'tape overrun' call error_out jmp dump_to_dos DGROUP SEGMENT BYTE PUBLIC DGROUP ENDS