(local lume (require "lume")) (local {: program : dat-parser : new-block : parse-dats : lo : hi} (require "asm")) (local {: stream : kvstream : one} (require "stream")) (local prg (program)) (local code1 (prg:org 0xc00)) (local tiles (prg:org 0x6100)) (fn dat-parser.vm [bytecodes] (local block (new-block)) (each [_ bytecode (ipairs (lume.slice bytecodes 2))] (if (= (type bytecode) :number) (parse-dats block [[:ref :lit] [:dw bytecode]]) (= (type bytecode) :string) (parse-dats block [[:ref bytecode]]) (= (type bytecode) :table) (parse-dats block bytecode) (error (.. "VM can't parse " (fv bytecode))))) block) (local mon { :hexout :0xfdda :putchar :0xfded :bell :0xff3a }) (local vm { :IP :0x60 :IPH :0x61 :W :0x62 :WH :0x63 :ROFF :0x64 :TOP :0x80 :TOPH :0x81 :ST1 :0x7e :ST1H :0x7f :ST2 :0x7c :ST2H :0x7d :RSTACK :0x6000 :ret (fn [self] [:jmp :next]) :push (fn [self v] (local l (bit.band v 0xff)) (local h (bit.band (bit.rshift v 8) 0xff)) [:block [:inx] [:inx] [:lda l] [:sta self.TOP :x] [:lda h] [:sta self.TOPH :x] ]) :drop (fn [self] [:block [:dex] [:dex]]) :def (fn [self name ...] (code1:append name (table.unpack (lume.concat [...] [(self:ret)])))) :word (fn [self name ...] (code1:append name [:jsr :subroutine] [:vm ...] [:vm :ret])) :inline (fn [self ...] [:block [:jsr :subroutine] [:vm ...] [:vm :restore]]) }) (fn inc16 [l h] [:block [:inc l] [:bne :done] [:inc h] :done ]) (fn add16 [l h] [:block [:clc] [:adc l] [:sta l] [:bcc :go] [:inc h] :go ]) (fn achar [c] (bit.bor (string.byte c) 0x80)) (fn astr [s] (-> [(string.byte s 1 -1)] (lume.map #(bit.bor $1 0x80)) (-> (table.unpack) (string.char)))) (code1:append :next [:ldy 0] [:lda [vm.IP] :y] [:sta vm.W] (inc16 vm.IP vm.IPH) [:lda [vm.IP] :y] [:sta vm.WH] (inc16 vm.IP vm.IPH) [:jmp [vm.W]]) (code1:append :reset [:lda #(lo ($1:lookup-addr :quit))] [:sta vm.IP] [:lda #(hi ($1:lookup-addr :quit))] [:sta vm.IPH] [:lda 0] [:sta vm.ROFF] [:ldx 0xfe] [:rts]) (vm:def :subroutine ; usage: [jsr :subroutine] followed by bytecode [:ldy vm.ROFF] [:lda vm.IP] [:sta vm.RSTACK :y] [:iny] [:lda vm.IPH] [:sta vm.RSTACK :y] [:iny] [:sty vm.ROFF] :interpret ; usage: [jsr :interpret] followed by bytecode [:pla] [:sta vm.IP] [:pla] [:sta vm.IPH] (inc16 vm.IP vm.IPH)) (vm:def :ret [:ldy vm.ROFF] [:dey] [:lda vm.RSTACK :y] [:sta vm.IPH] [:dey] [:lda vm.RSTACK :y] [:sta vm.IP] [:sty vm.ROFF]) (code1:append :native [:jmp [vm.IP]]) (code1:append :quit [:rts]) (code1:append :restore [:lda vm.IP] [:sta vm.W] [:lda vm.IPH] [:sta vm.WH] [:ldy vm.ROFF] [:dey] [:lda vm.RSTACK :y] [:sta vm.IPH] [:dey] [:lda vm.RSTACK :y] [:sta vm.IP] [:sty vm.ROFF] [:jmp [vm.W]]) (vm:def :mixed-hires [:sta :0xc050] [:sta :0xc057] [:sta :0xc053]) (vm:def :drop (vm:drop)) (vm:def :dup [:inx] [:inx] [:lda vm.ST1H :x] [:sta vm.TOPH :x] [:lda vm.ST1 :x] [:sta vm.TOP :x]) (vm:def :swap [:lda vm.TOP :x] [:ldy vm.ST1 :x] [:sty vm.TOP :x] [:sta vm.ST1 :x] [:lda vm.TOPH :x] [:ldy vm.ST1H :x] [:sty vm.TOPH :x] [:sta vm.ST1H :x]) (vm:def :over [:inx] [:inx] [:lda vm.ST2H :x] [:sta vm.TOPH :x] [:lda vm.ST2 :x] [:sta vm.TOP :x]) (vm:word :2dup :over :over) (vm:def :>rot ; (a b c -- c a b) [:lda vm.TOP :x] ; a: c (a b c) [:ldy vm.ST2 :x] ; y: a (a b c) [:sta vm.ST2 :x] ; a: c (c b c) [:lda vm.ST1 :x] ; a: b (c b c) [:sta vm.TOP :x] ; a: b (c b b) [:sty vm.ST1 :x] ; y: a (c a b) [:lda vm.TOPH :x] ; a: c (a b c) [:ldy vm.ST2H :x] ; y: a (a b c) [:sta vm.ST2H :x] ; a: c (c b c) [:lda vm.ST1H :x] ; a: b (c b c) [:sta vm.TOPH :x] ; a: b (c b b) [:sty vm.ST1H :x] ; y: a (c a b) ) (vm:def :screen ; yx -- p [:lda vm.TOPH :x] ; a=y [:lsr :a] [:lsr :a] ; a=y/4 [:tay] ; y=y/4 [:lda 0x03] [:and vm.TOPH :x] ; a=y%4 [:ora 0x20] ; a=0x20 + y%4 [:sta vm.TOPH :x] ; high byte is set (and y is wiped) [:lda vm.TOP :x] ; a=x [:asl :a] ; a = x*2 [:clc] [:adc :screeny-lookup :y] ; a=x*2 + (y/4)*0x28 [:sta vm.TOP :x] ; low byte is set ) ; note: the graphical tile data must not cross a page boundary! ; TODO: add support to the assembler for enforcing that (fn draw-block [] [:block [:clc] [:ldy 8] :loop [:lda [vm.TOP :x]] [:sta [vm.ST1 :x]] [:inc vm.TOP :x] [:lda vm.ST1H :x] [:adc 4] [:sta vm.ST1H :x] [:dey] [:bne :loop]]) (fn draw-vertical-block [] [:block (draw-block) [:lda vm.ST1H :x] [:sbc 31] ; with carry clear this is 32 [:sta vm.ST1H :x] [:lda vm.ST1 :x] [:ora 0x80] [:sta vm.ST1 :x] (draw-block)]) (vm:def :drawtile ; p gfx -- (draw-vertical-block) [:lda vm.ST1H :x] [:sbc 31] [:sta vm.ST1H :x] [:lda vm.ST1 :x] [:sbc 0x7f] [:sta vm.ST1 :x] (draw-vertical-block) (vm:drop) (vm:drop) ) (vm:def :cleargfx (vm:push 0x4000) [:block :page [:dec vm.TOPH :x] [:lda 0] [:block :start [:sta [vm.TOP :x]] [:inc vm.TOP :x] [:bne :start]] [:lda vm.TOPH :x] [:cmp 0x20] [:bne :page]] (vm:drop)) (tiles:append :blanktile [:bytes "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"]) (tiles:append :testtile [:bytes "12345678901234567890123456789012"]) ;; 19x11 means full map is 209 bytes (code1:append :main [:jsr :reset] [:jsr :interpret] [:vm :mixed-hires :cleargfx 0x0000 :tile>screen :lit :testtile :drawtile 0x0200 :tile>screen :lit :testtile :drawtile 0x0002 :tile>screen :lit :testtile :drawtile 0x0202 :tile>screen :lit :testtile :drawtile 0x0606 :tile>screen :lit :testtile :drawtile 0x0913 :tile>screen :lit :testtile :drawtile :quit]) (prg:assemble) prg