mame / nREPL support

This commit is contained in:
Jeremy Penner 2020-11-08 15:36:38 -05:00
parent b31465b0f9
commit 763e969d6e
10 changed files with 411 additions and 40 deletions

View file

@ -39,3 +39,12 @@
"ctrl+v" "tileedit:paste" "ctrl+v" "tileedit:paste"
}) })
(fn inline-eval [eval]
(let [ldoc core.active_view.doc
(aline acol bline bcol) (ldoc:get_selection)
inject #(ldoc:insert bline bcol (eval $1))]
(if (and (= aline bline) (= acol bcol))
(inject (ldoc:get_text aline 1 aline 10000000))
(inject (ldoc:get_text aline acol bline bcol)))))
{: inline-eval}

89
lib/bencode.lua Normal file
View file

@ -0,0 +1,89 @@
-- Based on bencode.lua from the jeejah project by Phil Hagelberg
-- Distributed under the MIT license
-- https://gitlab.com/technomancy/jeejah/
local encode, decode
local function decode_list(str, t, total_len)
-- print("list", str, lume.serialize(t))
if #str == 0 then error("Incomplete") end
if(str:sub(1,1) == "e") then return t, total_len + 1 end
local value, v_len = decode(str)
table.insert(t, value)
total_len = total_len + v_len
return decode_list(str:sub(v_len + 1), t, total_len)
end
local function decode_table(str, t, total_len)
-- print("table", str, lume.serialize(t))
if #str == 0 then error("Incomplete") end
if(str:sub(1,1) == "e") then return t, total_len + 1 end
local key, k_len = decode(str)
local value, v_len = decode(str:sub(k_len+1))
local end_pos = 1 + k_len + v_len
t[key] = value
total_len = total_len + k_len + v_len
return decode_table(str:sub(end_pos), t, total_len)
end
function decode(str)
-- print("decoding", str)
if #str == 0 then
error("Incomplete")
elseif(str:sub(1,1) == "l") then
return decode_list(str:sub(2), {}, 1)
elseif(str:sub(1,1) == "d") then
return decode_table(str:sub(2), {}, 1)
elseif(str:sub(1,1) == "i") then
local iend = str:find("e")
if iend == nil then error("Incomplete") end
return(tonumber(str:sub(2, iend - 1))), iend
elseif(str:match("[0-9]+:")) then
local num_str = str:match("[0-9]+")
local beginning_of_string = #num_str + 2
local str_len = tonumber(num_str)
local total_len = beginning_of_string + str_len - 1
if #str < total_len then error("Incomplete") end
return str:sub(beginning_of_string, total_len), total_len
else
error("Could not parse "..str)
end
end
local function encode_str(s) return #s .. ":" .. s end
local function encode_int(n) return "i" .. tostring(n) .. "e" end
local function encode_table(t)
local s = "d"
for k,v in pairs(t) do s = s .. encode(k) .. encode(v) end
return s .. "e"
end
local function encode_list(l)
local s = "l"
for _,x in ipairs(l) do s = s .. encode(x) end
return s .. "e"
end
local function count(tbl)
local i = 0
for _ in pairs(tbl) do i = i + 1 end
return i
end
function encode(x)
local unpack = unpack or table.unpack
if(type(x) == "table" and select("#", unpack(x)) == count(x)) then
return encode_list(x)
elseif(type(x) == "table") then
return encode_table(x)
elseif(type(x) == "number" and math.floor(x) == x) then
return encode_int(x)
elseif(type(x) == "string") then
return encode_str(x)
else
error("Could not encode " .. type(x) .. ": " .. tostring(x))
end
end
return {decode=decode, encode=encode}

View file

@ -1,5 +1,6 @@
(local lume (require :lib.lume)) (local lume (require :lib.lume))
(local json (require :lib.dkjson)) (local json (require :lib.dkjson))
(local core (require :core))
(fn string.fromhex [str] (fn string.fromhex [str]
(str:gsub ".." (fn [cc] (string.char (tonumber cc 16))))) (str:gsub ".." (fn [cc] (string.char (tonumber cc 16)))))
@ -48,5 +49,17 @@
(f:write (json.encode value)) (f:write (json.encode value))
(f:close)) (f:close))
{: lo : hi : int8-to-bytes : int16-to-bytes : reload : hotswap : swappable :require swappable-require : readjson : writejson} (fn waitfor [pred]
(local coro (coroutine.running))
(core.add_thread
(fn []
(while (not (pred))
(coroutine.yield))
(coroutine.resume coro))
coro)
(coroutine.yield))
(fn in-coro [f ...] (-> (coroutine.create f) (coroutine.resume ...)))
{: lo : hi : int8-to-bytes : int16-to-bytes : reload : hotswap : swappable :require swappable-require : readjson : writejson : waitfor : in-coro}

