aboutsummaryrefslogtreecommitdiff
path: root/error_handling.moon
diff options
context:
space:
mode:
Diffstat (limited to 'error_handling.moon')
-rw-r--r--error_handling.moon143
1 files changed, 143 insertions, 0 deletions
diff --git a/error_handling.moon b/error_handling.moon
new file mode 100644
index 0000000..101c11e
--- /dev/null
+++ b/error_handling.moon
@@ -0,0 +1,143 @@
+-- This file contains the logic for making nicer error messages
+debug_getinfo = debug.getinfo
+export SOURCE_MAP
+
+ok, to_lua = pcall -> require('moonscript.base').to_lua
+if not ok then to_lua = nil
+moonscript_line_tables = setmetatable {}, {
+ __index: (filename)=>
+ return nil unless to_lua
+ _, line_table = to_lua(FILE_CACHE[filename])
+ self[filename] = line_table
+ return line_table
+}
+
+-- Make a better version of debug.getinfo that provides info about the original source
+-- where the error came from, even if that's in another language.
+debug.getinfo = (thread,f,what)->
+ if what == nil
+ f,what,thread = thread,f,nil
+ if type(f) == 'number' then f += 1 -- Account for this wrapper function
+ info = if thread == nil
+ debug_getinfo(f,what)
+ else debug_getinfo(thread,f,what)
+ if not info or not info.func then return info
+ if info.short_src or info.source or info.linedefine or info.currentline
+ -- TODO: get name properly
+ if map = SOURCE_MAP[info.source]
+ if info.currentline
+ info.currentline = assert(map[info.currentline])
+ if info.linedefined
+ info.linedefined = assert(map[info.linedefined])
+ if info.lastlinedefined
+ info.lastlinedefined = assert(map[info.lastlinedefined])
+ info.short_src = info.source\match('@([^[]*)') or info.short_src
+ return info
+
+print_err_msg = (error_message, stack_offset=3)->
+ io.stderr\write("#{colored.red "ERROR:"} #{colored.bright colored.red (error_message or "")}\n")
+ io.stderr\write("stack traceback:\n")
+
+ -- TODO: properly print out the calling site of nomsu code, not just the *called* code
+ ok, to_lua = pcall -> require('moonscript.base').to_lua
+ if not ok then to_lua = -> nil
+ LINE_TABLES = setmetatable {},
+ __index: (file)=>
+ _, line_table = to_lua(file)
+ self[file] = line_table or false
+ return line_table or false
+
+ get_line = (file, line_no)->
+ start = LINE_STARTS[file][line_no] or 1
+ stop = (LINE_STARTS[file][line_no+1] or 0) - 1
+ return file\sub(start, stop)
+
+ level = stack_offset
+ while true
+ -- TODO: reduce duplicate code
+ calling_fn = debug_getinfo(level)
+ if not calling_fn then break
+ if calling_fn.func == run then break
+ level += 1
+ name = calling_fn.name and "function '#{calling_fn.name}'" or nil
+ if calling_fn.linedefined == 0 then name = "main chunk"
+ if name == "run_lua_fn" then continue
+ line = nil
+ if map = SOURCE_MAP[calling_fn.source]
+ if calling_fn.currentline
+ calling_fn.currentline = assert(map[calling_fn.currentline])
+ if calling_fn.linedefined
+ calling_fn.linedefined = assert(map[calling_fn.linedefined])
+ if calling_fn.lastlinedefined
+ calling_fn.lastlinedefined = assert(map[calling_fn.lastlinedefined])
+ --calling_fn.short_src = calling_fn.source\match('"([^[]*)')
+ filename,start,stop = calling_fn.source\match('@([^[]*)%[([0-9]+):([0-9]+)]')
+ assert(filename)
+ file = FILE_CACHE[filename]\sub(tonumber(start),tonumber(stop))
+ err_line = get_line(file, calling_fn.currentline)\sub(1,-2)
+ offending_statement = colored.bright(colored.red(err_line\match("^[ ]*(.*)")))
+ -- TODO: get name properly
+ name = "action '#{calling_fn.name}'"
+ line = colored.yellow("#{filename}:#{calling_fn.currentline} in #{name}\n #{offending_statement}")
+ else
+ ok, file = pcall ->FILE_CACHE[calling_fn.short_src]
+ if not ok then file = nil
+ local line_num
+ if name == nil
+ search_level = level
+ _info = debug.getinfo(search_level)
+ while _info and (_info.func == pcall or _info.func == xpcall)
+ search_level += 1
+ _info = debug.getinfo(search_level)
+ if _info
+ for i=1,999
+ varname, val = debug.getlocal(search_level, i)
+ if not varname then break
+ if val == calling_fn.func
+ name = "local '#{varname}'"
+ if not varname\match("%(")
+ break
+ unless name
+ for i=1,_info.nups
+ varname, val = debug.getupvalue(_info.func, i)
+ if not varname then break
+ if val == calling_fn.func
+ name = "upvalue '#{varname}'"
+ if not varname\match("%(")
+ break
+ if file and calling_fn.short_src\match(".moon$") and LINE_TABLES[file]
+ char = LINE_TABLES[file][calling_fn.currentline]
+ line_num = 1
+ for _ in file\sub(1,char)\gmatch("\n") do line_num += 1
+ line = colored.cyan("#{calling_fn.short_src}:#{line_num} in #{name or '?'}")
+ else
+ line_num = calling_fn.currentline
+ if calling_fn.short_src == '[C]'
+ line = colored.green("#{calling_fn.short_src} in #{name or '?'}")
+ else
+ line = colored.blue("#{calling_fn.short_src}:#{calling_fn.currentline} in #{name or '?'}")
+
+ if file
+ err_line = get_line(file, line_num)\sub(1,-2)
+ offending_statement = colored.bright(colored.red(err_line\match("^[ ]*(.*)$")))
+ line ..= "\n "..offending_statement
+ io.stderr\write(" #{line}\n")
+ if calling_fn.istailcall
+ io.stderr\write(" #{colored.dim colored.white " (...tail calls...)"}\n")
+
+ io.stderr\flush!
+
+err_hand = (error_message)->
+ print_err_msg error_message
+ os.exit(false, true)
+
+-- Note: xpcall has a slightly different API in Lua <=5.1 vs. >=5.2, but this works
+-- for both APIs
+has_ldt, ldt = pcall(require,'ldt')
+safe_run = if has_ldt
+ ldt.guard
+else
+ (fn)->
+ xpcall(fn, err_hand)
+
+return safe_run