aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--error_handling.lua103
-rw-r--r--error_handling.moon77
2 files changed, 173 insertions, 7 deletions
diff --git a/error_handling.lua b/error_handling.lua
index b7d5d13..ecf6088 100644
--- a/error_handling.lua
+++ b/error_handling.lua
@@ -57,14 +57,109 @@ debug.getinfo = function(thread, f, what)
end
return info
end
+local strdist
+strdist = function(a, b, cache)
+ if cache == nil then
+ cache = { }
+ end
+ if a == b then
+ return 0
+ end
+ if #a < #b then
+ a, b = b, a
+ end
+ if b == "" then
+ return #a
+ end
+ local k = a .. '\003' .. b
+ if not (cache[k]) then
+ cache[k] = math.min(strdist(a:sub(1, -2), b, cache) + 1, strdist(a, b:sub(1, -2), cache) + 1, strdist(a:sub(1, -2), b:sub(1, -2), cache) + (a:sub(-1) ~= b:sub(-1) and 1.1 or 0))
+ if #a >= 2 and #b >= 2 and a:sub(-1, -1) == b:sub(-2, -2) and a:sub(-2, -2) == b:sub(-1, -1) then
+ cache[k] = math.min(cache[k], strdist(a:sub(1, -3), b:sub(1, -3), cache) + 1)
+ end
+ end
+ return cache[k]
+end
local enhance_error
-enhance_error = function(error_message, start_fn, stop_fn)
+enhance_error = function(error_message)
if not (error_message and error_message:match("%d|")) then
error_message = error_message or ""
do
- local fn = (error_message:match("attempt to call a nil value %(global '(.*)'%)") or error_message:match("attempt to call global '(.*)' %(a nil value%)"))
- if fn then
- error_message = "The action '" .. tostring(fn:from_lua_id()) .. "' is not defined."
+ local fn_name = (error_message:match("attempt to call a nil value %(global '(.*)'%)") or error_message:match("attempt to call global '(.*)' %(a nil value%)"))
+ if fn_name then
+ local action_name = fn_name:from_lua_id()
+ error_message = "The action '" .. tostring(action_name) .. "' is not defined."
+ local func = debug.getinfo(2, 'f').func
+ local ename, env = debug.getupvalue(func, 1)
+ if not (ename == "_ENV" or ename == "_G") then
+ func = debug.getinfo(3, 'f').func
+ ename, env = debug.getupvalue(func, 1)
+ end
+ local THRESHOLD = math.min(4.5, .9 * #action_name)
+ local candidates = { }
+ local cache = { }
+ for i = 1, 99 do
+ local k, v = debug.getlocal(2, i)
+ if k == nil then
+ break
+ end
+ if not (k:sub(1, 1) == "(" or type(v) ~= 'function') then
+ k = k:from_lua_id()
+ if strdist(k, action_name, cache) <= THRESHOLD and k ~= "" then
+ table.insert(candidates, k)
+ end
+ end
+ end
+ for i = 1, debug.getinfo(func, 'u').nups do
+ local k, v = debug.getupvalue(func, i)
+ if not (k:sub(1, 1) == "(" or type(v) ~= 'function') then
+ k = k:from_lua_id()
+ if strdist(k, action_name, cache) <= THRESHOLD and k ~= "" then
+ table.insert(candidates, k)
+ end
+ end
+ end
+ local scan
+ scan = function(t, is_lua_id)
+ for k, v in pairs(t) do
+ if type(k) == 'string' and type(v) == 'function' then
+ if not (is_lua_id) then
+ k = k:from_lua_id()
+ end
+ if strdist(k, action_name, cache) <= THRESHOLD and k ~= "" then
+ table.insert(candidates, k)
+ end
+ end
+ end
+ end
+ scan(env.COMPILE_RULES, true)
+ scan(env.COMPILE_RULES._IMPORTS, true)
+ scan(env)
+ scan(env._IMPORTS)
+ if #candidates > 0 then
+ for _index_0 = 1, #candidates do
+ local c = candidates[_index_0]
+ THRESHOLD = math.min(THRESHOLD, strdist(c, action_name, cache))
+ end
+ do
+ local _accum_0 = { }
+ local _len_0 = 1
+ for _index_0 = 1, #candidates do
+ local c = candidates[_index_0]
+ if strdist(c, action_name, cache) <= THRESHOLD then
+ _accum_0[_len_0] = c
+ _len_0 = _len_0 + 1
+ end
+ end
+ candidates = _accum_0
+ end
+ if #candidates == 1 then
+ error_message = error_message .. "\n\x1b[3mSuggestion: Maybe you meant '" .. tostring(candidates[1]) .. "'? "
+ elseif #candidates > 0 then
+ local last = table.remove(candidates)
+ error_message = error_message .. ("\n" .. C('italic', "Suggestion: Maybe you meant '" .. tostring(table.concat(candidates, "', '")) .. "'" .. tostring(#candidates > 1 and ',' or '') .. " or '" .. tostring(last) .. "'? "))
+ end
+ end
end
end
local level = 2
diff --git a/error_handling.moon b/error_handling.moon
index ebd8309..b69b75f 100644
--- a/error_handling.moon
+++ b/error_handling.moon
@@ -38,13 +38,84 @@ debug.getinfo = (thread,f,what)->
else "main chunk"
return info
-enhance_error = (error_message, start_fn, stop_fn)->
+-- This uses a slightly modified Damerau-Levenshtein distance:
+strdist = (a,b,cache={})->
+ if a == b then return 0
+ if #a < #b then a,b = b,a
+ if b == "" then return #a
+ k = a..'\003'..b
+ unless cache[k]
+ -- Insert, delete, substitute (given weight 1.1 as a heuristic)
+ cache[k] = math.min(
+ strdist(a\sub(1,-2),b,cache) + 1,
+ strdist(a,b\sub(1,-2),cache) + 1,
+ strdist(a\sub(1,-2),b\sub(1,-2),cache) + (a\sub(-1) ~= b\sub(-1) and 1.1 or 0)
+ )
+ -- Transposition:
+ if #a >= 2 and #b >= 2 and a\sub(-1,-1) == b\sub(-2,-2) and a\sub(-2,-2) == b\sub(-1,-1)
+ cache[k] = math.min(cache[k], strdist(a\sub(1,-3),b\sub(1,-3),cache) + 1)
+ return cache[k]
+
+enhance_error = (error_message)->
-- Hacky: detect the line numbering
unless error_message and error_message\match("%d|")
error_message or= ""
- if fn = (error_message\match("attempt to call a nil value %(global '(.*)'%)") or
+ if fn_name = (error_message\match("attempt to call a nil value %(global '(.*)'%)") or
error_message\match("attempt to call global '(.*)' %(a nil value%)"))
- error_message = "The action '#{fn\from_lua_id!}' is not defined."
+
+ action_name = fn_name\from_lua_id!
+ error_message = "The action '#{action_name}' is not defined."
+
+ -- This check is necessary for handling both top-level code and code inside a fn
+ func = debug.getinfo(2,'f').func
+ ename,env = debug.getupvalue(func, 1)
+ unless ename == "_ENV" or ename == "_G"
+ func = debug.getinfo(3,'f').func
+ ename,env = debug.getupvalue(func, 1)
+
+ THRESHOLD = math.min(4.5, .9*#action_name) -- Ignore matches with strdist > THRESHOLD
+ candidates = {}
+ cache = {}
+
+ -- Locals:
+ for i=1,99
+ k, v = debug.getlocal(2, i)
+ break if k == nil
+ unless k\sub(1,1) == "(" or type(v) != 'function'
+ k = k\from_lua_id!
+ if strdist(k, action_name, cache) <= THRESHOLD and k != ""
+ table.insert candidates, k
+
+ -- Upvalues:
+ for i=1,debug.getinfo(func, 'u').nups
+ k, v = debug.getupvalue(func, i)
+ unless k\sub(1,1) == "(" or type(v) != 'function'
+ k = k\from_lua_id!
+ if strdist(k, action_name, cache) <= THRESHOLD and k != ""
+ table.insert candidates, k
+
+ -- Globals and global compile rules:
+ scan = (t, is_lua_id)->
+ for k,v in pairs(t)
+ if type(k) == 'string' and type(v) == 'function'
+ k = k\from_lua_id! unless is_lua_id
+ if strdist(k, action_name, cache) <= THRESHOLD and k != ""
+ table.insert candidates, k
+ scan env.COMPILE_RULES, true
+ scan env.COMPILE_RULES._IMPORTS, true
+ scan env
+ scan env._IMPORTS
+
+ if #candidates > 0
+ for c in *candidates do THRESHOLD = math.min(THRESHOLD, strdist(c, action_name, cache))
+ candidates = [c for c in *candidates when strdist(c, action_name, cache) <= THRESHOLD]
+ --candidates = ["#{c}[#{strdist(c,action_name,cache)}/#{THRESHOLD}]" for c in *candidates]
+ if #candidates == 1
+ error_message ..= "\n\x1b[3mSuggestion: Maybe you meant '#{candidates[1]}'? "
+ elseif #candidates > 0
+ last = table.remove(candidates)
+ error_message ..= "\n"..C('italic', "Suggestion: Maybe you meant '#{table.concat candidates, "', '"}'#{#candidates > 1 and ',' or ''} or '#{last}'? ")
+
level = 2
while true
-- TODO: reduce duplicate code