melee-roguelite/lib/stdio.fnl
2022-04-12 18:46:28 +02:00

46 lines
1.9 KiB
Fennel

(require "love.event")
;; This module exists in order to expose stdio over a channel so that it
;; can be used in a non-blocking way from another thread.
(fn prompt [cont?]
(io.write (if cont? ".." ">> ")) (io.flush) (.. (tostring (io.read)) "\n"))
;; This module is loaded twice: initially in the main thread where ... is nil,
;; and then again in a separate thread where ... contains the channel used to
;; communicate with the main thread.
(fn looper [event channel]
(match (channel:demand)
[:write vals] (do (io.write (table.concat vals "\t"))
(io.write "\n"))
[:read cont?] (love.event.push event (prompt cont?)))
(looper event channel))
(match ...
(event channel) (looper event channel))
{:start (fn start-repl []
(let [code (love.filesystem.read "lib/stdio.fnl")
luac (if code
(love.filesystem.newFileData
(fennel.compileString code) "io")
(love.filesystem.read "lib/stdio.lua"))
thread (love.thread.newThread luac)
io-channel (love.thread.newChannel)
coro (coroutine.create fennel.repl)
options {:readChunk (fn [{: stack-size}]
(io-channel:push [:read (< 0 stack-size)])
(coroutine.yield))
:onValues (fn [vals]
(io-channel:push [:write vals]))
:onError (fn [errtype err]
(io-channel:push [:write [err]]))
:moduleName "lib.fennel"}]
;; this thread will send "eval" events for us to consume:
(coroutine.resume coro options)
(thread:start "eval" io-channel)
(set love.handlers.eval
(fn [input]
(coroutine.resume coro input)))))}