code / nomsu

Lines6.6K Lua5.1K PEG1.3K make117
2 others 83
Markdown60 Bourne Again Shell23
(390 lines)
1 local debug_getinfo = debug.getinfo
2 local Files = require("files")
3 local C = require("colors")
4 local pretty_error = require("pretty_errors")
5 local ok, to_lua = pcall(function()
6 return require('moonscript.base').to_lua
7 end)
8 if not ok then
9 to_lua = function()
10 return nil
11 end
12 end
13 local MOON_SOURCE_MAP = setmetatable({ }, {
14 __index = function(self, file)
15 local _, line_table = to_lua(file)
16 self[file] = line_table or false
17 return line_table or false
18 end
19 })
20 debug.getinfo = function(thread, f, what)
21 if what == nil then
22 f, what, thread = thread, f, nil
23 end
24 if type(f) == 'number' then
25 f = f + 1
26 end
27 local info
28 if thread == nil then
29 info = debug_getinfo(f, what)
30 else
31 info = debug_getinfo(thread, f, what)
32 end
33 if not info or not info.func then
34 return info
35 end
36 if info.short_src or info.source or info.linedefine or info.currentline then
37 do
38 local map = SOURCE_MAP[info.source]
39 if map then
40 if info.currentline then
41 info.currentline = assert(map[info.currentline])
42 end
43 if info.linedefined then
44 info.linedefined = assert(map[info.linedefined])
45 end
46 if info.lastlinedefined then
47 info.lastlinedefined = assert(map[info.lastlinedefined])
48 end
49 info.short_src = info.source:match('@([^[]*)') or info.short_src
50 if info.name then
51 info.name = "action '" .. tostring(info.name:from_lua_id()) .. "'"
52 else
53 info.name = "main chunk"
54 end
55 end
56 end
57 end
58 return info
59 end
60 local strdist
61 strdist = function(a, b, cache)
62 if cache == nil then
63 cache = { }
64 end
65 if a == b then
66 return 0
67 end
68 if #a < #b then
69 a, b = b, a
70 end
71 if b == "" then
72 return #a
73 end
74 local k = a .. '\003' .. b
75 if not (cache[k]) then
76 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))
77 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
78 cache[k] = math.min(cache[k], strdist(a:sub(1, -3), b:sub(1, -3), cache) + 1)
79 end
80 end
81 return cache[k]
82 end
83 local enhance_error
84 enhance_error = function(error_message)
85 if not (error_message and error_message:match("%d|")) then
86 error_message = error_message or ""
87 do
88 local fn_name = error_message:match("attempt to call a nil value %(method '(.*)'%)")
89 if fn_name then
90 local action_name = fn_name:from_lua_id()
91 error_message = "This object does not have the method '" .. tostring(action_name) .. "'."
92 else
93 do
94 fn_name = (error_message:match("attempt to call a nil value %(global '(.*)'%)") or error_message:match("attempt to call global '(.*)' %(a nil value%)"))
95 if fn_name then
96 local action_name = fn_name:from_lua_id()
97 error_message = "The action '" .. tostring(action_name) .. "' is not defined."
98 local func = debug.getinfo(2, 'f').func
99 local env
100 if _VERSION == "Lua 5.1" then
101 env = getfenv(func)
102 else
103 local ename
104 ename, env = debug.getupvalue(func, 1)
105 if not (ename == "_ENV" or ename == "_G") then
106 func = debug.getinfo(3, 'f').func
107 ename, env = debug.getupvalue(func, 1)
108 end
109 end
110 local THRESHOLD = math.min(4.5, .9 * #action_name)
111 local candidates = { }
112 local cache = { }
113 for i = 1, 99 do
114 local k, v = debug.getlocal(2, i)
115 if k == nil then
116 break
117 end
118 if not (k:sub(1, 1) == "(" or type(v) ~= 'function') then
119 k = k:from_lua_id()
120 if strdist(k, action_name, cache) <= THRESHOLD and k ~= "" then
121 table.insert(candidates, k)
122 end
123 end
124 end
125 for i = 1, debug.getinfo(func, 'u').nups do
126 local k, v = debug.getupvalue(func, i)
127 if not (k:sub(1, 1) == "(" or type(v) ~= 'function') then
128 k = k:from_lua_id()
129 if strdist(k, action_name, cache) <= THRESHOLD and k ~= "" then
130 table.insert(candidates, k)
131 end
132 end
133 end
134 local scan
135 scan = function(t, is_lua_id)
136 if not (t) then
137 return
138 end
139 for k, v in pairs(t) do
140 if type(k) == 'string' and type(v) == 'function' then
141 if not (is_lua_id) then
142 k = k:from_lua_id()
143 end
144 if strdist(k, action_name, cache) <= THRESHOLD and k ~= "" then
145 table.insert(candidates, k)
146 end
147 end
148 end
149 end
150 scan(env.COMPILE_RULES, true)
151 scan(env.COMPILE_RULES._IMPORTS, true)
152 scan(env)
153 scan(env._IMPORTS)
154 if #candidates > 0 then
155 for _index_0 = 1, #candidates do
156 local c = candidates[_index_0]
157 THRESHOLD = math.min(THRESHOLD, strdist(c, action_name, cache))
158 end
160 local _accum_0 = { }
161 local _len_0 = 1
162 for _index_0 = 1, #candidates do
163 local c = candidates[_index_0]
164 if strdist(c, action_name, cache) <= THRESHOLD then
165 _accum_0[_len_0] = c
166 _len_0 = _len_0 + 1
167 end
168 end
169 candidates = _accum_0
170 end
171 if #candidates == 1 then
172 error_message = error_message .. "\n\x1b[3mSuggestion: Maybe you meant '" .. tostring(candidates[1]) .. "'? "
173 elseif #candidates > 0 then
174 local last = table.remove(candidates)
175 error_message = error_message .. ("\n" .. C('italic', "Suggestion: Maybe you meant '" .. tostring(table.concat(candidates, "', '")) .. "'" .. tostring(#candidates > 1 and ',' or '') .. " or '" .. tostring(last) .. "'? "))
176 end
177 end
178 end
179 end
180 end
181 end
182 local level = 2
183 while true do
184 local calling_fn = debug_getinfo(level)
185 if not calling_fn then
186 break
187 end
188 level = level + 1
189 local filename, file, line_num
191 local map = SOURCE_MAP and SOURCE_MAP[calling_fn.source]
192 if map then
193 if calling_fn.currentline then
194 line_num = assert(map[calling_fn.currentline])
195 end
196 local start, stop
197 filename, start, stop = calling_fn.source:match('@([^[]*)%[([0-9]+):([0-9]+)]')
198 if not filename then
199 filename, start = calling_fn.source:match('@([^[]*)%[([0-9]+)]')
200 end
201 assert(filename)
202 file = Files.read(filename)
203 else
204 filename = calling_fn.short_src
205 file = Files.read(filename)
206 if calling_fn.short_src:match("%.moon$") and type(MOON_SOURCE_MAP[file]) == 'table' then
207 local char = MOON_SOURCE_MAP[file][calling_fn.currentline]
208 line_num = file:line_number_at(char)
209 else
210 line_num = calling_fn.currentline
211 end
212 end
213 end
214 if file and filename and line_num then
215 local start = 1
216 local lines = file:lines()
217 for i = 1, line_num - 1 do
218 start = start + #lines[i] + 1
219 end
220 local stop = start + #lines[line_num]
221 start = start + #lines[line_num]:match("^ *")
222 error_message = pretty_error({
223 title = "Error",
224 error = error_message,
225 source = file,
226 start = start,
227 stop = stop,
228 filename = filename
230 break
231 end
232 if calling_fn.func == xpcall then
233 break
234 end
235 end
236 end
237 local ret = {
238 C('bold red', error_message or "Error"),
239 "stack traceback:"
241 local level = 2
242 while true do
243 local _continue_0 = false
244 repeat
245 local calling_fn = debug_getinfo(level)
246 if not calling_fn then
247 break
248 end
249 if calling_fn.func == xpcall then
250 break
251 end
252 level = level + 1
253 local name = calling_fn.name and "function '" .. tostring(calling_fn.name) .. "'" or nil
254 if calling_fn.linedefined == 0 then
255 name = "main chunk"
256 end
257 if name == "function 'run_lua_fn'" then
258 _continue_0 = true
259 break
260 end
261 local line = nil
263 local map = SOURCE_MAP and SOURCE_MAP[calling_fn.source]
264 if map then
265 if calling_fn.currentline then
266 calling_fn.currentline = assert(map[calling_fn.currentline])
267 end
268 if calling_fn.linedefined then
269 calling_fn.linedefined = assert(map[calling_fn.linedefined])
270 end
271 if calling_fn.lastlinedefined then
272 calling_fn.lastlinedefined = assert(map[calling_fn.lastlinedefined])
273 end
274 local filename, start, stop = calling_fn.source:match('@([^[]*)%[([0-9]+):([0-9]+)]')
275 if not filename then
276 filename, start = calling_fn.source:match('@([^[]*)%[([0-9]+)]')
277 end
278 assert(filename)
279 if calling_fn.name then
280 name = "action '" .. tostring(calling_fn.name:from_lua_id()) .. "'"
281 else
282 name = "main chunk"
283 end
284 local file = Files.read(filename)
285 local lines = file and file:lines() or { }
287 local err_line = lines[calling_fn.currentline]
288 if err_line then
289 local offending_statement = C('bright red', err_line:match("^[ ]*(.*)"))
290 line = C('yellow', tostring(filename) .. ":" .. tostring(calling_fn.currentline) .. " in " .. tostring(name) .. "\n " .. tostring(offending_statement))
291 else
292 line = C('yellow', tostring(filename) .. ":" .. tostring(calling_fn.currentline) .. " in " .. tostring(name))
293 end
294 end
295 else
296 local line_num
297 if name == nil then
298 local search_level = level
299 local _info = debug.getinfo(search_level)
300 while true do
301 search_level = search_level + 1
302 _info = debug.getinfo(search_level)
303 if not (_info) then
304 break
305 end
306 for i = 1, 999 do
307 local varname, val = debug.getlocal(search_level, i)
308 if not varname then
309 break
310 end
311 if val == calling_fn.func then
312 name = "local '" .. tostring(varname) .. "'"
313 if not varname:match("%(") then
314 break
315 end
316 end
317 end
318 if not (name) then
319 for i = 1, _info.nups do
320 local varname, val = debug.getupvalue(_info.func, i)
321 if not varname then
322 break
323 end
324 if val == calling_fn.func then
325 name = "upvalue '" .. tostring(varname) .. "'"
326 if not varname:match("%(") then
327 break
328 end
329 end
330 end
331 end
332 end
333 end
334 local file, lines
336 file = Files.read(calling_fn.short_src)
337 if file then
338 lines = file:lines()
339 end
340 end
341 if file and (calling_fn.short_src:match("%.moon$") or file:match("^#![^\n]*moon\n")) and type(MOON_SOURCE_MAP[file]) == 'table' then
342 local char = MOON_SOURCE_MAP[file][calling_fn.currentline]
343 line_num = file:line_number_at(char)
344 line = C('cyan', tostring(calling_fn.short_src) .. ":" .. tostring(line_num) .. " in " .. tostring(name or '?'))
345 else
346 line_num = calling_fn.currentline
347 if calling_fn.short_src == '[C]' then
348 line = C('green', tostring(calling_fn.short_src) .. " in " .. tostring(name or '?'))
349 else
350 line = C('blue', tostring(calling_fn.short_src) .. ":" .. tostring(calling_fn.currentline) .. " in " .. tostring(name or '?'))
351 end
352 end
353 if file then
355 local err_line = lines[line_num]
356 if err_line then
357 local offending_statement = C('bright red', tostring(err_line:match("^[ ]*(.*)$")))
358 line = line .. ("\n " .. offending_statement)
359 end
360 end
361 end
362 end
363 end
364 table.insert(ret, line)
365 if calling_fn.istailcall then
366 table.insert(ret, C('dim', " (...tail calls...)"))
367 end
368 _continue_0 = true
369 until true
370 if not _continue_0 then
371 break
372 end
373 end
374 return table.concat(ret, "\n")
375 end
376 local guard
377 guard = function(fn)
378 local err
379 ok, err = xpcall(fn, enhance_error)
380 if not ok then
381 io.stderr:write(err)
382 io.stderr:flush()
383 return os.exit(1)
384 end
385 end
386 return {
387 guard = guard,
388 enhance_error = enhance_error,
389 print_error = print_error