250 lines
8.3 KiB
Plaintext
250 lines
8.3 KiB
Plaintext
|
(local {: stream : kvstream : one} (require "stream"))
|
||
|
|
||
|
(local opcodes {})
|
||
|
|
||
|
; op mode arg
|
||
|
; single-byte ops
|
||
|
(let [ops
|
||
|
{:php 0x08 :plp 0x28 :pha 0x48 :pla 0x68 :dey 0x88 :tay 0xa8 :iny 0xc8 :inx 0xe8
|
||
|
:clc 0x18 :sec 0x38 :cli 0x58 :sei 0x78 :tya 0x98 :clv 0xb8 :cld 0xd8 :sed 0xf8
|
||
|
:txa 0x8a :txs 0x9a :tax 0xaa :tsx 0xba :dex 0xca :nop 0xea :brk 0x00 :rti 0x40
|
||
|
:rts 0x60}]
|
||
|
(each [opcode byte (pairs ops)]
|
||
|
(tset opcodes opcode (fn [mode] (if mode nil byte)))))
|
||
|
|
||
|
; branch ops
|
||
|
(let [ops {:bpl 0x10 :bmi 0x30 :bvc 0x50 :bvs 0x70 :bcc 0x90 :bcs 0xb0 :bne 0xd0 :beq 0xf0}]
|
||
|
(each [opcode byte (pairs ops)]
|
||
|
(tset opcodes opcode (fn [mode] (if (= mode :rel) byte nil)))))
|
||
|
(set opcodes.jsr (fn [mode] (if (= mode :abs) 0x20 nil)))
|
||
|
|
||
|
; aaabbbcc ops
|
||
|
(fn aaabbbcc [aaa cc modemap]
|
||
|
(local base (bit.bor cc (bit.lshift aaa 5)))
|
||
|
(fn [mode]
|
||
|
(local bbb (. modemap mode))
|
||
|
(if bbb (bit.bor base (bit.lshift bbb 2)) nil)))
|
||
|
|
||
|
(fn indexed-modes [...]
|
||
|
(let [modemap {}]
|
||
|
(each [index mode (pairs [...])]
|
||
|
(tset modemap mode (- index 1)))
|
||
|
modemap))
|
||
|
|
||
|
(fn without-modes [modemap ...]
|
||
|
(let [newmodemap (table.clone modemap)]
|
||
|
(each [_ mode (pairs [...])]
|
||
|
(tset newmodemap mode nil))
|
||
|
newmodemap))
|
||
|
|
||
|
(fn only-modes [modemap ...]
|
||
|
(let [newmodemap {}]
|
||
|
(each [_ mode (pairs [...])]
|
||
|
(tset newmodemap mode (. modemap mode)))
|
||
|
newmodemap))
|
||
|
|
||
|
; cc=1 ops
|
||
|
(let [cc1-modes (indexed-modes :zp-x* :zp :imm :abs :zp*-y :zp-x :abs-y :abs-x)
|
||
|
ops {:ora 0 :and 1 :eor 2 :adc 3 :lda 5 :cmp 6 :sbc 7}]
|
||
|
(each [opcode aaa (pairs ops)]
|
||
|
(tset opcodes opcode (aaabbbcc aaa 1 cc1-modes))
|
||
|
(tset opcodes :sta (aaabbbcc 4 1 (without-modes cc1-modes :imm)))))
|
||
|
|
||
|
; cc=2 ops
|
||
|
(let [cc2-modes (indexed-modes nil :zp :a :abs nil :zp-x nil :abs-x)]
|
||
|
(each [opcode aaa (pairs {:asl 0 :rol 1 :lsr 2 :ror 3})]
|
||
|
(tset opcodes opcode (aaabbbcc aaa 2 cc2-modes))
|
||
|
(each [opcode aaa (pairs {:dec 6 :inc 7})]
|
||
|
(tset opcodes opcode (aaabbbcc aaa 2 (without-modes cc2-modes :a))))))
|
||
|
(tset opcodes :stx (aaabbbcc 4 2 (indexed-modes nil :zp nil :abs nil nil :zp-y)))
|
||
|
(tset opcodes :ldx (aaabbbcc 5 2 (indexed-modes :imm :zp nil :abs nil nil :zp-y nil :abs-y)))
|
||
|
|
||
|
; cc=0 ops
|
||
|
(let [cc0-modes (indexed-modes :imm :zp nil :abs nil :zp-x nil :abs-x)]
|
||
|
(tset opcodes :bit (aaabbbcc 1 0 (only-modes cc0-modes :zp :abs)))
|
||
|
(tset opcodes :sty (aaabbbcc 4 0 (only-modes cc0-modes :zp :abs :zp-x)))
|
||
|
(tset opcodes :ldy (aaabbbcc 5 0 cc0-modes))
|
||
|
(each [opcode aaa (pairs {:cpy 6 :cpx 7})]
|
||
|
(tset opcodes opcode (aaabbbcc aaa 0 (only-modes cc0-modes :imm :zp :abs)))))
|
||
|
(tset opcodes :jmp (fn [mode] (match mode :abs 0x4c :abs* 0x6c _ nil)))
|
||
|
|
||
|
(fn size [mode]
|
||
|
(if
|
||
|
(or (= mode nil) (= mode :a)) 1
|
||
|
(= (mode:sub 1 3) :abs) 3
|
||
|
2))
|
||
|
(fn opsize [op] (if (= op.opcode :block) 0 (size op.mode)))
|
||
|
|
||
|
(fn parse-mode-arg [op]
|
||
|
(match op
|
||
|
[_ :a] [:a nil]
|
||
|
([_ imm] ? (= (type imm) "number")) [:imm imm]
|
||
|
([op addr] ? (and (= (type addr) "string") (= (op:sub 1 1) "b"))) [:rel addr] ; branch
|
||
|
([_ addr] ? (= (type addr) "string")) [:addr addr]
|
||
|
[_ addr :x] [:addr-x addr]
|
||
|
[_ addr :y] [:addr-y addr]
|
||
|
[_ [addr] :y] [:zp*-y addr]
|
||
|
[_ [addr :x]] [:zp-x* addr]
|
||
|
[_ [addr]] [:abs* addr]
|
||
|
[_] [nil nil]
|
||
|
_ (error (.. "Unrecognized syntax" (fv op)))))
|
||
|
|
||
|
(fn parse-ops [block ops]
|
||
|
(var index 1)
|
||
|
(each [_ op (ipairs ops)]
|
||
|
(if (= (type op) "string")
|
||
|
(tset block.symbols op index)
|
||
|
(let [opcode (. op 1)]
|
||
|
(if
|
||
|
(. opcodes opcode)
|
||
|
(let [[mode arg] (parse-mode-arg op)]
|
||
|
(table.insert block.code {: opcode : mode : arg}))
|
||
|
|
||
|
(= opcode :block)
|
||
|
(let [ops (table.clone op)]
|
||
|
(table.remove ops 1)
|
||
|
(table.insert block.code (parse-ops {: opcode :code [] :symbols {}} ops)))
|
||
|
|
||
|
(error (.. "Unrecognized opcode " (fv opcode))))
|
||
|
(set index (+ index 1)))))
|
||
|
block)
|
||
|
|
||
|
(fn block [org ...] (parse-ops {:type :code : org :code [] :symbols {}} [...]))
|
||
|
(fn db [org init] {:type :var : org : init :size 1})
|
||
|
(fn dw [org init] {:type :var : org : init :size 2})
|
||
|
(fn allot [org size] {:type :var : org : size})
|
||
|
|
||
|
(fn make-env [blocks]
|
||
|
{: blocks
|
||
|
:push (fn [self block]
|
||
|
(make-env
|
||
|
(-> (stream self.blocks)
|
||
|
(: :concat (one block))
|
||
|
(: :tolist))))
|
||
|
:lookup
|
||
|
(fn [self name]
|
||
|
(-> (stream self.blocks)
|
||
|
(: :reverse)
|
||
|
(: :map
|
||
|
(fn [block]
|
||
|
(let [symbol (. block.symbols name)]
|
||
|
(match (type symbol)
|
||
|
"number" (. block.code symbol)
|
||
|
"table" symbol
|
||
|
_ nil))))
|
||
|
(: :filter (fn [symbol] symbol))
|
||
|
(: :first)))
|
||
|
:lookup-block
|
||
|
(fn [self name]
|
||
|
(if
|
||
|
(-> (stream self.blocks)
|
||
|
(: :skip 1)
|
||
|
(: :filter (fn [block] (. block.symbols name)))
|
||
|
(: :next))
|
||
|
(. self.blocks 2)
|
||
|
(. (. self.blocks 1) name)))
|
||
|
:is-zp? (fn [self name] (< (. (self:lookup-block name) :org) 0x100))})
|
||
|
|
||
|
(fn op-stream [env block]
|
||
|
(-> (stream block.code)
|
||
|
(: :map
|
||
|
(fn [op]
|
||
|
(if (= op.opcode :block)
|
||
|
(: (one op env)
|
||
|
:concat (op-stream (env:push op) op))
|
||
|
(one op env))))
|
||
|
(: :flatten)))
|
||
|
|
||
|
(fn int8-to-bytes [i]
|
||
|
(string.char (bit.band i 0xff)))
|
||
|
(fn int16-to-bytes [i]
|
||
|
(string.char (bit.band i 0xff) (bit.band (bit.rshift i 8) 0xff)))
|
||
|
|
||
|
(fn program []
|
||
|
{:symbols {}
|
||
|
:start-symbol :main
|
||
|
:add (fn [self name elem] (tset self.symbols name elem))
|
||
|
:block (fn [self name org ...] (self:add name (block org ...)))
|
||
|
:db (fn [self name org init] (self:add name (db org init)))
|
||
|
:dw (fn [self name org init] (self:add name (dw org init)))
|
||
|
:allot (fn [self name org size] (self:add name (allot org size)))
|
||
|
:op-stream
|
||
|
(fn [self block] (op-stream (make-env [self block]) block))
|
||
|
:patch-addr-modes
|
||
|
(fn [self]
|
||
|
(each [name block (pairs self.symbols)]
|
||
|
(each [op env (: (self:op-stream block) :iter)]
|
||
|
(when (and op.mode (= (op.mode:sub 1 4) :addr))
|
||
|
(let [zp-mode (.. :zp (op.mode:sub 5))
|
||
|
abs-mode (.. :abs (op.mode:sub 5))
|
||
|
is-zp (and ((. opcodes op.opcode) zp-mode) (env:is-zp? op.arg))]
|
||
|
(set op.mode (if is-zp zp-mode abs-mode)))))))
|
||
|
:allocate
|
||
|
(fn [self org size org-addr]
|
||
|
(var addr (. org-addr org))
|
||
|
(when (= nil addr)
|
||
|
(tset org-addr org org)
|
||
|
(set addr org))
|
||
|
(tset org-addr org (+ addr size))
|
||
|
addr)
|
||
|
:allocate-var-block
|
||
|
(fn [self block org-addr]
|
||
|
(set block.addr (self:allocate block.org block.size org-addr)))
|
||
|
:allocate-code-block
|
||
|
(fn [self block org-addr]
|
||
|
(set block.addr (self:allocate block.org 0 org-addr))
|
||
|
(each [op env (: (self:op-stream block) :iter)]
|
||
|
(set op.addr (self:allocate block.org (opsize op) org-addr))))
|
||
|
:allocate-addresses
|
||
|
(fn [self]
|
||
|
(let [org-addr {}]
|
||
|
(each [name block (pairs self.symbols)]
|
||
|
(match block.type
|
||
|
:var (self:allocate-var-block block org-addr)
|
||
|
:code (self:allocate-code-block block org-addr)))))
|
||
|
:generate-var-block
|
||
|
(fn [self block]
|
||
|
(match block.size
|
||
|
1 (int8-to-bytes (or block.init 0))
|
||
|
2 (int16-to-bytes (or block.init 0))
|
||
|
n (string.rep "\0" n)))
|
||
|
:lookup-addr
|
||
|
(fn [self sym env]
|
||
|
(local op (env:lookup sym))
|
||
|
(if op
|
||
|
op.addr
|
||
|
(tonumber sym)))
|
||
|
:generate-op-arg
|
||
|
(fn [self op env]
|
||
|
(if
|
||
|
(= op.mode :imm) (int8-to-bytes op.arg)
|
||
|
(= op.mode :rel)
|
||
|
(int8-to-bytes (- (self:lookup-addr op.arg env) (+ op.addr 2)))
|
||
|
(= (size op.mode) 2) (int8-to-bytes (self:lookup-addr op.arg env))
|
||
|
(= (size op.mode) 3) (int16-to-bytes (self:lookup-addr op.arg env))
|
||
|
""))
|
||
|
:generate-op
|
||
|
(fn [self op env]
|
||
|
(let [bytegen (. opcodes op.opcode)]
|
||
|
(if bytegen
|
||
|
(.. (int8-to-bytes (bytegen op.mode)) (self:generate-op-arg op env))
|
||
|
"")))
|
||
|
:generate-code-block
|
||
|
(fn [self block]
|
||
|
(var bytes "")
|
||
|
(each [op env (: (self:op-stream block) :iter)]
|
||
|
(set bytes (.. bytes (self:generate-op op env))))
|
||
|
(set block.bytes bytes))
|
||
|
:generate-bytes
|
||
|
(fn [self]
|
||
|
(each [name block (pairs self.symbols)]
|
||
|
(match block.type
|
||
|
:var (self:generate-var-block block)
|
||
|
:code (self:generate-code-block block))))
|
||
|
:assemble
|
||
|
(fn [self]
|
||
|
(self:patch-addr-modes)
|
||
|
(self:allocate-addresses)
|
||
|
(self:generate-bytes))})
|
||
|
|
||
|
program
|