Here's a little novelty I banged up. 86 lines of BASIC (3314 bytes tokenized) for a decently featured assembling/disassembling monitor.
I tried to write something like this back when I was a wee little ember with a C64, some docs, and no dev tools, but couldn't hack it back then. After recently seeing an old book with a type-in monitor (and screaming internally about its performance & size issues), I decided to take another crack at it, many decades later when it's wholly, utterly unnecessary.
If anybody actually decides to try this, let me know if there are any edge case bugs still lurking and I'll still get 'em fixed. (The INPUT statement is horrible and requires hackish workarounds.) Here's a .prg version:
https://white-flame.com/heymon.prgDocumentation:
Code:
;; Commands:
;; a [address] = assemble
;; d [address] = disassemble
;; m [address] = display memory
;; w [address] = write memory, one byte or word at a time
;; g [address] = go run machine code, RTS returns to the monitor
;; s = save
;; l = load
;; +<dec> = dec->hex
;; $<hex> = hex->dec
;; r = display regs
;; r <reg> <val> = set a, x, or y
;;
;; Numbers default to hex, but prefixes $ff and +255 are supported everywhere
Just the BASICs:
Code:
0 goto998
10 data1brk,2ora(x),,,,2ora,2asl,,1php,2ora#,1asl,,,3ora,3asl,
11 data2bpl,2ora()y,,,,2orax,2aslx,,1clc,3oray,,,,3orax,3aslx,
12 data3jsr,2and(x),,,2bit,2and,2rol,,1plp,2and#,1rol,,3bit,3and,3rol,
13 data2bmi,2and()y,,,,2andx,2rolx,,1sec,3andy,,,,3andx,3rolx,
14 data1rti,2eor(x),,,,2eor,2lsr,,1pha,2eor#,1lsr,,3jmp,3eor,3lsr,
15 data2bvc,2eor()y,,,,2eorx,2lsrx,,1cli,3eory,,,,3eorx,3lsrx,
16 data1rts,2adc(x),,,,2adc,2ror,,1pla,2adc#,1ror,,3jmp(),3adc,3ror,
17 data2bvs,2adc()y,,,,2adcx,2rorx,,1sei,3adcy,,,,3adcx,3rorx,
18 data,2sta(x),,,2sty,2sta,2stx,,1dey,,1txa,,3sty,3sta,3stx,
19 data2bcc,2sta()y,,,2styx,2stax,2stxy,,1tya,3stay,1txs,,,3stax,,
20 data2ldy#,2lda(x),2ldx#,,2ldy,2lda,2ldx,,1tay,2lda#,1tax,,3ldy,3lda,3ldx,
21 data2bcs,2lda()y,,,2ldyx,2ldax,2ldxy,,1clv,3lday,1tsx,,3ldyx,3ldax,3ldxy,
22 data2cpy#,2cmp(x),,,2cpy,2cmp,2dec,,1iny,2cmp#,1dex,,3cpy,3cmp,3dec,
23 data2bne,2cmp()y,,,,2cmpx,2decx,,1cld,3cmpy,,,,3cmpx,3decx,
24 data2cpx#,2sbc(x),,,2cpx,2sbc,2inc,,1inx,2sbc#,1nop,,3cpx,3sbc,3inc,
25 data2beq,2sbc()y,,,,2sbcx,2incx,,1sed,3sbcy,,,,3sbcx,3incx,
49 i=1
50 ba=16:t$=mid$(h$,i,1):ift$="+"ort$="$"thenba=16+6*(t$="+"):i=i+1
51 ifi>len(h$)thend=-1:return
52 d=.:nd=.:fori=itolen(h$):t=asc(mid$(h$,i,1))-48:ift>10thent=t-7
53 ift>=0andt<bathend=d*ba+t:nd=nd+1:next
54 ifnd=0thend=-1:return
55 h=int(d/256):l=d-h*256:ifba=10thennd=2:ifhthennd=4
56 return
60 t=4096:j=1:goto62
61 t=16:j=3
62 forj=jto4:e=(d/t)and15:printchr$(48+e-(e>9)*7);:d=d-e*t:t=t/16:next:return
70 d=p:gosub60:poke631,34:poke198,1:input#5,h$:return
100 gosub70:ifh$=""thenprint:return
101 t=fre(0):iflen(h$)=3thenk$=h$:d=-1:b=1:goto108
102 k$=left$(h$,3):ifmid$(h$,4,1)<>" "thenprint"!!bad syntax":goto100
103 i=5:t$=mid$(h$,5,1):ift$="#"ort$="("thenk$=k$+t$:i=6
104 gosub50:ifd<0ord>65535thenprint"!!bad operand":goto100
105 fori=itolen(h$):t$=mid$(h$,i,1):ift$<>","thenk$=k$+t$
106 next:b=2-(nd>2)
107 ifleft$(k$,1)="b"andleft$(k$,3)<>"brk"thengosub113
108 k$=chr$(48+b)+k$:restore:t=-1:fori=0to255:readt$:ift$<>k$thennext
109 ift$=k$thent=i:i=255:next
110 ift<0thenprint"!!bad instruction":goto100
111 pokep,t:ifb>1thenpokep+1,l:ifb>2thenpokep+2,h
112 p=p+b:print" ok":goto100
113 d=d-p-2:ifd<-128ord>127thenprint"!!bad branch range":goto100
114 l=dand255:b=2:return
150 fork=1to12:t=peek(p):restore:fori=0tot:readk$:next:ifk$=""thenk$="1???"
151 b=val(left$(k$,1)):d=p:gosub60
152 fori=1tob:d=peek(p+i-1):print" ";:gosub61:next
153 printspc(10-b*3);mid$(k$,2,3);" ";
154 i=5:t$=mid$(k$,i,1):ift$="("ort$="#"thenprintt$;:i=6
155 ifb>1thenprint"$";:d=peek(p+1):ifb=3thend=d+256*peek(p+2):gosub60:goto158
156 ifmid$(k$,2,1)="b"andpeek(p)thend=d+2+p+256*(d>=128):gosub60:goto158
157 ifb=2thengosub61
158 fori=itolen(k$):t$=mid$(k$,i,1):ift$="x"ort$="y"thenprint",";
159 printt$;:next:p=p+b:print:next:return
200 fork=1to8:d=p:gosub60:fori=0to7:print" ";:d=peek(p+i):gosub61:next
201 print" ";:fori=0to7:d=peek(p+i):if(dand127)>31thenprintchr$(d);:next
202 if(dand127)<32thenprint"{rvon}";chr$((dand31)+64);"{rvof}";:next
203 print:p=p+8:next:return
250 gosub70:print:ifh$=""thenreturn
251 gosub49:ifd<0ord>65535thenprint" bad number":goto250
252 pokep,l:p=p+1:ifnd>2thenpokep,h:p=p+1
253 goto250
300 sysp:goto354
350 iflen(h$)=1then354
351 i=5:gosub50:ifd<0ord>255thenprint" bad number":return
352 t$=mid$(h$,3,1):fori=1to3:ift$=mid$("axy",i,1)thenpoke(779+i),d
353 next
354 print" a x y":fori=0to2:print" ";:d=peek(780+i):gosub61:next:print:return
400 input"filename,sa,ea";k$,h$,t2$:ifk$=""thenreturn
401 open15,8,15,"s:"+k$:close15:gosub49:sa=d
402 open1,8,1,k$+",p,w":print#1,chr$(l);chr$(h);:h$=t2$:gosub49
403 fori=satod:print#1,chr$(peek(i));:next:close1
404 open15,8,15:input#15,n,m$,t,s:printn;m$;t;s:close15:return
450 input"filename";k$:ifk$=""thenreturn
451 close5:loadk$,8,1:open5,0:goto404
500 gosub49:gosub60:print:return
550 gosub49:printd:return
998 clr:open5,0:p=49152:print"{down}heymon v1 - white flame 2021"
999 h$="r":goto1001
1000 d=p:gosub60:print"> ";:input#5,h$:print
1001 t$=left$(h$,1):k=0:k$="xadmwgrsl+$"
1002 k=0:fori=1tolen(k$):ift$=mid$(k$,i,1)thenk=i:i=99
1003 next:ifk<=6theni=3:gosub50:ifd>=0thenp=d
1004 onkgosub9999,100,150,200,250,300,350,400,450,500,550
1005 goto1000
9999 end
Full documented source:
Code:
;; HeyMon, a monitor written in C64 BASIC
;; by White Flame, 2021
;; Snip lines at 80 chars to eliminate the rhs comments, and trim whitespace:
;; cut -c -80 heymon.txt | sed 's/ *$//' | petcat -w2 -o heymon.prg
;; Commands:
;; a [address] = assemble
;; d [address] = disassemble
;; m [address] = display memory
;; w [address] = write memory, one byte or word at a time
;; g [address] = go run machine code, RTS returns to the monitor
;; s = save
;; l = load
;; +<dec> = dec->hex
;; $<hex> = hex->dec
;; r = display regs
;; r <reg> <val> = set a, x, or y
;;
;; Numbers default to hex, but prefixes $ff and +255 are supported everywhere
; A minimal set of variables are generally reused:
; P = program counter
; H$ = hex/main input string
; D = numeric value of H$, number to print as hex
; H,L = high/low bytes of D
; B = number of bytes in the current instruction
; K$ = instruction key from DATA statements
; T,T$ = temporaries
; I,J,K = iteration counters, I often traverses H$
0 goto998 ;; Jump to main code at end, so data & subroutines are at the top
;;-----------------------
;; Instruction reference
;; Concatenated num bytes, mnemonic, operand prefix char, operand suffix (without commas)
10 data1brk,2ora(x),,,,2ora,2asl,,1php,2ora#,1asl,,,3ora,3asl,
11 data2bpl,2ora()y,,,,2orax,2aslx,,1clc,3oray,,,,3orax,3aslx,
12 data3jsr,2and(x),,,2bit,2and,2rol,,1plp,2and#,1rol,,3bit,3and,3rol,
13 data2bmi,2and()y,,,,2andx,2rolx,,1sec,3andy,,,,3andx,3rolx,
14 data1rti,2eor(x),,,,2eor,2lsr,,1pha,2eor#,1lsr,,3jmp,3eor,3lsr,
15 data2bvc,2eor()y,,,,2eorx,2lsrx,,1cli,3eory,,,,3eorx,3lsrx,
16 data1rts,2adc(x),,,,2adc,2ror,,1pla,2adc#,1ror,,3jmp(),3adc,3ror,
17 data2bvs,2adc()y,,,,2adcx,2rorx,,1sei,3adcy,,,,3adcx,3rorx,
18 data,2sta(x),,,2sty,2sta,2stx,,1dey,,1txa,,3sty,3sta,3stx,
19 data2bcc,2sta()y,,,2styx,2stax,2stxy,,1tya,3stay,1txs,,,3stax,,
20 data2ldy#,2lda(x),2ldx#,,2ldy,2lda,2ldx,,1tay,2lda#,1tax,,3ldy,3lda,3ldx,
21 data2bcs,2lda()y,,,2ldyx,2ldax,2ldxy,,1clv,3lday,1tsx,,3ldyx,3ldax,3ldxy,
22 data2cpy#,2cmp(x),,,2cpy,2cmp,2dec,,1iny,2cmp#,1dex,,3cpy,3cmp,3dec,
23 data2bne,2cmp()y,,,,2cmpx,2decx,,1cld,3cmpy,,,,3cmpx,3decx,
24 data2cpx#,2sbc(x),,,2cpx,2sbc,2inc,,1inx,2sbc#,1nop,,3cpx,3sbc,3inc,
25 data2beq,2sbc()y,,,,2sbcx,2incx,,1sed,3sbcy,,,,3sbcx,3incx,
;;--------------
;; Parse number
;; Parse H$ starting from character I into D/H/L, from "+255", "FF", or "$FF", any number of digits
;; Stops at end of string or at any non-numeric character, leaves I intact on return
;; D = -1 if no digits
;; ND = number of actual digits processed, to distinguish zp $ff from abs $00ff. In dec this fakes 2 or 4 digits based on the number being >255
;; BA = base, either 16 or 10
49 i=1 ;; Force start from the beginning of the string
50 ba=16:t$=mid$(h$,i,1):ift$="+"ort$="$"thenba=16+6*(t$="+"):i=i+1 ;; Calculate base & skip over optional prefix
51 ifi>len(h$)thend=-1:return ;; Empty string fails
52 d=.:nd=.:fori=itolen(h$):t=asc(mid$(h$,i,1))-48:ift>10thent=t-7 ;; Loop through the string, always trying hex digits
53 ift>=0andt<bathend=d*ba+t:nd=nd+1:next ;; Compare with base, loop. If we hit the end of the string, flag with I=-1
54 ifnd=0thend=-1:return ;; No actual digits means exit
55 h=int(d/256):l=d-h*256:ifba=10thennd=2:ifhthennd=4 ;; Exit, keep I and D where they are, calc L and H, calc ND if in decimal mode
56 return
;;---------------------
;; Print hex number
;; Print D has a hex number
;; GOSUB 60 for 16 bit
;; GOSUB 61 for 8 bit
;; We can't use bitwise AND if the number is >32768, so that sucks. Need to whittle down the value of D as we go
60 t=4096:j=1:goto62 ;; Divide by T to get the current hex digit, J=iteration up to 4
61 t=16:j=3
62 forj=jto4:e=(d/t)and15:printchr$(48+e-(e>9)*7);:d=d-e*t:t=t/16:next:return
;;--------------
;; Input a line
70 d=p:gosub60:poke631,34:poke198,1:input#5,h$:return ;; Inject a " into the input buffer, to contain commas and allow empty input
;;-------------
;; Assembler
100 gosub70:ifh$=""thenprint:return ;; Print prompt, input, exit on blank line.
101 t=fre(0):iflen(h$)=3thenk$=h$:d=-1:b=1:goto108 ;; Simple 1 byte instruction, no operand.
;; The FRE prevents ?FORMULA TOO COMPLEX on line 102 after a few instructions?
102 k$=left$(h$,3):ifmid$(h$,4,1)<>" "thenprint"!!bad syntax":goto100 ;; Extract mnemonic into key (K$) & check space
103 i=5:t$=mid$(h$,5,1):ift$="#"ort$="("thenk$=k$+t$:i=6 ;; Check for operand prefix character, append to key
104 gosub50:ifd<0ord>65535thenprint"!!bad operand":goto100 ;; Parse operand
105 fori=itolen(h$):t$=mid$(h$,i,1):ift$<>","thenk$=k$+t$ ;; Add suffix to key, but skipping any commas
106 next:b=2-(nd>2) ;; Calc number of bytes
107 ifleft$(k$,1)="b"andleft$(k$,3)<>"brk"thengosub113 ;; Fixup branch address for all "Bxx" instructions, except BRK
108 k$=chr$(48+b)+k$:restore:t=-1:fori=0to255:readt$:ift$<>k$thennext ;; Search for key in DATA statements, (T=opcode)
109 ift$=k$thent=i:i=255:next
110 ift<0thenprint"!!bad instruction":goto100
111 pokep,t:ifb>1thenpokep+1,l:ifb>2thenpokep+2,h ;; Store instruction into memory
112 p=p+b:print" ok":goto100 ;; Advance
113 d=d-p-2:ifd<-128ord>127thenprint"!!bad branch range":goto100 ;; Subroutine, calc & range check branch destination
114 l=dand255:b=2:return ;; compute operand byte and set instruction length to 2
;;---------------
;; Disassembler
150 fork=1to12:t=peek(p):restore:fori=0tot:readk$:next:ifk$=""thenk$="1???" ;; Get spec string for this opcode byte into K$
151 b=val(left$(k$,1)):d=p:gosub60 ;; Extract number of bytes, print PC
152 fori=1tob:d=peek(p+i-1):print" ";:gosub61:next ;; Print hex dump
153 printspc(10-b*3);mid$(k$,2,3);" "; ;; Print mnemonic
154 i=5:t$=mid$(k$,i,1):ift$="("ort$="#"thenprintt$;:i=6 ;; Print prefix
155 ifb>1thenprint"$";:d=peek(p+1):ifb=3thend=d+256*peek(p+2):gosub60:goto158 ;; Calculate & print operand (16bit)
156 ifmid$(k$,2,1)="b"andpeek(p)thend=d+2+p+256*(d>=128):gosub60:goto158 ;; branch
157 ifb=2thengosub61 ;; 8bit
158 fori=itolen(k$):t$=mid$(k$,i,1):ift$="x"ort$="y"thenprint","; ;; Print suffix, adding in commas before X or Y
159 printt$;:next:p=p+b:print:next:return ;; Advance
;;--------------
;; Memory dump
200 fork=1to8:d=p:gosub60:fori=0to7:print" ";:d=peek(p+i):gosub61:next ;; Dump byte values
201 print" ";:fori=0to7:d=peek(p+i):if(dand127)>31thenprintchr$(d);:next ;; Dump characters
202 if(dand127)<32thenprint"{rvon}";chr$((dand31)+64);"{rvof}";:next
203 print:p=p+8:next:return ;; Loop
;;----------------
;; Memory write
250 gosub70:print:ifh$=""thenreturn ;; Line input
251 gosub49:ifd<0ord>65535thenprint" bad number":goto250 ;; Convert & check
252 pokep,l:p=p+1:ifnd>2thenpokep,h:p=p+1 ;; Poke either 1 or 2 bytes depending on size
253 goto250
;;------------
;; Execute
300 sysp:goto354 ;; Show regs after ML routine finishes
;;-----------------
;; Set or Show Registers
350 iflen(h$)=1then354 ;; If more than just "r", set a register
351 i=5:gosub50:ifd<0ord>255thenprint" bad number":return ;; Parse the numeric parameter
352 t$=mid$(h$,3,1):fori=1to3:ift$=mid$("axy",i,1)thenpoke(779+i),d ;; Set a/x/y by poking into BASIC register buffer
353 next
354 print" a x y":fori=0to2:print" ";:d=peek(780+i):gosub61:next:print:return ;; Register dump
;;--------
;; Save
400 input"filename,sa,ea";k$,h$,t2$:ifk$=""thenreturn
401 open15,8,15,"s:"+k$:close15:gosub49:sa=d
402 open1,8,1,k$+",p,w":print#1,chr$(l);chr$(h);:h$=t2$:gosub49
403 fori=satod:print#1,chr$(peek(i));:next:close1
404 open15,8,15:input#15,n,m$,t,s:printn;m$;t;s:close15:return
;;--------
;; Load
450 input"filename";k$:ifk$=""thenreturn
451 close5:loadk$,8,1:open5,0:goto404 ;; If the load succees, program restarts. Else, reopen keyboard & display error
;;-----------
;; Dec->Hex
500 gosub49:gosub60:print:return
;;-----------
;; Hex->Dec
550 gosub49:printd:return
;;-----------
;; Main loop
998 clr:open5,0:p=49152:print"{down}heymon v1 - white flame 2021" ;; This can re-run after LOAD, so ready for that. Open keyboard for INPUT#
999 h$="r":goto1001 ;; Run the register display on startup, for no good reason
1000 d=p:gosub60:print"> ";:input#5,h$:print ;; Show PC and get input
1001 t$=left$(h$,1):k=0:k$="xadmwgrsl+$" ;; Convert 1st character to an offset in this string
1002 k=0:fori=1tolen(k$):ift$=mid$(k$,i,1)thenk=i:i=99
1003 next:ifk<=6theni=3:gosub50:ifd>=0thenp=d ;; The first few instructions all take an optional param to set the PC
1004 onkgosub9999,100,150,200,250,300,350,400,450,500,550 ;; Dispatch
1005 goto1000 ;; Loop
9999 end