Updated stdio.fnl to use built in fennel repl.
This commit is contained in:
parent
67692bd5e0
commit
373398ce36
2866
fennel.lua
Normal file
2866
fennel.lua
Normal file
File diff suppressed because it is too large
Load diff
178
lib/fennel
178
lib/fennel
|
@ -3,80 +3,196 @@
|
||||||
local fennel_dir = arg[0]:match("(.-)[^\\/]+$")
|
local fennel_dir = arg[0]:match("(.-)[^\\/]+$")
|
||||||
package.path = fennel_dir .. "?.lua;" .. package.path
|
package.path = fennel_dir .. "?.lua;" .. package.path
|
||||||
local fennel = require('fennel')
|
local fennel = require('fennel')
|
||||||
|
local unpack = unpack or table.unpack
|
||||||
|
|
||||||
local help = [[
|
local help = [[
|
||||||
Usage: fennel [FLAG] [FILE]
|
Usage: fennel [FLAG] [FILE]
|
||||||
|
|
||||||
|
Run fennel, a lisp programming language for the Lua runtime.
|
||||||
|
|
||||||
--repl : Launch an interactive repl session
|
--repl : Launch an interactive repl session
|
||||||
--compile FILES : Compile files and write their Lua to stdout
|
--compile FILES : Compile files and write their Lua to stdout
|
||||||
--help : Display this text
|
|
||||||
|
|
||||||
When not given a flag, runs the file given as the first argument.]]
|
--no-searcher : Skip installing package.searchers entry
|
||||||
|
--indent VAL : Indent compiler output with VAL
|
||||||
|
--add-package-path PATH : Add PATH to package.path for finding Lua modules
|
||||||
|
--add-fennel-path PATH : Add PATH to fennel.path for finding Fennel modules
|
||||||
|
--globals G1[,G2...] : Allow these globals in addition to standard ones
|
||||||
|
--globals-only G1[,G2] : Same as above, but exclude standard ones
|
||||||
|
--require-as-include : Inline required modules in the output
|
||||||
|
--metadata : Enable function metadata, even in compiled output
|
||||||
|
--no-metadata : Disable function metadata, even in REPL
|
||||||
|
--correlate : Make Lua output line numbers match Fennel input
|
||||||
|
|
||||||
local options = {
|
--eval SOURCE (-e) : Evaluate source code and print the result
|
||||||
sourcemap = true,
|
|
||||||
}
|
|
||||||
|
|
||||||
local function dosafe(filename, opts, arg1)
|
--help (-h) : Display this text
|
||||||
|
--version (-v) : Show version
|
||||||
|
|
||||||
|
Metadata is typically considered a development feature and is not recommended
|
||||||
|
for production. It is used for docstrings and enabled by default in the REPL.
|
||||||
|
|
||||||
|
When not given a flag, runs the file given as the first argument.
|
||||||
|
When given neither flag nor file, launches a repl.
|
||||||
|
|
||||||
|
If ~/.fennelrc exists, loads it before launching a repl.]]
|
||||||
|
|
||||||
|
local options = {}
|
||||||
|
|
||||||
|
local function dosafe(filename, opts, args)
|
||||||
local ok, val = xpcall(function()
|
local ok, val = xpcall(function()
|
||||||
return fennel.dofile(filename, opts, arg1)
|
return fennel.dofile(filename, opts, unpack(args))
|
||||||
end, fennel.traceback)
|
end, fennel.traceback)
|
||||||
if not ok then
|
if not ok then
|
||||||
print(val)
|
io.stderr:write(val .. "\n")
|
||||||
os.exit(1)
|
os.exit(1)
|
||||||
end
|
end
|
||||||
return val
|
return val
|
||||||
end
|
end
|
||||||
|
|
||||||
local compileOptHandlers = {
|
local function allowGlobals(globalNames)
|
||||||
['--indent'] = function ()
|
options.allowedGlobals = {}
|
||||||
options.indent = table.remove(arg, 3)
|
for g in globalNames:gmatch("([^,]+),?") do
|
||||||
|
table.insert(options.allowedGlobals, g)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for i=#arg, 1, -1 do
|
||||||
|
if arg[i] == "--no-searcher" then
|
||||||
|
options.no_searcher = true
|
||||||
|
table.remove(arg, i)
|
||||||
|
elseif arg[i] == "--indent" then
|
||||||
|
options.indent = table.remove(arg, i+1)
|
||||||
if options.indent == "false" then options.indent = false end
|
if options.indent == "false" then options.indent = false end
|
||||||
table.remove(arg, 2)
|
table.remove(arg, i)
|
||||||
end,
|
elseif arg[i] == "--add-package-path" then
|
||||||
['--sourcemap'] = function ()
|
local entry = table.remove(arg, i+1)
|
||||||
options.sourcemap = table.remove(arg, 3)
|
package.path = entry .. ";" .. package.path
|
||||||
if options.sourcemap == "false" then options.sourcemap = false end
|
table.remove(arg, i)
|
||||||
table.remove(arg, 2)
|
elseif arg[i] == "--add-fennel-path" then
|
||||||
end,
|
local entry = table.remove(arg, i+1)
|
||||||
['--correlate'] = function ()
|
fennel.path = entry .. ";" .. fennel.path
|
||||||
|
table.remove(arg, i)
|
||||||
|
elseif arg[i] == "--correlate" then
|
||||||
options.correlate = true
|
options.correlate = true
|
||||||
table.remove(arg, 2)
|
table.remove(arg, i)
|
||||||
end,
|
elseif arg[i] == "--globals" then
|
||||||
}
|
allowGlobals(table.remove(arg, i+1))
|
||||||
|
for globalName in pairs(_G) do
|
||||||
|
table.insert(options.allowedGlobals, globalName)
|
||||||
|
end
|
||||||
|
table.remove(arg, i)
|
||||||
|
elseif arg[i] == "--globals-only" then
|
||||||
|
allowGlobals(table.remove(arg, i+1))
|
||||||
|
table.remove(arg, i)
|
||||||
|
elseif arg[i] == "--require-as-include" then
|
||||||
|
options.requireAsInclude = true
|
||||||
|
table.remove(arg, i)
|
||||||
|
elseif arg[i] == "--metadata" then
|
||||||
|
options.useMetadata = true
|
||||||
|
table.remove(arg, i)
|
||||||
|
elseif arg[i] == "--no-metadata" then
|
||||||
|
options.useMetadata = false
|
||||||
|
table.remove(arg, i)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not options.no_searcher then
|
||||||
|
local opts = {}
|
||||||
|
for k,v in pairs(options) do opts[k] = v end
|
||||||
|
table.insert((package.loaders or package.searchers), fennel.make_searcher(opts))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Try to load readline library
|
||||||
|
local function tryReadline(opts)
|
||||||
|
local ok, readline = pcall(require, "readline")
|
||||||
|
if ok then
|
||||||
|
readline.set_options({
|
||||||
|
keeplines = 1000
|
||||||
|
})
|
||||||
|
function opts.readChunk(parserState)
|
||||||
|
local prompt = parserState.stackSize > 0 and '.. ' or '>> '
|
||||||
|
local str = readline.readline(prompt)
|
||||||
|
if str then
|
||||||
|
return str .. "\n"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- completer is registered by the repl, until then returns empty list
|
||||||
|
local completer
|
||||||
|
function opts.registerCompleter(replCompleter)
|
||||||
|
completer = replCompleter
|
||||||
|
end
|
||||||
|
local function replCompleter(text, from, to)
|
||||||
|
if completer then
|
||||||
|
-- stop completed syms from adding extra space on match
|
||||||
|
readline.set_completion_append_character('')
|
||||||
|
return completer(text:sub(from, to))
|
||||||
|
else
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
readline.set_complete_function(replCompleter)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
if arg[1] == "--repl" or #arg == 0 then
|
if arg[1] == "--repl" or #arg == 0 then
|
||||||
local ppok, pp = pcall(fennel.dofile, fennel_dir .. "fennelview.fnl", options)
|
local ppok, pp = pcall(fennel.dofile, fennel_dir .. "fennelview.fnl", options)
|
||||||
if ppok then
|
if ppok then
|
||||||
options.pp = pp
|
options.pp = pp
|
||||||
|
else
|
||||||
|
ppok, pp = pcall(require, "fennelview")
|
||||||
|
if ppok then
|
||||||
|
options.pp = pp
|
||||||
|
end
|
||||||
end
|
end
|
||||||
local initFilename = (os.getenv("HOME") or "") .. "/.fennelrc"
|
local initFilename = (os.getenv("HOME") or "") .. "/.fennelrc"
|
||||||
local init = io.open(initFilename, "rb")
|
local init = io.open(initFilename, "rb")
|
||||||
|
|
||||||
|
tryReadline(options)
|
||||||
|
|
||||||
if init then
|
if init then
|
||||||
init:close()
|
init:close()
|
||||||
-- pass in options so fennerlrc can make changes to it
|
-- pass in options so fennerlrc can make changes to it
|
||||||
dosafe(initFilename, options, options)
|
dosafe(initFilename, options, options)
|
||||||
end
|
end
|
||||||
print("Welcome to fennel!")
|
print("Welcome to Fennel " .. fennel.version .. "!")
|
||||||
|
if options.useMetadata ~= false then
|
||||||
|
print("Use (doc something) to view documentation.")
|
||||||
|
end
|
||||||
fennel.repl(options)
|
fennel.repl(options)
|
||||||
elseif arg[1] == "--compile" then
|
elseif arg[1] == "--compile" then
|
||||||
-- Handle options
|
|
||||||
while compileOptHandlers[arg[2]] do
|
|
||||||
compileOptHandlers[arg[2]]()
|
|
||||||
end
|
|
||||||
for i = 2, #arg do
|
for i = 2, #arg do
|
||||||
local f = assert(io.open(arg[i], "rb"))
|
local f = arg[i] == "-" and io.stdin or assert(io.open(arg[i], "rb"))
|
||||||
options.filename=arg[i]
|
options.filename=arg[i]
|
||||||
local ok, val = xpcall(function()
|
local ok, val = xpcall(function()
|
||||||
return fennel.compileString(f:read("*all"), options)
|
return fennel.compileString(f:read("*all"), options)
|
||||||
end, fennel.traceback)
|
end, fennel.traceback)
|
||||||
|
if ok then
|
||||||
print(val)
|
print(val)
|
||||||
if not ok then os.exit(1) end
|
else
|
||||||
|
io.stderr:write(val .. "\n")
|
||||||
|
os.exit(1)
|
||||||
|
end
|
||||||
f:close()
|
f:close()
|
||||||
end
|
end
|
||||||
elseif #arg >= 1 and arg[1] ~= "--help" then
|
elseif arg[1] == "--eval" or arg[1] == "-e" then
|
||||||
|
if arg[2] and arg[2] ~= "-" then
|
||||||
|
print(fennel.eval(arg[2], options))
|
||||||
|
else
|
||||||
|
local source = io.stdin:read("*a")
|
||||||
|
print(fennel.eval(source, options))
|
||||||
|
end
|
||||||
|
elseif arg[1] == "--version" or arg[1] == "-v" then
|
||||||
|
print("Fennel " .. fennel.version)
|
||||||
|
elseif #arg >= 1 and arg[1] ~= "--help" and arg[1] ~= "-h" then
|
||||||
local filename = table.remove(arg, 1) -- let the script have remaining args
|
local filename = table.remove(arg, 1) -- let the script have remaining args
|
||||||
dosafe(filename)
|
if filename == "-" then
|
||||||
|
local source = io.stdin:read("*a")
|
||||||
|
fennel.eval(source, options)
|
||||||
|
else
|
||||||
|
dosafe(filename, options, arg)
|
||||||
|
end
|
||||||
else
|
else
|
||||||
print(help)
|
print(help)
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
(require "love.event")
|
(require "love.event")
|
||||||
(local fennel (require "lib.fennel"))
|
|
||||||
(local view (require "lib.fennelview"))
|
(local view (require "lib.fennelview"))
|
||||||
|
|
||||||
;; This module exists in order to expose stdio over a channel so that it
|
;; This module exists in order to expose stdio over a channel so that it
|
||||||
|
@ -7,51 +6,41 @@
|
||||||
|
|
||||||
(local (event channel) ...)
|
(local (event channel) ...)
|
||||||
|
|
||||||
(defn display [s]
|
|
||||||
(io.write s)
|
|
||||||
(io.flush))
|
|
||||||
|
|
||||||
(defn prompt []
|
|
||||||
(display "\n>> "))
|
|
||||||
|
|
||||||
(defn read-chunk []
|
|
||||||
(let [input (io.read)]
|
|
||||||
(when input
|
|
||||||
(.. input "\n"))))
|
|
||||||
|
|
||||||
(var input "")
|
|
||||||
(when channel
|
(when channel
|
||||||
(let [(bytestream clearstream) (fennel.granulate read-chunk)
|
(let [prompt (fn [] (io.write "> ") (io.flush) (io.read "*l"))]
|
||||||
read (fennel.parser
|
((fn looper [input]
|
||||||
(fn []
|
(when input
|
||||||
(let [c (or (bytestream) 10)]
|
;; This is consumed by love.handlers[event]
|
||||||
(set input (.. input (string.char c)))
|
|
||||||
c)))]
|
|
||||||
(while true
|
|
||||||
(prompt)
|
|
||||||
(set input "")
|
|
||||||
(let [(ok ast) (pcall read)]
|
|
||||||
(if (not ok)
|
|
||||||
(do
|
|
||||||
(display (.. "Parse error:" ast "\n"))
|
|
||||||
;; fixme: not sure why the following fails
|
|
||||||
;; (clearstream)
|
|
||||||
)
|
|
||||||
(do
|
|
||||||
(love.event.push event input)
|
(love.event.push event input)
|
||||||
(display (: channel :demand))))))))
|
(let [output (: channel :demand)]
|
||||||
|
;; There is probably a more efficient way of determining an error
|
||||||
|
(if (and (. output 2) (= "Error:" (. output 2)))
|
||||||
|
(print (view output))
|
||||||
|
(each [_ ret (ipairs output)]
|
||||||
|
(print ret))))
|
||||||
|
(io.flush)
|
||||||
|
(looper (prompt)))) (prompt))))
|
||||||
|
|
||||||
{:start (fn start-repl []
|
{:start (fn start-repl []
|
||||||
|
|
||||||
(let [code (love.filesystem.read "stdio.fnl")
|
(let [code (love.filesystem.read "stdio.fnl")
|
||||||
lua (if code
|
luac (if code
|
||||||
(love.filesystem.newFileData
|
(love.filesystem.newFileData
|
||||||
(fennel.compileString code) "io")
|
(fennel.compileString code) "io")
|
||||||
(love.filesystem.read "lib/stdio.lua"))
|
(love.filesystem.read "lib/stdio.lua"))
|
||||||
thread (love.thread.newThread lua)
|
thread (love.thread.newThread luac)
|
||||||
io-channel (love.thread.newChannel)]
|
io-channel (love.thread.newChannel)
|
||||||
|
coro (coroutine.create fennel.repl)
|
||||||
|
out (fn [val]
|
||||||
|
(: io-channel :push val))
|
||||||
|
options {:readChunk coroutine.yield
|
||||||
|
:onValues out
|
||||||
|
:onError (fn [kind ...] (out [kind "Error:" ...]))
|
||||||
|
:pp view
|
||||||
|
}]
|
||||||
;; this thread will send "eval" events for us to consume:
|
;; this thread will send "eval" events for us to consume:
|
||||||
|
(coroutine.resume coro options)
|
||||||
(: thread :start "eval" io-channel)
|
(: thread :start "eval" io-channel)
|
||||||
(set love.handlers.eval
|
(set love.handlers.eval
|
||||||
(fn [input]
|
(fn [input]
|
||||||
(let [(ok val) (pcall fennel.eval input)]
|
(coroutine.resume coro (.. input "\n"))))))}
|
||||||
(: io-channel :push (if ok (view val) val)))))))}
|
|
||||||
|
|
116
lib/stdio.lua
116
lib/stdio.lua
|
@ -1,78 +1,46 @@
|
||||||
require("love.event")
|
require("love.event")
|
||||||
local fennel = require("lib.fennel")
|
local view = require("lib.fennelview") local event, channel = ... local function _0_(...) if channel then
|
||||||
local view = require("lib.fennelview")
|
|
||||||
local event, channel = ...
|
|
||||||
local function display(s)
|
|
||||||
io.write(s)
|
|
||||||
return io.flush()
|
|
||||||
end
|
|
||||||
do local _ = display end
|
|
||||||
local function prompt()
|
local prompt = prompt local function _0_() io.write("> ") io.flush() return io.read("*l") end prompt = _0_
|
||||||
return display("\n>> ")
|
local function looper(input) if input then
|
||||||
end
|
|
||||||
do local _ = prompt end
|
|
||||||
local function read_chunk()
|
|
||||||
local input = io.read()
|
|
||||||
if input then
|
|
||||||
return (input .. "\n")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
do local _ = read_chunk end
|
|
||||||
local input = ""
|
|
||||||
local function _0_(...)
|
|
||||||
if channel then
|
|
||||||
local bytestream, clearstream = fennel.granulate(read_chunk)
|
|
||||||
local function _0_()
|
|
||||||
local c = (bytestream() or 10)
|
|
||||||
input = (input .. string.char(c))
|
|
||||||
return c
|
|
||||||
end
|
|
||||||
local read = fennel.parser(_0_)
|
|
||||||
while true do
|
|
||||||
prompt()
|
|
||||||
input = ""
|
|
||||||
do
|
|
||||||
local ok, ast = pcall(read)
|
|
||||||
local function _1_(...)
|
|
||||||
if not ok then
|
|
||||||
return display(("Parse error:" .. ast .. "\n"))
|
|
||||||
else
|
|
||||||
love.event.push(event, input)
|
love.event.push(event, input)
|
||||||
return display(channel:demand())
|
do local output = channel:demand()
|
||||||
end
|
|
||||||
end
|
if (output[2] and ("Error:" == output[2])) then
|
||||||
_1_(...)
|
print(view(output)) else
|
||||||
end
|
for _, ret in ipairs(output) do
|
||||||
end
|
print(ret) end end end
|
||||||
return nil
|
io.flush()
|
||||||
end
|
return looper(prompt()) end end return looper(prompt()) end end _0_(...)
|
||||||
end
|
|
||||||
_0_(...)
|
local function start_repl()
|
||||||
local function start_repl()
|
|
||||||
local code = love.filesystem.read("stdio.fnl")
|
local code = love.filesystem.read("stdio.fnl") local luac = luac
|
||||||
local function _1_()
|
|
||||||
if code then
|
if code then
|
||||||
return love.filesystem.newFileData(fennel.compileString(code), "io")
|
luac = love.filesystem.newFileData(fennel.compileString(code), "io") else
|
||||||
else
|
|
||||||
return love.filesystem.read("lib/stdio.lua")
|
luac = love.filesystem.read("lib/stdio.lua") end
|
||||||
end
|
local thread = love.thread.newThread(luac)
|
||||||
end
|
|
||||||
local lua = _1_()
|
|
||||||
local thread = love.thread.newThread(lua)
|
|
||||||
local io_channel = love.thread.newChannel()
|
local io_channel = love.thread.newChannel()
|
||||||
|
local coro = coroutine.create(fennel.repl) local out = out
|
||||||
|
local function _2_(val)
|
||||||
|
return io_channel:push(val) end out = _2_ local options = options
|
||||||
|
|
||||||
|
|
||||||
|
local function _3_(kind, ...) return out({kind, "Error:", ...}) end options = {onError = _3_, onValues = out, pp = view, readChunk = coroutine.yield}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
coroutine.resume(coro, options)
|
||||||
thread:start("eval", io_channel)
|
thread:start("eval", io_channel)
|
||||||
local function _2_(input)
|
|
||||||
local ok, val = pcall(fennel.eval, input)
|
local function _4_(input)
|
||||||
local function _3_()
|
return coroutine.resume(coro, (input .. "\n")) end love.handlers.eval = _4_ return nil end return {start = start_repl}
|
||||||
if ok then
|
|
||||||
return view(val)
|
|
||||||
else
|
|
||||||
return val
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return io_channel:push(_3_())
|
|
||||||
end
|
|
||||||
love.handlers.eval = _2_
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
return {start = start_repl}
|
|
||||||
|
|
36
readme.org
36
readme.org
|
@ -6,6 +6,13 @@ The first step is to see how fennel works as a standalone execution environment,
|
||||||
|
|
||||||
This repo contains the minimal viable setup to get started with Phil Hegelberg's game design process, plus some additional libraries.
|
This repo contains the minimal viable setup to get started with Phil Hegelberg's game design process, plus some additional libraries.
|
||||||
|
|
||||||
|
#+BEGIN_SRC bash
|
||||||
|
PROJECT=project-name
|
||||||
|
git clone https://gitlab.com/alexjgriffith/min-love2d-fennel.git $PROJECT
|
||||||
|
cd $PROJECT
|
||||||
|
rm -rf .git
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
* Phil's Modal Callbacks (PMC)
|
* Phil's Modal Callbacks (PMC)
|
||||||
Phil Hegelberg's [[https://gitlab.com/technomancy/exo-encounter-667/][exo-encounter-667]] is structured using a modal callback system. Each game state has a mode and each mode has a series of specific callbacks.
|
Phil Hegelberg's [[https://gitlab.com/technomancy/exo-encounter-667/][exo-encounter-667]] is structured using a modal callback system. Each game state has a mode and each mode has a series of specific callbacks.
|
||||||
|
|
||||||
|
@ -38,3 +45,32 @@ If you design your game as a series of states in a very simple state machine, fo
|
||||||
(add-hook 'fennel-mode-hook 'fennel-mode-hook-fun)
|
(add-hook 'fennel-mode-hook 'fennel-mode-hook-fun)
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
|
* Known Issues
|
||||||
|
Presently fennel.lua has to be in the main directory for =fennel-reload= from fennel-mode.el to work. The issue is
|
||||||
|
|
||||||
|
#+BEGIN_SRC elisp
|
||||||
|
(let [old (require :module-name)
|
||||||
|
_ (tset package.loaded :module-name nil)
|
||||||
|
(ok new) (pcall require :module-name) ;; fails if fennel not in base directory
|
||||||
|
new (if (not ok) (do (print new) old) new)]
|
||||||
|
(when (= (type new) :table)
|
||||||
|
(each [k v (pairs new)] (tset old k v))
|
||||||
|
(each [k (pairs old)] (when (not (. new k)) (tset old k nil)))
|
||||||
|
(tset package.loaded :module-name old)))
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
|
||||||
|
The error that gets thrown is.
|
||||||
|
#+BEGIN_SRC
|
||||||
|
./module-name.fnl:#Some Number#: module 'fennel' not found:
|
||||||
|
no field package.preload['fennel']
|
||||||
|
no 'fennel' in LOVE game directories.
|
||||||
|
no file 'fennel' in LOVE paths.
|
||||||
|
no file './fennel.lua'
|
||||||
|
no file '/usr/local/share/luajit-2.0.5/fennel.lua'
|
||||||
|
no file '/usr/local/share/lua/5.1/fennel.lua'
|
||||||
|
no file '/usr/local/share/lua/5.1/fennel/init.lua'
|
||||||
|
no file './fennel.so'
|
||||||
|
no file '/usr/local/lib/lua/5.1/fennel.so'
|
||||||
|
no file '/usr/local/lib/lua/5.1/loadall.so'
|
||||||
|
#+END_SRC
|
||||||
|
|
Loading…
Reference in a new issue