Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement loader for Fennel #740

Merged
merged 1 commit into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions busted-scm-1.rockspec
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ build = {
['busted.modules.files.lua'] = 'busted/modules/files/lua.lua',
['busted.modules.files.moonscript'] = 'busted/modules/files/moonscript.lua',
['busted.modules.files.terra'] = 'busted/modules/files/terra.lua',
['busted.modules.files.fennel'] = 'busted/modules/files/fennel.lua',

['busted.outputHandlers.base'] = 'busted/outputHandlers/base.lua',
['busted.outputHandlers.utfTerminal'] = 'busted/outputHandlers/utfTerminal.lua',
Expand Down
102 changes: 102 additions & 0 deletions busted/modules/files/fennel.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
-- NOTE: the traceback of `pending` and `error` is broken, and there does not
-- seem to be anything we can do about it. The Moonscript loader has the same
-- problem.


local path = require 'pl.path'
local ok, fennel = pcall(require, 'fennel')

local MESSAGE_PATTERN = '^([^\n]-):(%d+): (.*)'
local MESSAGE_TEMPLATE = '%s:%d: %s'
local source_maps = {}

local ret = {}

-- A Fennel variant of the standard Lua loadstring function; registers a source
-- map for the given chunk as a side effect.
local function loadstring_(str, chunk_name)
local options = {filename = chunk_name:sub(2)}
local success, lua, src_map = pcall(fennel.compileString, str, options)
if not success then
return nil, lua, nil
end
-- NOTE: `loadstring` was deprecated in favour of a more general `load` in
-- Lua 5.2
local thunk, err = (loadstring or load)(lua, chunk_name)
return thunk, err, src_map
end

-- A Fennel variant of the standard Lua loadfile function
local function loadfile_(fname)
local file, err = io.open(fname)
if not (file) then
return nil, err
end
local text = assert(file:read("*a"))
file:close()
return loadstring_(text, '@' .. tostring(fname))
end

-- Sometimes Fennel gives files like [string "./filename.fnl"], so we'll chop
-- it up to only get the filename.
local rewrite_filename = function(filename)
return filename:match('string "(.+)"') or filename
end

-- Maps a Lua line number to the corresponding Fennel line number using a
-- previously stored source map.
local rewrite_linenumber = function(fname, lineno)
local src_map = source_maps['@' .. fname]
if not fname or not src_map then return lineno end

local entry = src_map[lineno]
if not entry then return lineno end

-- Assume that all lines of a Lua file are stored in the same Fennel file;
-- the format of all entries is {file_name, line_number}
return entry[2]
end

local getTrace = function(filename, info)
-- NOTE: `info` is the result of `debug.getinfo(level, 'Sl')`, enriched with
-- `traceback` and `message`. The `traceback` is result of
-- `debug.traceback('', level)`

local index = info.traceback:find('\n%s*%[C]')
info.traceback = info.traceback:sub(1, index)

info.short_src = rewrite_filename(info.short_src)
info.linedefined = rewrite_linenumber(filename, info.linedefined)
info.currentline = rewrite_linenumber(filename, info.currentline)

return info
end

local rewriteMessage = function(filename, message)
local fname, lineno, msg = message:match(MESSAGE_PATTERN)
if not fname then
return message
end

fname = rewrite_filename(fname)
lineno = rewrite_linenumber(fname, tonumber(lineno))

return MESSAGE_TEMPLATE:format(fname, tostring(lineno), msg)
end

ret.match = function(busted, filename)
local result = ok and path.extension(filename) == '.fnl'
return result
end

ret.load = function(busted, filename)
local file, err, src_map = loadfile_(filename)
if not file then
busted.publish({'error', 'file'}, {descriptor = 'file', name = filename}, nil, err, {})
else
source_maps[assert(src_map).key] = src_map
end
return file, getTrace, rewriteMessage
end

return ret
Loading