43
link/command.fnl Normal file
View file

@ -0,0 +1,43 @@
(local util (require :lib.util))
(local link (require :link))
(local editor (require :editor))
(local command (require :core.command))
(local keymap (require :core.keymap))
(each [_ linktype (ipairs link.types)]
(command.add #(not= link.name linktype) {
(.. "link:switch-to-" linktype) #(link:switch linktype)
}))
(command.add #(and link.machine.run (not (link.machine:running?))) {
"link:boot" #(link.machine:run)
})
(command.add #(and link.machine.die (link.machine:running?)) {
"link:kill" #(link.machine:die)
})
(command.add #(not (link.machine:connected?)) {
"link:connect" #(link.machine:connect)
})
(command.add #(link.machine:connected?) {
"link:disconnect" #(link.machine:disconnect)
})
(let [connected-methods
{:reboot :reboot
:continue :continue
:step :step
:squelch :stop}]
(each [name method (pairs connected-methods)]
(command.add #(and (link.machine:connected?) (. link.machine method)) {
(.. "link:" name) #(: link.machine method)
})))
(command.add #(and (link.machine:connected?) link.machine.coro-eval) {
"link:eval" #(util.in-coro #(editor.inline-eval #(link.machine:coro-eval $1)))
})
(keymap.add {
"alt+m" "link:eval"
})
{}

View file

@ -28,6 +28,11 @@
(fn [self] (fn [self]
(when (not self.pid) (when (not self.pid)
(set self.pid (spawn [:nixGL gsplus-path :-debugport (tostring debug-port)])))) (set self.pid (spawn [:nixGL gsplus-path :-debugport (tostring debug-port)]))))
:run
(fn [self]
(self:boot)
(while (not self.socket) (self:connect))
(self:hello))
:die :die
(fn [self] (fn [self]
(self:disconnect) (self:disconnect)
@ -47,6 +52,7 @@
(self.socket:close) (self.socket:close)
(set self.socket nil))) (set self.socket nil)))
:connected? (fn [self] self.socket) :connected? (fn [self] self.socket)
:running? (fn [self] self.pid)
:cmd (fn [self cmd] (self.socket:send (.. cmd "\n"))) :cmd (fn [self cmd] (self.socket:send (.. cmd "\n")))
:response :response
(fn [self] (fn [self]
@ -115,27 +121,12 @@
(org:append :debug-stub [:jmp post-check-jump] :on-hotswap ...)) (org:append :debug-stub [:jmp post-check-jump] :on-hotswap ...))
}) })
(command.add #(not machine.pid) {
"gsplus:launch-gsplus" #(machine:boot)
"gsplus:boot" (fn []
(machine:boot)
(while (not machine.socket) (machine:connect))
(machine:hello)
)
})
(command.add (fn [] machine.pid) {
"gsplus:kill-gsplus" #(machine:die)
})
(command.add (fn [] machine.socket) { (command.add (fn [] machine.socket) {
"gsplus:disconnect" #(machine:disconnect)
"gsplus:hello" #(machine:hello) "gsplus:hello" #(machine:hello)
"gsplus:dump-cpu-state" #(pp (machine:getreg)) "gsplus:dump-cpu-state" #(pp (machine:getreg))
"gsplus:step" #(pp (machine:step)) "gsplus:step" #(pp (machine:step))
"gsplus:continue" #(pp (machine:continue)) "gsplus:continue" #(pp (machine:continue))
}) })
(command.add #(not machine.socket) {
"gsplus:connect" #(machine:connect)
})
machine machine

View file

