;;; ;;; MC3 line editor v1.2 for MCFS2 ;;; By: Daniel Tufvesson 2017-2022 ;;; mon_pdata equ $c009 ; print text string @ x ended by $04 mon_pcrlf equ $c021 ; print crlf sys_return equ $d003 ; MCFS2 return sys_call equ $d006 ; MCFS2 system call consvec equ $7fe5 ; console status vector conovec equ $7fe8 ; console output vector conivec equ $7feb ; console input vector ;;; ;;; Program start ;;; org $a100 ;;; Clear memory pshx jsr mem_clear pulx ;;; Load file to edit ;;; Save filename (first argument) jsr str_skp_next tst ,x bne *+5 jmp prompt ; no file name provided jsr copy_to_fname ; copy file name argument to local buffer ldx #file_name ; set file name in fcb stx fcb0+2 ;;; Load file info ldaa #$01 ; check file fommand staa fcb0 ldx #fcb0 jsr sys_call tst fcb0+1 ; read error code bne file_not_found ;;; Check file ldaa fcb0+4 ; get flags bita #$40 bne file_is_directory ;;; Calculate end of file ldx fcb0+7 bne file_too_big ldx fcb0+9 ldab #$ff abx stx xtemp1 ; save file end cpx mem_end bhi file_too_big ;;; Load file contents into memory ldaa #$10 ; load file fommand staa fcb0 ldx mem_start stx fcb0+4 ldx #fcb0 jsr sys_call tst fcb0+1 ; read error code bne file_read_error ;;; Make sure last line of file has a line ending ldx xtemp1 * ldaa ,x * cmpa eol * beq load_complete inx ldaa eol staa ,x load_complete: jmp prompt ;;; File error handlers file_not_found: ldx #txt_not_found jsr mon_pdata bra cmd_quit file_read_error: ldx #txt_read_error jsr mon_pdata bra cmd_quit file_is_directory: ldx #txt_is_directory jsr mon_pdata bra cmd_quit file_too_big: ldx #txt_out_of_memory jsr mon_pdata bra cmd_quit ;;; ;;; Command prompt ;;; prompt: ldx #txt_prmt jsr mon_pdata jsr clbuf ; clear existing line buffer jsr edit_line bcc prompt_abort jsr mon_pcrlf ldaa line_buffer ; get first char beq prompt anda #$df cmpa #'I bne *+5 jmp cmd_insert cmpa #'D bne *+5 jmp cmd_delete cmpa #'P bne *+5 jmp cmd_print cmpa #'R bne *+5 jmp cmd_replace cmpa #'S bne *+5 jmp cmd_save cmpa #'H bne *+5 jmp cmd_help cmpa #'N bne *+5 jmp cmd_numbers cmpa #'Q bne *+5 jmp cmd_quit ldaa #'? jsr conovec prompt_abort: jsr mon_pcrlf bra prompt ;;; ;;; Quit command ;;; cmd_quit: jmp sys_return ;;; ;;; Insert command ;;; cmd_insert: ldx #line_buffer ; read line number argument jsr str_skp_next jsr parse_dec ldx xtemp1 cpx #0 bne cmd_insert_verify jsr find_last bra cmd_insert_loop cmd_insert_verify: stx xtemp1 jsr find_last ; find last line in memory ldx xtemp1 cpx line_pointer bhi cmd_insert_end ; make sure given line is valid stx line_pointer jsr find_line cmd_insert_loop: jsr mon_pcrlf jsr clbuf ; clear line buffer tst line_numbers_enable beq cmd_insert2 ; line numbers enabled? ldx #line_pointer jsr outdec ; print line number * ldx #mem_pointer * jsr mon_out4hs ldx #txt_zdel jsr mon_pdata cmd_insert2: jsr edit_line ; read line into buffer bcc cmd_insert_end jsr mem_insert ; insert new line into memory bcs cmd_insert_loop ; insert OK, enter next line ldx #txt_out_of_memory ; out of memory jsr mon_pdata cmd_insert_end: jsr mon_pcrlf jsr mon_pcrlf jmp prompt ;;; ;;; Delete command ;;; cmd_delete: ldx #line_buffer ; read line number argument jsr str_skp_next jsr parse_dec bcs cmd_delete_end jsr find_last ; find last line in memory ldx xtemp1 cpx #0 beq cmd_delete_end ; line zero is invalid cpx line_pointer bhi cmd_delete_end ; make sure given line is valid stx line_pointer jsr find_line jsr mem_delete cmd_delete_end: jsr mon_pcrlf jmp prompt ;;; ;;; Print command ;;; cmd_print: ldx #line_buffer ; read line number argument jsr str_skp_next jsr parse_dec bcc cmd_print0 ; when invalid line print from start ldx xtemp1 cpx #0 bne cmd_print0 ; when line is zero print from start ldx #1 stx xtemp1 cmd_print0: jsr find_last ; find last line in memory ldx xtemp1 cpx line_pointer bhi cmd_print_end ; make sure given line is valid stx line_pointer jsr find_line jsr mon_pcrlf ldaa term_lines staa temp cmd_print1: jsr print_line ldx mem_pointer tst ,x ; all done? beq cmd_print_end jsr conivec cmpa term_escape ; escape? beq cmd_print_end cmpa #$0d beq cmd_print2 ldaa term_lines staa temp bra cmd_print1 cmd_print2: ldaa #1 staa temp bra cmd_print1 cmd_print_end: jsr mon_pcrlf jmp prompt ;;; ;;; Replace command ;;; cmd_replace: ldx #line_buffer ; read line number argument jsr str_skp_next jsr parse_dec bcs cmd_replace_end jsr find_last ; find last line in memory ldx xtemp1 beq cmd_replace_end ; line zero is invalid cpx line_pointer bhi cmd_replace_end ; make sure given line is valid stx line_pointer jsr find_line jsr mon_pcrlf ;;; Print existing line ldaa #1 staa temp ; print single line jsr print_line ;;; Edit line ldx line_pointer ; restore line pointer since dex ; ends with line_pointer + 1 stx line_pointer tst line_numbers_enable beq cmd_replace1 ; line numbers enabled? ldx #line_pointer jsr outdec * ldx #mem_pointer * jsr mon_out4hs ldx #txt_zdel jsr mon_pdata cmd_replace1: jsr clbuf ; clear existing line buffer jsr edit_line ; enter new line bcc cmd_replace_end ;;; Replace line in memory jsr find_line jsr mem_insert ; insert new line into memory bcs cmd_replace2 ; insert OK, enter next line ldx #txt_out_of_memory ; out of memory jsr mon_pdata jsr mon_pcrlf jmp prompt cmd_replace2: jsr find_line jsr mem_delete ; delete old line cmd_replace_end: jsr mon_pcrlf jmp prompt ;;; ;;; Save command ;;; cmd_save: ldx #txt_name jsr mon_pdata jsr fname_to_buffer ; prepare file name for user edit jsr edit_line bcc save_abort jsr buffer_to_fname jsr mon_pcrlf ldx #file_name stx fcb0+2 jsr find_last ; update mem_pointer to end of file contents ldaa #$08 ; delete file command staa fcb0 ldx #fcb0 jsr sys_call ldx mem_start stx fcb0+4 ldx mem_pointer cpx mem_start beq cmd_save1 dex ; adjust to end of file data cpx mem_start beq cmd_save1 ldaa ,x cmpa eol bne cmd_save1 dex ; remove last eol character cmd_save1: stx fcb0+6 ldaa #$20 ; save file command staa fcb0 ldaa #$03 ; set file flags staa fcb0+8 ldx #fcb0 jsr sys_call ldx #fcb0 ; check for error tst 1,x bne save_error ldx #txt_save_ok jsr mon_pdata jmp prompt save_abort: jsr mon_pcrlf jmp prompt save_error: ldx #txt_save_error jsr mon_pdata ldx #fcb0 jsr out4hs jsr mon_pcrlf jmp prompt ;;; ;;; Help command ;;; cmd_help: ldx #txt_help jsr mon_pdata jmp prompt ;;; ;;; Line numbers commandd ;;; cmd_numbers: tst line_numbers_enable beq cmd_numbers0 clr line_numbers_enable ldx #txt_off jsr mon_pdata jsr mon_pcrlf jmp prompt cmd_numbers0: inc line_numbers_enable ldx #txt_on jsr mon_pdata jsr mon_pcrlf jmp prompt ;;; ;;; Clear memory ;;; mem_clear: ldx mem_start mem_clear_loop: clr ,x inx cpx mem_end bne mem_clear_loop rts ;;; ;;; Clear line buffer ;;; clbuf: ldx #line_buffer clrb clbufl: clr ,x inx incb cmpb #line_buffer_length bne clbufl rts ;;; ;;; Decimal output routine borrowed from FLEX. Modified to make use of D-reg. ;;; IN: X = pointer to 16-bit unsigned binary number ;;; outdec: clr outdec_digit ldaa #4 staa outdec_loop1 ldd 0,x ldx #decimal_table ;;; Iterate over table for each position outdec_1: bsr outdec_2 inx inx dec outdec_loop1 bne outdec_1 tba jmp outhr ;;; Binary to decimal conversion outdec_2: clr outdec_loop2 outdec_3: cmpa 0,x bcs outdec_5 bhi outdec_4 cmpb 1,x bcs outdec_5 outdec_4: subd 0,x inc outdec_loop2 bra outdec_3 ;;; Print digit or leading space outdec_5: psha ldaa outdec_loop2 bne outdec_6 tst outdec_digit bne outdec_6 ldaa #$20 jsr conovec bra outdec_7 outdec_6: inc outdec_digit jsr outhr outdec_7: pula rts ;;; Decimal table decimal_table: fdb 10000 fdb 1000 fdb 100 fdb 10 ;;; ;;; Print hex values ;;; outhl: lsra lsra lsra lsra outhr: anda #$0f adda #$30 cmpa #$39 bls outhe adda #$27 ; $07=lower case & $27=upper case outhe: jmp conovec out2h: ldaa 0,x jsr outhl ldaa 0,x inx jmp outhr out4hs: bsr out2h out2hs: bsr out2h ;;; ;;; Parse decimal value from ASCII string at X ;;; Result in variable 'xtemp1' ;;; parse_dec: clr xtemp1 clr xtemp1+1 ldaa ,x beq parse_dec_err cmpa #$20 beq parse_dec_err parse_dec_loop: ldaa ,x beq parse_dec_end cmpa #$20 beq parse_dec_end jsr char_to_hex bcc parse_dec_err cmpa #$09 bgt parse_dec_err jsr mult10 clr xtemp2 ; add value staa xtemp2+1 ldd xtemp1 addd xtemp2 std xtemp1 inx bra parse_dec_loop parse_dec_end: clc rts parse_dec_err: sec rts ;;; ;;; Multiply variable 'xtemp1' by 10 ;;; mult10: psha pshb ldd xtemp1 asld ; * 2 std temp asld ; * 4 asld ; * 8 addd temp ; D = D*8+D*2 std xtemp1 pulb pula rts ;;; ;;; Convert ASCII char to hex ;;; IN: Chanracter in A ;;; OUT: Value in A. Carry set on OK ;;; char_to_hex: beq char_to_hex_fail ; end of string suba #$30 bmi char_to_hex_fail ; not hex cmpa #$09 ble char_to_hex_ok ; $9 and below anda #$df ; to upper case cmpa #$11 bmi char_to_hex_fail ; not hex cmpa #$16 bgt char_to_hex_fail ; not hex suba #7 char_to_hex_ok: sec rts char_to_hex_fail: clc rts ;;; ;;; Find next word in string pointed at by X ;;; str_skp_next: ldaa ,x beq str_skp_next_e cmpa #$20 beq str_skp_next_1 inx bra str_skp_next str_skp_next_1: ldaa ,x beq str_skp_next_e cmpa #$20 bne str_skp_next_e inx bra str_skp_next_1 str_skp_next_e: rts ;;; ;;; Print zero terminated string ;;; str_print: ldaa ,x beq str_print_end jsr conovec inx bra str_print str_print_end: rts ;;; ;;; Edit line in buffer ;;; updates line_length ;;; carry set on ok ;;; carry clear on abort ;;; edit_line: clrb ldx #line_buffer edit_line_print_loop: ldaa ,x ; print existing buffer cmpa #$00 beq edit_line_loop ; found end of buffer jsr conovec inx incb cmpb #line_buffer_length bhi *+4 bra edit_line_print_loop edit_line_loop: jsr conivec ; get character from acia cmpa term_escape ; is character escape ($1b)? beq edit_line_es ; exit cmpa #$7f ; is character delete ($7f) or non-printable? beq edit_line_loop ; read new character bhi edit_line_loop ; read new character cmpa #$08 ; is character a backspace ($08)? beq edit_line_bs cmpa #$09 ; is character a tab ($09)? beq edit_line_tab cmpa #$0d ; is character a carriage return ($0d)? beq edit_line_cr cmpa #$1f ; is character nonprintable? bls edit_line_loop ; read new character jmp edit_line_ok ; if we have come this far character is ok edit_line_bs: cmpb #0 beq edit_line_loop ; if at beginning of buffer dont delete dex decb jsr conovec ; print the recieved backspace ldaa #$20 jsr conovec ; print space ldaa #$08 jsr conovec ; print backspace clra staa ,x ; clear character from buffer (store $00 at its place) jmp edit_line_loop ; read new character edit_line_tab: cmpb #line_buffer_length beq edit_line_loop ; if at end of buffer dont enter new character ldaa #$20 jsr conovec staa ,x inx incb tba anda #$07 bne edit_line_tab jmp edit_line_loop ; read new character edit_line_ok: cmpb #line_buffer_length beq edit_line_loop ; if at end of buffer dont enter new character jsr conovec ; echo entered character staa ,x inx incb jmp edit_line_loop ; read new character edit_line_cr: stab line_length clr ,x ; terminate string sec ; entry OK rts edit_line_es: stab line_length clc ; entry aborted rts ;;; ;;; Copy file name to/from line buffer ;;; fname_to_buffer: clr line_length ldx #file_name stx xtemp1 ldx #line_buffer stx xtemp2 fname_to_buffer_loop: ldx xtemp1 ldaa ,x beq fname_to_buffer_done inx stx xtemp1 ldx xtemp2 staa ,x inx stx xtemp2 inc line_length bra fname_to_buffer_loop fname_to_buffer_done: ldx xtemp2 clr ,x rts buffer_to_fname: clr line_length ldx #line_buffer stx xtemp1 ldx #file_name stx xtemp2 bra fname_to_buffer_loop ;;; ;;; Copy string at X to file name buffer ;;; copy_to_fname: stx xtemp1 ldx #file_name stx xtemp2 ldab #$64 copy_to_fname_loop: ldx xtemp1 ldaa ,x beq copy_to_fname_done cmpa #$20 beq copy_to_fname_done inx stx xtemp1 ldx xtemp2 staa ,x inx stx xtemp2 decb bvc copy_to_fname_loop copy_to_fname_done: ldx xtemp2 clr ,x rts ;;; ;;; Insert line from buffer ;;; into memory at mem_pointer ;;; updates line_pointer and mem_pointer ;;; carry set on OK ;;; carry clear on fail ;;; mem_insert: ldx mem_pointer ldaa ,x cmpa #$00 ; non-existing line in memory? beq mem_insert1 jsr mem_make_room ; make room for new line bcc mem_insert_fail ; unable to make room in memory for new line mem_insert1: ldx #line_buffer stx xtemp1 ldab line_length mem_insert2: tstb beq mem_insert3 ldx xtemp1 ldaa ,x inx stx xtemp1 ldx mem_pointer staa ,x inx stx mem_pointer inx cpx mem_end beq mem_insert_oom ; out of memory decb bra mem_insert2 mem_insert3: ldx mem_pointer cpx mem_end beq mem_insert_oom ; out of memory ldaa eol staa ,x inx stx mem_pointer ldx line_pointer inx stx line_pointer sec ; line insert OK rts mem_insert_oom: ldx mem_pointer ; make sure last line ends with EOL ldaa eol staa ,x mem_insert_fail: clc ; line insert failed rts ;;; ;;; Make room in memory for new line ;;; uses line_length and mem_pointer ;;; carry set on OK ;;; carry clear on fail ;;; mem_make_room: ldx mem_pointer mem_make_room1: ldaa ,x ; find end of file inx cmpa #$00 bne mem_make_room1 stx xtemp1 ldab line_length incb abx stx xtemp2 cpx mem_end bhi mem_make_room4 ; out of memory mem_make_room2: ldx xtemp1 ldaa ,x dex stx xtemp1 ldx xtemp2 staa ,x dex stx xtemp2 ldx xtemp1 cpx mem_pointer inx bhi mem_make_room2 mem_make_room3: sec rts mem_make_room4: clc rts ;;; ;;; Delete line from memory given by mem_pointer ;;; mem_delete: ldx mem_pointer stx xtemp1 ldaa ,x cmpa #$00 ; non-existing line in memory? beq mem_delete_end ldab #1 ; length counter (chars + EOL) mem_delete1: ldaa ,x ; locate end of line cmpa #$00 beq mem_delete2 cmpa eol beq mem_delete2 inx incb bra mem_delete1 mem_delete2: ldx xtemp1 ; copy data abx stx xtemp2 ldaa ,x cmpa #$00 ; end of file? beq mem_delete3 ldx xtemp1 staa ,x inx stx xtemp1 bra mem_delete2 mem_delete3: ldx xtemp2 ; clear last line in memory mem_delete4: dex clr ,x cpx xtemp1 bne mem_delete4 mem_delete_end: rts ;;; ;;; Find last line_pointer and mem_pointer ;;; find_last: ldx #1 stx line_pointer ldx mem_start find_last1: cpx mem_end ; end of memory? beq find_last2 stx mem_pointer ldaa ,x cmpa #$00 ; end of file found? beq find_last2 cmpa eol ; end of line found? beq find_last3 inx bra find_last1 find_last2: rts find_last3: ldd line_pointer ; increase line counter addd #1 std line_pointer inx bra find_last1 ;;; ;;; Find mem_pointer from line_pointer ;;; find_line: ldx #1 stx xtemp1 ldx mem_start stx mem_pointer ldx line_pointer cpx #1 beq find_line2 ; special case for first line ldx mem_pointer find_line1: cpx mem_end ; end of memory beq find_line2 ldaa ,x cmpa #$00 ; end of file found? beq find_line2 cmpa eol ; end of line found? beq find_line3 inx stx mem_pointer bra find_line1 find_line2: rts find_line3: ldd xtemp1 ; increase line counter addd #1 std xtemp1 ldaa line_pointer+1 ; line found? cmpa xtemp1+1 bne find_line4 ldaa line_pointer cmpa xtemp1 bne find_line4 inx stx mem_pointer rts find_line4: inx bra find_line1 ;;; ;;; Print lines ;;; start at mem_pointer ;;; end after number of lines ;;; given by val in temp ;;; print_line: ldx mem_pointer cpx mem_end bhi print_line_end ; end of mem? ldaa ,x cmpa #0 beq print_line_end ; end of file? tst temp bmi print_line1 ; no pause? beq print_line_end ; all done? dec temp print_line1: tst line_numbers_enable beq print_line2 ; line numbers enabled? ldx #line_pointer jsr outdec * ldx #mem_pointer * jsr mon_out4hs ldx #txt_zdel jsr mon_pdata print_line2: ldx mem_pointer ldaa ,x inx stx mem_pointer cmpa eol ; end of line? beq print_line3 jsr conovec bra print_line2 print_line3: ldx line_pointer inx stx line_pointer jsr mon_pcrlf bra print_line print_line_end: rts ;;; ;;; Texts ;;; txt_prmt: fcc ":: " fcb $04 txt_sure: fcc "Sure? " fcb $04 txt_out_of_memory: fcb $0d,$0a,$0a fcc " Out of memory" fcb $0d,$0a,$0a,$04 txt_help: fcb $0d,$0a fcc " Line editor v1.2" fcb $0d,$0a fcc " Daniel Tufvesson 2017-2022" fcb $0d,$0a,$0a fcc " Insert " fcb $0d,$0a fcc " Delete " fcb $0d,$0a fcc " Print " fcb $0d,$0a fcc " Replace " fcb $0d,$0a fcc " Save" fcb $0d,$0a fcc " Help" fcb $0d,$0a fcc " Numbers (on/off)" fcb $0d,$0a fcc " Quit" fcb $0d,$0a,$0a,$04 txt_zdel: fcc " : " fcb $04 txt_on: fcc "on" fcb $04 txt_off: fcc "off" fcb $04 txt_not_found: fcc "File not found" fcb $0d,$0a,$04 txt_read_error: fcc "File read error" fcb $0d,$0a,$04 txt_is_directory: fcc "Entry is a directory" fcb $0d,$0a,$04 txt_name: fcc "Name: " fcb $04 txt_save_ok: fcc "Save OK" fcb $0d,$0a,$04 txt_save_error: fcc "Save failed! $" fcb $04 ;;; ;;; Variables ;;; line_buffer: rmb 64 ; line buffer line_buffer_length: equ 63 ; line buffer length (leave room for trailing zero) line_length: rmb 1 ; line length mem_start: fdb $0100 ; start of memory mem_end: fdb $7dff ; end of memory mem_pointer: rmb 2 ; memory pointer line_pointer: rmb 2 ; line pointer temp: rmb 1 xtemp1: rmb 2 xtemp2: rmb 2 outdec_digit: rmb 1 ; decimal print digit counter outdec_loop1: rmb 1 ; decimal print loop1 counter outdec_loop2: rmb 1 ; decimal print loop2 counter line_numbers_enable: fcb $01 ; enable line numbers eol: fcb $0a ; end of line marker term_lines: fcb 20 ; number of terminal lines term_escape: fcb $1b ; escape code file_name: fcc "untitled" fcb $0 rmb 55 ;;; ;;; File Control Block ;;; fcb0: fcb 0 ; command fcb 0 ; error code fdb 0 ; file name fcb 0 ; flags fdb 0 ; sectors fdb 0 ; size MSB fdb 0 ; size LSB fdb 0 ; year fcb 0 ; month fcb 0 ; day fcb 0 ; hours fcb 0 ; minutes fcb 0 ; seconds