local tl = require("tl")
local lint = require("htl.lint")
local fmt_mod = require("htl.fmt")
local H = {}
H.lint_cfg = lint.DEFAULT
function H.set_lints(spec)
local cfg, err = lint.config(spec)
if not cfg then return nil, err end
H.lint_cfg = cfg
return true
end
function H.lint_rules()
return lint.rule_names()
end
function H.format(filename, indent)
local fd, err = io.open(filename, "rb")
if not fd then return nil, "could not open " .. filename .. ": " .. tostring(err) end
local src = fd:read("a")
fd:close()
return fmt_mod.format(src, filename, { indent = indent })
end
H.GEN_TARGET = "5.4"
local function new_env()
return assert(tl.new_env({
defaults = {
feat_lax = "off",
gen_compat = "off",
gen_target = H.GEN_TARGET,
},
}), "htl: tl.new_env failed")
end
H.env = new_env()
local function fmt(filename, e)
return string.format("%s:%d:%d: %s", e.filename or filename, e.y or 0, e.x or 0, e.msg or "?")
end
function H.check(filename)
local env = new_env() local result, err = tl.check_file(filename, env)
if not result then
return { ok = false, errors = { tostring(err) }, warnings = {} }
end
local errors, warnings = {}, {}
for _, e in ipairs(result.syntax_errors or {}) do errors[#errors + 1] = fmt(filename, e) end
for _, e in ipairs(result.type_errors or {}) do errors[#errors + 1] = fmt(filename, e) end
for _, w in ipairs(result.warnings or {}) do warnings[#warnings + 1] = fmt(filename, w) end
local deps = {}
for _, fname in pairs(result.dependencies or {}) do deps[#deps + 1] = fname end
table.sort(deps)
local lints = {}
if result.ast and #(result.syntax_errors or {}) == 0 then
local src
local fd = io.open(filename, "rb")
if fd then src = fd:read("a"); fd:close() end
if src then
local found = lint.run(src, filename, H.lint_cfg)
for _, l in ipairs(found or {}) do lints[#lints + 1] = fmt(filename, l) end
end
end
return { ok = #errors == 0, errors = errors, warnings = warnings, deps = deps, lints = lints, result = result }
end
function H.gen(filename)
local c = H.check(filename)
if not c.ok then
return nil, c
end
local code, gerr = tl.generate(c.result.ast, H.GEN_TARGET)
if not code then
c.ok = false
c.errors = { filename .. ": generate failed: " .. tostring(gerr) }
return nil, c
end
return code, c
end
function H.gen_string(src, filename)
local result = tl.check_string(src, H.env, filename)
local errors = {}
for _, e in ipairs(result.syntax_errors or {}) do errors[#errors + 1] = fmt(filename, e) end
for _, e in ipairs(result.type_errors or {}) do errors[#errors + 1] = fmt(filename, e) end
local warnings = {}
for _, w in ipairs(result.warnings or {}) do warnings[#warnings + 1] = fmt(filename, w) end
local c = { ok = #errors == 0, errors = errors, warnings = warnings, deps = {}, lints = {}, result = result }
if not c.ok or not result.ast then
return nil, c
end
local code, gerr = tl.generate(result.ast, H.GEN_TARGET)
if not code then
c.ok = false
c.errors = { filename .. ": generate failed: " .. tostring(gerr) }
return nil, c
end
return code, c
end
local function strict_searcher(module_name)
local found, fd = tl.search_module(module_name, false)
if not found then
local dfound, dfd = tl.search_module(module_name, true)
if dfound and dfound:match("%.d%.tl$") then
dfd:close()
local lua_path = dfound:gsub("%.d%.tl$", ".lua")
local init_path = dfound:gsub("%.d%.tl$", "/init.lua")
local lf = io.open(lua_path, "rb") or io.open(init_path, "rb")
if lf then
lf:close()
return "\n\ttype-only '" .. dfound .. "' (implementation served by the .lua searcher)"
end
return function() return {} end, dfound
elseif dfd then
dfd:close()
end
return "\n\tno .tl module '" .. module_name .. "' on package.path"
end
fd:close()
local code, c = H.gen(found)
if not code then
error(table.concat(c.errors, "\n"), 0)
end
local chunk, lerr = load(code, "@" .. found, "t")
if not chunk then
error("htl: generated Lua failed to load: " .. tostring(lerr), 0)
end
return function(modname)
return chunk(modname, found)
end, found
end
function H.install_searcher()
table.insert(package.searchers, 2, strict_searcher)
end
function H.add_path(dir)
package.path = dir .. "/?.lua;" .. dir .. "/?/init.lua;" .. package.path
end
return H