@ -3,7 +3,7 @@
(fn [self name] (fn [self name]
(set self.machine (require (.. "link." name))) (set self.machine (require (.. "link." name)))
(set self.name name)) (set self.name name))
:types [:serial :gsplus :tape]}) :types [:serial :gsplus :tape :mame]})
(local serial (require :link.serial)) (local serial (require :link.serial))
(link:switch (if (and (pcall #(serial:connect)) (serial:connected?)) :serial :gsplus)) (link:switch (if (and (pcall #(serial:connect)) (serial:connected?)) :serial :gsplus))

64
link/mame.fnl Normal file
View file

@ -0,0 +1,64 @@
(local util (require :lib.util))
(local {: spawn : kill} (util.require :lib.spawn))
(local nrepl (require :link.nrepl))
(local Session (require :link.nrepl-session))
(local bencode (require :lib.bencode))
(local lume (require :lib.lume))
(fn start-mame [platform]
(spawn [:mame :-debug :-plugins :-pluginspath "/home/jeremy/src;/usr/share/mame/plugins" :-plugin :jeejah platform]))
(local Machine (Session:extend))
(fn Machine.new [self]
(Machine.super.new self {:out (fn []) :value (fn [])}))
(fn Machine.boot [self]
(when (not self.pid)
(set self.pid (start-mame :apple2p))))
(fn Machine.run [self]
(self:boot)
(self:connect))
(fn Machine.die [self]
(self:disconnect)
(when self.pid
(kill (- self.pid) 1)
(set self.pid nil)))
(fn Machine.running? [self] self.pid)
(fn Machine.connected? [self] self.session)
(fn Machine.connect [self]
(util.in-coro (fn []
(when (not (nrepl:connected?))
(util.waitfor
#(if (nrepl:connected?) true
(do (pcall #(nrepl:connect)) false))))
(self:init-session))))
(fn Machine.disconnect [self]
(when (nrepl:connected?) (nrepl:disconnect))
(set self.session nil))
(fn Machine.write [self addr bytes]
(self:send
{:op :eval
:code
"(let [bencode (require :bencode)
{: addr : bytes} (bencode.decode (io.read))
mem (-> (manager:machine) (. :devices ::maincpu :spaces :program))]
(print :writing (bytes:len) :to addr)
(for [i 1 (bytes:len)]
(mem:write_u8 (+ addr i -1) (string.byte (bytes:sub i i)))))"}
{:status/need-input #(self:send-oob {:op :stdin :stdin (bencode.encode {: addr : bytes})})}))
(fn Machine.reboot [self] (self:eval "(: (manager:machine) :hard_reset)"))
(fn Machine.coro-eval [self code]
(var result nil)
(self:eval code
(self:coro-handlers (coroutine.running) {:out #(set result $2)}))
(coroutine.yield)
(or result "<no result?>"))
(fn Machine.dbgcmd [self cmd]
(self:eval (.. "(-> (manager:machine) (: :debugger) (: :command \"" cmd "\"))")))
(fn Machine.continue [self] (self:dbgcmd :go))
(fn Machine.step [self] (self:dbgcmd :s))
(fn Machine.stop-at [self addr])
(Machine:new)
Machine

61
link/nrepl-session.fnl Normal file
View file

@ -0,0 +1,61 @@
(local Object (require :core.object))
(local nrepl (require :link.nrepl))
(local lume (require :lib.lume))
(local Session (Object:extend))
(fn Session.new [self ?handlers]
(set self.queue [])
(set self.in-progress false)
(set self.handlers ?handlers))
(fn Session.init-session [self]
(when (nrepl:connected?)
(self:do #(nrepl:new-session
#(do (set self.session $2)
(self:done-msg))
(self:make-handlers)))))
(fn Session.cleanup-handlers [self]
{:status/done #(self:done-msg)
:status/interrupted #(self:done-msg)})
(fn Session.make-handlers [self]
(lume.merge
(or self.handlers {})
(nrepl:chain-handlers [:status/done :status/interrupted]
(or self.handlers {})
(self:cleanup-handlers))))
(fn Session.coro-handlers [self coro ?handlers]
(lume.merge
(or ?handlers {})
(nrepl:chain-handlers [:status/done :status/interrupted]
(self:cleanup-handlers)
{:status/done #(coroutine.resume coro)
:status/interrupted #(coroutine.resume coro)})))
(fn Session.do [self f]
(if self.in-progress (table.insert self.queue f)
(do (set self.in-progress true)
(f))))
(fn Session.done-msg [self]
(if (> (length self.queue) 0) ((table.remove self.queue 1))
(set self.in-progress false)))
(fn Session.send [self message ?handlers]
(self:do #(nrepl:send message ?handlers self.session)))
(fn Session.send-oob [self message ?handlers]
(local handlers
(lume.merge
(nrepl:chain-handlers [:status/done :status/interrupted]
(or self.handlers {}))
(or ?handlers {})))
(nrepl:send message handlers self.session))
(fn Session.eval [self code ?handlers]
(self:send {:op :eval : code} ?handlers))
Session

114
link/nrepl.fnl Normal file
View file

@ -0,0 +1,114 @@
(local core (require :core))
(local socket (require :socket))
(local bencode (require :lib.bencode))
(local lume (require :lib.lume))
(fn contains? [tbl item]
(or (= tbl item) (lume.find tbl item)))
(local nrepl
{:active-requests {}
:session-handlers {}
:default-handlers
{:out #(core.log $2)
:value #(core.log $2)
:ex #(core.err $2)
:status/interrupted #($1:done $3.id)
:status/done #($1:done $3.id)}
:merge-handlers
(fn [self message]
(lume.merge self.default-handlers
(or (. self.session-handlers message.session) {})
(or (. self.active-requests message.id) {})))
:chain-handlers
(fn [self keys ...]
(local new-handlers {})
(each [_ key (ipairs keys)]
(each [_ handlers (ipairs [self.default-handlers ...])]
(local next-handler (. handlers key))
(local prev-handler (. new-handlers key))
(if (and next-handler prev-handler)
(tset new-handlers key
#(do (prev-handler $1 $2 $3) (next-handler $1 $2 $3)))
next-handler
(tset new-handlers key next-handler))))
new-handlers)
:counter 1
:input ""
:parse-input
(fn [self]
(match (pcall #(bencode.decode self.input))
(true val len)
(do (set self.input (self.input:sub (+ len 1)))
val)
(false :Incomplete) nil
(false _)
(do (set self.input "")
nil)))
:receive
(fn [self]
(when self.connection
(local (data err part) (self.connection:receive "*a"))
(local response (or data part))
(when (> (response:len) 0)
(print "Got some data")
(set self.input (.. self.input response)))
(match (self:parse-input) nil nil
input (self:handle input))
(when (= err :closed)
(self:disconnect))))
:send
(fn [self msg ?handlers ?session]
(when self.connection
(when (not msg.id)
(set msg.id self.counter)
(set self.counter (+ self.counter 1)))
(when (not msg.session)
(set msg.session (or ?session self.default-session)))
(when ?handlers
(tset self.active-requests msg.id ?handlers))
(self.connection:send (bencode.encode msg))))
:done
(fn [self msg-id]
(tset self.active-requests msg-id nil))
:handle
(fn [self response]
(pp response)
(local handlers (self:merge-handlers response))
(each [prop handler (pairs handlers)]
(local idiv (prop:find :/))
(local key (if idiv (prop:sub 1 (- idiv 1)) prop))
(print "checking" key (and idiv (prop:sub (+ idiv 1))))
(when (and (. response key)
(or (= idiv nil)
(contains? (. response key) (prop:sub (+ idiv 1)))))
(print "handling")
(handler self (. response key) response))))
:disconnect
(fn [self]
(when self.connection
(self.connection:close)
(set self.connection nil)
(set self.default-session nil)
(set self.active-requests {})
(set self.session-handlers {})))
:connect
(fn [self ?port ?host]
(when (not self.connection)
(local (port host) (values (or ?port 7888) (or ?host :localhost)))
(set self.connection (assert (socket.connect host port)))
(self.connection:settimeout 0)
(core.add_thread #(while true (self:receive) (coroutine.yield)) self)
(self:send {:op :clone}
{:new-session #(set self.default-session $2)})))
:connected?
(fn [self] (not= self.default-session nil))
:new-session
(fn [self callback ?handler]
(self:send {:op :clone}
{:new-session
(fn [self session message]
(tset self.session-handlers session ?handler)
(callback self session message))}))})
nrepl

View file

@ -1,19 +1,13 @@
(require "editor") (local editor (require :editor))
(local util (require "lib.util")) (local util (require :lib.util))
(local lume (require "lib.lume")) (local lume (require :lib.lume))
(local link (require "link")) (local link (require :link))
(local core (require "core")) (require :link.command)
(local command (require "core.command")) (local core (require :core))
(local keymap (require "core.keymap")) (local command (require :core.command))
(local translate (require "core.doc.translate")) (local keymap (require :core.keymap))
(local translate (require :core.doc.translate))
(each [_ linktype (ipairs link.types)]
(command.add #(not= link.name linktype) {
(.. "honeylisp:switch-to-" linktype) #(link:switch linktype)
}))
(command.add #(= link.name :tape) {
"honeylisp:squelch" #(link.machine:stop)
})
(command.add #(link.machine:connected?) { (command.add #(link.machine:connected?) {
"honeylisp:upload" (fn [] "honeylisp:upload" (fn []
(local p (util.reload "game")) (local p (util.reload "game"))
@ -39,14 +33,7 @@
(ldoc:get_text aline acol bline bcol)) (ldoc:get_text aline acol bline bcol))
(command.add "core.docview" { (command.add "core.docview" {
"fennel:eval" (fn [] "fennel:eval" #(editor.inline-eval #(fv (fennel.eval $1 {:env _G :compiler-env _G}) {}))
(let [ldoc core.active_view.doc
(aline acol bline bcol) (ldoc:get_selection)
options {:env _G :compiler-env _G}
inject #(ldoc:insert bline bcol (fv (fennel.eval $1 options) {}))]
(if (and (= aline bline) (= acol bcol))
(inject (ldoc:get_text aline 1 aline 10000000))
(inject (ldoc:get_text aline acol bline bcol)))))
"lume:hotswap" (fn [] "lume:hotswap" (fn []
(local modname (local modname
(-> core.active_view.doc.filename (-> core.active_view.doc.filename