Example Programs This Appendix collects all the programs referred to in the course of this manual.
<filename>hello1.oph</filename> .word $0801 .org $0801 .outfile "hello.prg" .word next, 10 ; Next line and current line number .byte $9e," 2064",0 ; SYS 2064 next: .word 0 ; End of program .advance 2064 ldx #0 loop: lda hello, x beq done jsr $ffd2 inx bne loop done: rts hello: .byte "HELLO, WORLD!", 0
<filename>hello2.oph</filename> .word $0801 .org $0801 .outfile "hello.prg" .scope .word _next, 10 ; Next line and current line number .byte $9e," 2064",0 ; SYS 2064 _next: .word 0 ; End of program .scend .advance 2064 .alias chrout $ffd2 ldx #0 * lda hello, x beq + jsr chrout inx bne - * rts hello: .byte "HELLO, WORLD!", 0
<filename>c64-1.oph</filename> .word $0801 .org $0801 .scope .word _next, 10 ; Next line and current line number .byte $9e," 2064",0 ; SYS 2064 _next: .word 0 ; End of program .scend .advance 2064 .require "../platform/c64kernal.oph"
<filename>c64kernal.oph</filename> ; KERNAL routine aliases (C64) .alias acptr $ffa5 .alias chkin $ffc6 .alias chkout $ffc9 .alias chrin $ffcf .alias chrout $ffd2 .alias ciout $ffa8 .alias cint $ff81 .alias clall $ffe7 .alias close $ffc3 .alias clrchn $ffcc .alias getin $ffe4 .alias iobase $fff3 .alias ioinit $ff84 .alias listen $ffb1 .alias load $ffd5 .alias membot $ff9c .alias memtop $ff99 .alias open $ffc0 .alias plot $fff0 .alias ramtas $ff87 .alias rdtim $ffde .alias readst $ffb7 .alias restor $ff8a .alias save $ffd8 .alias scnkey $ff9f .alias screen $ffed .alias second $ff93 .alias setlfs $ffba .alias setmsg $ff90 .alias setnam $ffbd .alias settim $ffdb .alias settmo $ffa2 .alias stop $ffe1 .alias talk $ffb4 .alias tksa $ff96 .alias udtim $ffea .alias unlsn $ffae .alias untlk $ffab .alias vector $ff8d ; Character codes for the colors. .alias color'0 144 .alias color'1 5 .alias color'2 28 .alias color'3 159 .alias color'4 156 .alias color'5 30 .alias color'6 31 .alias color'7 158 .alias color'8 129 .alias color'9 149 .alias color'10 150 .alias color'11 151 .alias color'12 152 .alias color'13 153 .alias color'14 154 .alias color'15 155 ; ...and reverse video .alias reverse'on 18 .alias reverse'off 146 ; ...and character set .alias upper'case 142 .alias lower'case 14
<filename>hello3.oph</filename> .include "c64-1.oph" .outfile "hello.prg" .macro print ldx #0 _loop: lda _1, x beq _done jsr chrout inx bne _loop _done: .macend .macro greet `print hello1 `print _1 `print hello2 .macend lda #147 jsr chrout `greet target1 `greet target2 `greet target3 `greet target4 `greet target5 `greet target6 `greet target7 `greet target8 `greet target9 `greet target10 rts hello1: .byte "HELLO, ",0 hello2: .byte "!", 13, 0 target1: .byte "PROGRAMMER", 0 target2: .byte "ROOM", 0 target3: .byte "BUILDING", 0 target4: .byte "NEIGHBORHOOD", 0 target5: .byte "CITY", 0 target6: .byte "NATION", 0 target7: .byte "WORLD", 0 target8: .byte "SOLAR SYSTEM", 0 target9: .byte "GALAXY", 0 target10: .byte "UNIVERSE", 0
<filename>hello4a.oph</filename> .include "c64-1.oph" .outfile "hello.prg" .macro print ldx #0 _loop: lda _1, x beq _done jsr chrout inx bne _loop _done: .macend .macro greet lda #30 jsr delay `print hello1 `print _1 `print hello2 .macend lda #147 jsr chrout `greet target1 `greet target2 `greet target3 `greet target4 `greet target5 `greet target6 `greet target7 `greet target8 `greet target9 `greet target10 rts hello1: .byte "HELLO, ",0 hello2: .byte "!", 13, 0 target1: .byte "PROGRAMMER", 0 target2: .byte "ROOM", 0 target3: .byte "BUILDING", 0 target4: .byte "NEIGHBORHOOD", 0 target5: .byte "CITY", 0 target6: .byte "NATION", 0 target7: .byte "WORLD", 0 target8: .byte "SOLAR SYSTEM", 0 target9: .byte "GALAXY", 0 target10: .byte "UNIVERSE", 0 ; DELAY routine. Executes 2,560*(A) NOP statements. delay: tax ldy #00 * nop nop nop nop nop nop nop nop nop nop iny bne - dex bne - rts
<filename>hello4b.oph</filename> .include "c64-1.oph" .outfile "hello.prg" .macro print ldx #0 _loop: lda _1, x beq _done jsr chrout inx bne _loop _done: .macend .macro greet lda #30 jsr delay `print hello1 `print _1 `print hello2 .macend lda #147 jsr chrout lda #lower'case jsr chrout `greet target1 `greet target2 `greet target3 `greet target4 `greet target5 `greet target6 `greet target7 `greet target8 `greet target9 `greet target10 rts hello1: .byte "Hello, ",0 hello2: .byte "!", 13, 0 target1: .byte "programmer", 0 target2: .byte "room", 0 target3: .byte "building", 0 target4: .byte "neighborhood", 0 target5: .byte "city", 0 target6: .byte "nation", 0 target7: .byte "world", 0 target8: .byte "Solar System", 0 target9: .byte "Galaxy", 0 target10: .byte "Universe", 0 ; DELAY routine. Executes 2,560*(A) NOP statements. delay: tax ldy #00 * nop nop nop nop nop nop nop nop nop nop iny bne - dex bne - rts
<filename>hello4c.oph</filename> .include "c64-1.oph" .outfile "hello.prg" .macro print ldx #0 _loop: lda _1, x beq _done jsr chrout inx bne _loop _done: .macend .macro greet lda #30 jsr delay `print hello1 `print _1 `print hello2 .macend lda #147 jsr chrout lda #lower'case jsr chrout `greet target1 `greet target2 `greet target3 `greet target4 `greet target5 `greet target6 `greet target7 `greet target8 `greet target9 `greet target10 rts .charmap 'A, "abcdefghijklmnopqrstuvwxyz" .charmap 'a, "ABCDEFGHIJKLMNOPQRSTUVWXYZ" hello1: .byte "Hello, ",0 hello2: .byte "!", 13, 0 target1: .byte "programmer", 0 target2: .byte "room", 0 target3: .byte "building", 0 target4: .byte "neighborhood", 0 target5: .byte "city", 0 target6: .byte "nation", 0 target7: .byte "world", 0 target8: .byte "Solar System", 0 target9: .byte "Galaxy", 0 target10: .byte "Universe", 0 ; DELAY routine. Executes 2,560*(A) NOP statements. delay: tax ldy #00 * nop nop nop nop nop nop nop nop nop nop iny bne - dex bne - rts
<filename>hello5.oph</filename> .include "c64-1.oph" .outfile "hello.prg" .data .org $C000 .text .macro print ldx #0 _loop: lda _1, x beq _done jsr chrout inx bne _loop _done: .macend .macro greet lda #30 jsr delay `print hello1 `print _1 `print hello2 .macend lda #147 jsr chrout `greet target1 `greet target2 `greet target3 `greet target4 `greet target5 `greet target6 `greet target7 `greet target8 `greet target9 `greet target10 rts hello1: .byte "HELLO, ",0 hello2: .byte "!", 13, 0 target1: .byte "PROGRAMMER", 0 target2: .byte "ROOM", 0 target3: .byte "BUILDING", 0 target4: .byte "NEIGHBORHOOD", 0 target5: .byte "CITY", 0 target6: .byte "NATION", 0 target7: .byte "WORLD", 0 target8: .byte "SOLAR SYSTEM", 0 target9: .byte "GALAXY", 0 target10: .byte "UNIVERSE", 0 ; DELAY routine. Takes values from the Accumulator and pauses ; for that many jiffies (1/60th of a second). .scope .data .space _tmp 1 .space _target 1 .text delay: sta _tmp ; save argument (rdtim destroys it) jsr rdtim clc adc _tmp ; add current time to get target sta _target * jsr rdtim cmp _target bmi - ; Buzz until target reached rts .scend .checkpc $A000 .data .checkpc $D000
<filename>hello6.oph</filename> .include "c64-1.oph" .outfile "hello.prg" .data .org $C000 .space cache 2 .text .macro print lda #<_1 ldx #>_1 jsr printstr .macend .macro greet lda #30 jsr delay `print hello1 `print _1 `print hello2 .macend ; Save the zero page locations that PRINTSTR uses. lda $10 sta cache lda $11 sta cache+1 lda #147 jsr chrout `greet target1 `greet target2 `greet target3 `greet target4 `greet target5 `greet target6 `greet target7 `greet target8 `greet target9 `greet target10 ; Restore the zero page values printstr uses. lda cache sta $10 lda cache+1 sta $11 rts hello1: .byte "HELLO, ",0 hello2: .byte "!", 13, 0 target1: .byte "PROGRAMMER", 0 target2: .byte "ROOM", 0 target3: .byte "BUILDING", 0 target4: .byte "NEIGHBORHOOD", 0 target5: .byte "CITY", 0 target6: .byte "NATION", 0 target7: .byte "WORLD", 0 target8: .byte "SOLAR SYSTEM", 0 target9: .byte "GALAXY", 0 target10: .byte "UNIVERSE", 0 ; DELAY routine. Takes values from the Accumulator and pauses ; for that many jiffies (1/60th of a second). .scope .data .space _tmp 1 .space _target 1 .text delay: sta _tmp ; save argument (rdtim destroys it) jsr rdtim clc adc _tmp ; add current time to get target sta _target * jsr rdtim cmp _target bmi - ; Buzz until target reached rts .scend ; PRINTSTR routine. Accumulator stores the low byte of the address, ; X register stores the high byte. Destroys the values of $10 and ; $11. .scope printstr: sta $10 stx $11 ldy #$00 _lp: lda ($10),y beq _done jsr chrout iny bne _lp _done: rts .scend .checkpc $A000 .data .checkpc $D000
<filename>hello7.oph</filename> .include "../platform/c64_0.oph" .require "../platform/c64kernal.oph" .outfile "hello.prg" .data .org $C000 .text .macro print lda #<_1 ldx #>_1 jsr printstr .macend .macro greet lda #30 jsr delay `print hello1 `print _1 `print hello2 .macend lda #147 jsr chrout `greet target1 `greet target2 `greet target3 `greet target4 `greet target5 `greet target6 `greet target7 `greet target8 `greet target9 `greet target10 rts hello1: .byte "HELLO, ",0 hello2: .byte "!", 13, 0 target1: .byte "PROGRAMMER", 0 target2: .byte "ROOM", 0 target3: .byte "BUILDING", 0 target4: .byte "NEIGHBORHOOD", 0 target5: .byte "CITY", 0 target6: .byte "NATION", 0 target7: .byte "WORLD", 0 target8: .byte "SOLAR SYSTEM", 0 target9: .byte "GALAXY", 0 target10: .byte "UNIVERSE", 0 ; DELAY routine. Takes values from the Accumulator and pauses ; for that many jiffies (1/60th of a second). .scope .data .space _tmp 1 .space _target 1 .text delay: sta _tmp ; save argument (rdtim destroys it) jsr rdtim clc adc _tmp ; add current time to get target sta _target * jsr rdtim cmp _target bmi - ; Buzz until target reached rts .scend ; PRINTSTR routine. Accumulator stores the low byte of the address, ; X register stores the high byte. Destroys the values of $10 and ; $11. .scope .data zp .space _ptr 2 .text printstr: sta _ptr stx _ptr+1 ldy #$00 _lp: lda (_ptr),y beq _done jsr chrout iny bne _lp _done: rts .scend .checkpc $A000 .data .checkpc $D000 .data zp .checkpc $80
<filename>structuredemo.oph</filename> .include "../platform/c64_0.oph" .require "../platform/c64kernal.oph" .outfile "structuredemo.prg" jsr print'unsorted jsr insertion'sort jsr print'list rts ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Linked list data: head, next, lb, hb. ; lb/hb: Low/high bytes of the data array. These are immutable and ; kept with the program text. ; head: Array index of the first element in the list, or #$FF if the ; list is empty ; next: Array of successor indices. If you've just read element X, ; the value of memory location next+X is the index of the ; next element. If next is #$FF, you've reached the end of ; the list. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; .data .org $C000 .space head 1 .space next 16 .text lb: .byte <$838,<$618,<$205,<$984,<$724,<$301,<$249,<$946 .byte <$925,<$043,<$114,<$697,<$985,<$633,<$312,<$086 hb: .byte >$838,>$618,>$205,>$984,>$724,>$301,>$249,>$946 .byte >$925,>$043,>$114,>$697,>$985,>$633,>$312,>$086 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; insertion'sort: Sorts the list defined by head, next, hb, lb. ; Arguments: None. ; Modifies: All registers destroyed, head and next array sorted. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; insertion'sort: lda #$FF ; Clear list by storing the terminator in 'head' sta head ldx #$0 ; Loop through the lb/hb array, adding each insertion'sort'loop: ; element one at a time txa pha jsr insert_elt pla tax inx cpx #$10 bne insertion'sort'loop rts ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; insert_elt: Insert an element into the linked list. Maintains the ; list in sorted, ascending order. Used by ; insertion'sort. ; Arguments: X register holds the index of the element to add. ; Modifies: All registers destroyed; head and next arrays updated ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; .data .space lbtoinsert 1 .space hbtoinsert 1 .space indextoinsert 1 .text insert_elt: ldy head ; If the list is empty, make cpy #$FF ; head point at it, and return. bne insert_elt'list'not'empty stx head tya sta next,x rts insert_elt'list'not'empty: lda lb,x ; Cache the data we're inserting sta lbtoinsert lda hb,x sta hbtoinsert stx indextoinsert ldy head ; Compare the first value with sec ; the data. If the data must lda lb,y ; be inserted at the front... sbc lbtoinsert lda hb,y sbc hbtoinsert bmi insert_elt'not'smallest tya ; Set its next pointer to the sta next,x ; old head, update the head stx head ; pointer, and return. rts insert_elt'not'smallest: ldx head insert_elt'loop: ; At this point, we know that lda next,x ; argument > data[X]. tay cpy #$FF ; if next[X] = #$FF, insert arg at end. beq insert_elt'insert'after'current lda lb,y ; Otherwise, compare arg to sec ; data[next[X]]. If we insert sbc lbtoinsert ; before that... lda hb,y sbc hbtoinsert bmi insert_elt'goto'next insert_elt'insert'after'current: ; Fix up all the next links tya ldy indextoinsert sta next,y tya sta next,x rts ; and return. insert_elt'goto'next: ; Otherwise, let X = next[X] tya ; and go looping again. tax jmp insert_elt'loop ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; print'unsorted: Steps through the data array and prints each value. ; Standalone procedure. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; print'unsorted: lda #<unsorted'hdr ldx #>unsorted'hdr jsr put'string ldy #$00 print'unsorted'loop: lda hb, Y jsr print'hex lda lb, y jsr print'hex lda #$20 jsr chrout iny cpy #$10 bne print'unsorted'loop lda #$0D jsr chrout rts ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; print'list: Starts at head, and prints out every value in the ; linked list. ; Standalone procedure. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; print'list: lda #<sorted'hdr ldx #>sorted'hdr jsr put'string ldy head print'list'loop: cpy #$FF beq print'list'done lda hb, y jsr print'hex lda lb, y jsr print'hex lda #$20 jsr chrout lda next, Y tay jmp print'list'loop print'list'done: lda #$0d jsr chrout rts ;; String data for the above routines. unsorted'hdr: .byte 147 ; Clear screen first! .byte "UNSORTED DATA:",13,0 sorted'hdr: .byte "SORTED DATA:",13,0 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; print'hex: outputs a two-character hex representation of a one- ; byte value. ; Arguments: Byte to print in accumulator ; Modifies: .A and .X ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; print'hex: pha clc lsr lsr lsr lsr tax lda hexstr,x jsr chrout pla and #$0F tax lda hexstr,X jsr chrout rts ; Character data array for print'hex. hexstr: .byte "0123456789ABCDEF" ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; put'string: outputs a C-style null terminated string with length ; less than 256 to the screen. If 256 bytes are written ; without finding a terminator, the routine ends quietly. ; Arguments: Low byte of string address in .A, high byte in .X ; Modifies: .A and .Y ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; .data zp .space put'string'addr 2 .text put'string: sta put'string'addr stx put'string'addr+1 ldy #$00 put'string'loop: lda (put'string'addr),y beq put'string'done jsr chrout iny bne put'string'loop put'string'done: rts
<filename>fibonacci.oph</filename> .include "../platform/c64_0.oph" .require "../platform/c64kernal.oph" .outfile "fibonacci.prg" lda #<opening ; Print opening text sta fun'args lda #>opening sta fun'args+1 jsr print'string lda #$00 sta fun'vars ; Count num from 0 to 19 * lda fun'vars ; Main loop: print num, with leading space if <10 cmp #$09 bcs + lda #$20 jsr chrout lda fun'vars * sta fun'args ; Copy num to args, print it, plus ": " inc fun'args lda #$00 sta fun'args+1 jsr print'dec lda #$3A jsr chrout lda #$20 jsr chrout lda fun'vars ; Copy num to args, call fib, print result sta fun'args jsr fib jsr print'dec lda #$0D ; Newline jsr chrout inc fun'vars ; Increment num; if it's 20, we're done. lda fun'vars cmp #20 bne -- ; Otherwise, loop. rts opening: .byte 147, " FIBONACCI SEQUENCE",13,13,0 .scope ; Uint16 fib (Uint8 x): compute Xth fibonnaci number. ; fib(0) = fib(1) = 1. ; Stack usage: 3. fib: lda #$03 jsr save'stack lda fun'vars ; If x < 2, goto _base. cmp #$02 bcc _base dec fun'args ; Otherwise, call fib(x-1)... jsr fib lda fun'args ; Copy the result to local variable... sta fun'vars+1 lda fun'args+1 sta fun'vars+2 lda fun'vars ; Call fib(x-2)... sec sbc #$02 sta fun'args jsr fib clc ; And add the old result to it, leaving it lda fun'args ; in the 'result' location. adc fun'vars+1 sta fun'args lda fun'args+1 adc fun'vars+2 sta fun'args+1 jmp _done ; and then we're done. _base: ldy #$01 ; In the base case, just copy 1 to the sty fun'args ; result. dey sty fun'args+1 _done: lda #$03 jsr restore'stack rts .scend .scope ; Stack routines: init'stack, save'stack, restore'stack .data zp .space _sp $02 .space _counter $01 .space fun'args $10 .space fun'vars $40 .text init'stack: lda #$00 sta _sp lda #$A0 sta _sp+1 rts save'stack: sta _counter sec lda _sp sbc _counter sta _sp lda _sp+1 sbc #$00 sta _sp+1 ldy #$00 * lda fun'vars, y sta (_sp), y lda fun'args, y sta fun'vars, y iny dec _counter bne - rts restore'stack: pha sta _counter ldy #$00 * lda (_sp), y sta fun'vars, y iny dec _counter bne - pla clc adc _sp sta _sp lda _sp+1 adc #$00 sta _sp+1 rts .scend ; Utility functions. print'dec prints an unsigned 16-bit integer. ; It's ugly and long, mainly because we don't bother with niceties ; like "division". print'string prints a zero-terminated string. .scope .data .org fun'args .space _val 2 .space _step 2 .space _res 1 .space _allowzero 1 .text print'dec: lda #$00 sta _allowzero lda #<10000 sta _step lda #>10000 sta _step+1 jsr repsub'16 lda #<1000 sta _step lda #>1000 sta _step+1 jsr repsub'16 lda #0 sta _step+1 lda #100 sta _step jsr repsub'16 lda #10 sta _step jsr repsub'16 lda _val jsr _print rts repsub'16: lda #$00 sta _res * lda _val sec sbc _step lda _val+1 sbc _step+1 bcc _done lda _val sec sbc _step sta _val lda _val+1 sbc _step+1 sta _val+1 inc _res jmp - _done: lda _res ora _allowzero beq _ret sta _allowzero lda _res _print: clc adc #'0 jsr chrout _ret: rts .scend print'string: ldy #$00 * lda (fun'args), y beq + jsr chrout iny jmp - * rts