aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBruce Hill <bruce@bruce-hill.com>2019-01-18 20:46:04 -0800
committerBruce Hill <bruce@bruce-hill.com>2019-01-18 20:46:10 -0800
commit5a99a24176d9dd2cea3d802989b47d55bd89e932 (patch)
tree3f1e49d960fb4cd5d803276f860566ffe95a3604
parent13cab23e204ede4f54e81f500418ece276970f31 (diff)
Better error handling.
-rw-r--r--error_handling.lua138
-rw-r--r--error_handling.moon96
-rw-r--r--lib/core/errors.nom12
-rw-r--r--nomsu_environment.lua2
-rw-r--r--nomsu_environment.moon2
5 files changed, 165 insertions, 85 deletions
diff --git a/error_handling.lua b/error_handling.lua
index 27022aa..8d6e7d9 100644
--- a/error_handling.lua
+++ b/error_handling.lua
@@ -1,5 +1,6 @@
local debug_getinfo = debug.getinfo
local Files = require("files")
+local pretty_error = require("pretty_errors")
local RED = "\027[31m"
local BRIGHT_RED = "\027[31;1m"
local RESET = "\027[0m"
@@ -54,16 +55,7 @@ debug.getinfo = function(thread, f, what)
end
info.short_src = info.source:match('@([^[]*)') or info.short_src
if info.name then
- do
- local tmp = info.name:match("^A_([a-zA-Z0-9_]*)$")
- if tmp then
- info.name = tmp:gsub("_", " "):gsub("x([0-9A-F][0-9A-F])", function(self)
- return string.char(tonumber(self, 16))
- end)
- else
- info.name = info.name
- end
- end
+ info.name = "action '" .. tostring(calling_fn.name:from_lua_id()) .. "'"
else
info.name = "main chunk"
end
@@ -72,12 +64,78 @@ debug.getinfo = function(thread, f, what)
end
return info
end
-local print_error
-print_error = function(error_message, start_fn, stop_fn)
- io.stderr:write(tostring(RED) .. "ERROR: " .. tostring(BRIGHT_RED) .. tostring(error_message or "") .. tostring(RESET) .. "\n")
- io.stderr:write("stack traceback:\n")
- local level = 1
- local found_start = false
+local enhance_error
+enhance_error = function(error_message, start_fn, stop_fn)
+ if not (error_message and error_message:match("\x1b")) then
+ error_message = error_message or ""
+ do
+ local fn = error_message:match("attempt to call a nil value %(global '(.*)'%)")
+ if fn then
+ if fn:match("x[0-9A-F][0-9A-F]") then
+ error_message = "The action '" .. tostring(fn:from_lua_id()) .. "' is not defined."
+ end
+ end
+ end
+ local level = 2
+ while true do
+ local calling_fn = debug_getinfo(level)
+ if not calling_fn then
+ break
+ end
+ level = level + 1
+ local filename, file, line_num
+ do
+ local map = SOURCE_MAP and SOURCE_MAP[calling_fn.source]
+ if map then
+ if calling_fn.currentline then
+ line_num = assert(map[calling_fn.currentline])
+ end
+ local start, stop
+ filename, start, stop = calling_fn.source:match('@([^[]*)%[([0-9]+):([0-9]+)]')
+ if not filename then
+ filename, start = calling_fn.source:match('@([^[]*)%[([0-9]+)]')
+ end
+ assert(filename)
+ file = Files.read(filename)
+ else
+ filename = calling_fn.short_src
+ file = Files.read(filename)
+ if calling_fn.short_src:match("%.moon$") and type(MOON_SOURCE_MAP[file]) == 'table' then
+ local char = MOON_SOURCE_MAP[file][calling_fn.currentline]
+ line_num = file:line_number_at(char)
+ else
+ line_num = calling_fn.currentline
+ end
+ end
+ end
+ if file and filename and line_num then
+ local start = 1
+ local lines = file:lines()
+ for i = 1, line_num - 1 do
+ start = start + #lines[i] + 1
+ end
+ local stop = start + #lines[line_num]
+ start = start + #lines[line_num]:match("^ *")
+ error_message = pretty_error({
+ title = "Error",
+ error = error_message,
+ source = file,
+ start = start,
+ stop = stop,
+ filename = filename
+ })
+ break
+ end
+ if calling_fn.func == xpcall then
+ break
+ end
+ end
+ end
+ local ret = {
+ tostring(RED) .. "ERROR: " .. tostring(BRIGHT_RED) .. tostring(error_message or "") .. tostring(RESET),
+ "stack traceback:"
+ }
+ local level = 2
while true do
local _continue_0 = false
repeat
@@ -85,19 +143,15 @@ print_error = function(error_message, start_fn, stop_fn)
if not calling_fn then
break
end
- level = level + 1
- if not (found_start) then
- if calling_fn.func == start_fn then
- found_start = true
- end
- _continue_0 = true
+ if calling_fn.func == xpcall then
break
end
+ level = level + 1
local name = calling_fn.name and "function '" .. tostring(calling_fn.name) .. "'" or nil
if calling_fn.linedefined == 0 then
name = "main chunk"
end
- if name == "run_lua_fn" then
+ if name == "function 'run_lua_fn'" then
_continue_0 = true
break
end
@@ -120,16 +174,7 @@ print_error = function(error_message, start_fn, stop_fn)
end
assert(filename)
if calling_fn.name then
- do
- local tmp = calling_fn.name:match("^A_([a-zA-Z0-9_]*)$")
- if tmp then
- name = "action '" .. tostring(tmp:gsub("_", " "):gsub("x([0-9A-F][0-9A-F])", function(self)
- return string.char(tonumber(self, 16))
- end)) .. "'"
- else
- name = "action '" .. tostring(calling_fn.name) .. "'"
- end
- end
+ name = "action '" .. tostring(calling_fn.name:from_lua_id()) .. "'"
else
name = "main chunk"
end
@@ -192,10 +237,7 @@ print_error = function(error_message, start_fn, stop_fn)
end
if file and (calling_fn.short_src:match("%.moon$") or file:match("^#![^\n]*moon\n")) and type(MOON_SOURCE_MAP[file]) == 'table' then
local char = MOON_SOURCE_MAP[file][calling_fn.currentline]
- line_num = 1
- for _ in file:sub(1, char):gmatch("\n") do
- line_num = line_num + 1
- end
+ line_num = file:line_number_at(char)
line = tostring(CYAN) .. tostring(calling_fn.short_src) .. ":" .. tostring(line_num) .. " in " .. tostring(name or '?') .. tostring(RESET)
else
line_num = calling_fn.currentline
@@ -216,12 +258,9 @@ print_error = function(error_message, start_fn, stop_fn)
end
end
end
- io.stderr:write(line, "\n")
+ table.insert(ret, line)
if calling_fn.istailcall then
- io.stderr:write(" " .. tostring(DIM) .. "(...tail calls...)" .. tostring(RESET) .. "\n")
- end
- if calling_fn.func == stop_fn then
- break
+ table.insert(ret, " " .. tostring(DIM) .. "(...tail calls...)" .. tostring(RESET))
end
_continue_0 = true
until true
@@ -229,19 +268,20 @@ print_error = function(error_message, start_fn, stop_fn)
break
end
end
- return io.stderr:flush()
+ return table.concat(ret, "\n")
end
local guard
guard = function(fn)
- local error_handler
- error_handler = function(error_message)
- print_error(error_message, error_handler, fn)
- local EXIT_FAILURE = 1
- return os.exit(EXIT_FAILURE)
+ local err
+ ok, err = xpcall(fn, enhance_error)
+ if not ok then
+ io.stderr:write(err)
+ io.stderr:flush()
+ return os.exit(1)
end
- return xpcall(fn, error_handler)
end
return {
guard = guard,
+ enhance_error = enhance_error,
print_error = print_error
}
diff --git a/error_handling.moon b/error_handling.moon
index c22a3ae..3252727 100644
--- a/error_handling.moon
+++ b/error_handling.moon
@@ -1,6 +1,7 @@
-- This file contains the logic for making nicer error messages
debug_getinfo = debug.getinfo
Files = require "files"
+pretty_error = require("pretty_errors")
export SOURCE_MAP
RED = "\027[31m"
@@ -31,7 +32,7 @@ debug.getinfo = (thread,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
+ -- TODO: reduce duplicate code
if map = SOURCE_MAP[info.source]
if info.currentline
info.currentline = assert(map[info.currentline])
@@ -40,31 +41,71 @@ debug.getinfo = (thread,f,what)->
if info.lastlinedefined
info.lastlinedefined = assert(map[info.lastlinedefined])
info.short_src = info.source\match('@([^[]*)') or info.short_src
- -- TODO: get name properly
info.name = if info.name
- if tmp = info.name\match("^A_([a-zA-Z0-9_]*)$")
- tmp\gsub("_"," ")\gsub("x([0-9A-F][0-9A-F])", => string.char(tonumber(@, 16)))
- else info.name
+ "action '#{calling_fn.name\from_lua_id!}'"
else "main chunk"
return info
-print_error = (error_message, start_fn, stop_fn)->
- io.stderr\write("#{RED}ERROR: #{BRIGHT_RED}#{error_message or ""}#{RESET}\n")
- io.stderr\write("stack traceback:\n")
+enhance_error = (error_message, start_fn, stop_fn)->
+ unless error_message and error_message\match("\x1b")
+ error_message or= ""
+ if fn = error_message\match("attempt to call a nil value %(global '(.*)'%)")
+ if fn\match "x[0-9A-F][0-9A-F]"
+ error_message = "The action '#{fn\from_lua_id!}' is not defined."
+ level = 2
+ while true
+ -- TODO: reduce duplicate code
+ calling_fn = debug_getinfo(level)
+ if not calling_fn then break
+ level += 1
+ local filename, file, line_num
+ if map = SOURCE_MAP and SOURCE_MAP[calling_fn.source]
+ if calling_fn.currentline
+ line_num = assert(map[calling_fn.currentline])
+ filename,start,stop = calling_fn.source\match('@([^[]*)%[([0-9]+):([0-9]+)]')
+ if not filename
+ filename,start = calling_fn.source\match('@([^[]*)%[([0-9]+)]')
+ assert(filename)
+ file = Files.read(filename)
+ else
+ filename = calling_fn.short_src
+ file = Files.read(filename)
+ if calling_fn.short_src\match("%.moon$") and type(MOON_SOURCE_MAP[file]) == 'table'
+ char = MOON_SOURCE_MAP[file][calling_fn.currentline]
+ line_num = file\line_number_at(char)
+ else
+ line_num = calling_fn.currentline
+
+ if file and filename and line_num
+ start = 1
+ lines = file\lines!
+ for i=1,line_num-1 do start += #lines[i] + 1
+ stop = start + #lines[line_num]
+ start += #lines[line_num]\match("^ *")
+ error_message = pretty_error{
+ title:"Error"
+ error:error_message, source:file
+ start:start, stop:stop, filename:filename
+ }
+ break
+ if calling_fn.func == xpcall then break
+
+
+ ret = {
+ "#{RED}ERROR: #{BRIGHT_RED}#{error_message or ""}#{RESET}"
+ "stack traceback:"
+ }
- level = 1
- found_start = false
+ level = 2
while true
-- TODO: reduce duplicate code
calling_fn = debug_getinfo(level)
if not calling_fn then break
+ if calling_fn.func == xpcall then break
level += 1
- unless found_start
- if calling_fn.func == start_fn then found_start = true
- continue
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
+ if name == "function 'run_lua_fn'" then continue
line = nil
if map = SOURCE_MAP and SOURCE_MAP[calling_fn.source]
if calling_fn.currentline
@@ -78,11 +119,8 @@ print_error = (error_message, start_fn, stop_fn)->
if not filename
filename,start = calling_fn.source\match('@([^[]*)%[([0-9]+)]')
assert(filename)
- -- TODO: get name properly
name = if calling_fn.name
- if tmp = calling_fn.name\match("^A_([a-zA-Z0-9_]*)$")
- "action '#{tmp\gsub("_"," ")\gsub("x([0-9A-F][0-9A-F])", => string.char(tonumber(@, 16)))}'"
- else "action '#{calling_fn.name}'"
+ "action '#{calling_fn.name\from_lua_id!}'"
else "main chunk"
file = Files.read(filename)
@@ -123,8 +161,7 @@ print_error = (error_message, start_fn, stop_fn)->
if file and (calling_fn.short_src\match("%.moon$") or file\match("^#![^\n]*moon\n")) and type(MOON_SOURCE_MAP[file]) == 'table'
char = MOON_SOURCE_MAP[file][calling_fn.currentline]
- line_num = 1
- for _ in file\sub(1,char)\gmatch("\n") do line_num += 1
+ line_num = file\line_number_at(char)
line = "#{CYAN}#{calling_fn.short_src}:#{line_num} in #{name or '?'}#{RESET}"
else
line_num = calling_fn.currentline
@@ -137,18 +174,17 @@ print_error = (error_message, start_fn, stop_fn)->
if err_line = lines[line_num]
offending_statement = "#{BRIGHT_RED}#{err_line\match("^[ ]*(.*)$")}#{RESET}"
line ..= "\n "..offending_statement
- io.stderr\write(line,"\n")
+ table.insert ret, line
if calling_fn.istailcall
- io.stderr\write(" #{DIM}(...tail calls...)#{RESET}\n")
- if calling_fn.func == stop_fn then break
+ table.insert ret, " #{DIM}(...tail calls...)#{RESET}"
- io.stderr\flush!
+ return table.concat(ret, "\n")
guard = (fn)->
- error_handler = (error_message)->
- print_error error_message, error_handler, fn
- EXIT_FAILURE = 1
- os.exit(EXIT_FAILURE)
- xpcall(fn, error_handler)
+ ok, err = xpcall(fn, enhance_error)
+ if not ok
+ io.stderr\write err
+ io.stderr\flush!
+ os.exit 1
-return {:guard, :print_error}
+return {:guard, :enhance_error, :print_error}
diff --git a/lib/core/errors.nom b/lib/core/errors.nom
index 8027228..a84b580 100644
--- a/lib/core/errors.nom
+++ b/lib/core/errors.nom
@@ -25,7 +25,7 @@ use "core/control_flow"
local _a, _b = \($a as lua expr), \($b as lua expr)
if _a ~= _b then
at_1_fail(\(quote "\($a.source)"),
- "Assumption failed: This value was "..tostring(_a).." when it was supposed to be "..tostring(_b)..".")
+ "Assumption failed: This value was "..tostring(_a).." but it was expected to be "..tostring(_b)..".")
end
end
")
@@ -35,7 +35,7 @@ use "core/control_flow"
local _a, _b = \($a as lua expr), \($b as lua expr)
if _a == _b then
at_1_fail(\(quote "\($a.source)"),
- "Assumption failed: This value was "..tostring(_a).." when it wasn't supposed to be.")
+ "Assumption failed: This value was "..tostring(_a).." but it wasn't expected to be.")
end
end
")
@@ -76,10 +76,10 @@ test:
Lua ("
do
local _fell_through = false
- local _result = {pcall(function()
+ local _result = {xpcall(function()
\($action as lua)
_fell_through = true
- end)}
+ end, enhance_error)}
if _result[1] then
\$success_lua
else
@@ -123,10 +123,10 @@ test:
(do $action then always $final_action) compiles to ("
do -- do/then always
local _fell_through = false
- local _results = {pcall(function()
+ local _results = {xpcall(function()
\($action as lua)
_fell_through = true
- end)}
+ end, enhance_error)}
\($final_action as lua)
if not _results[1] then error(_results[2], 0) end
if not _fell_through then return table.unpack(_results, 2) end
diff --git a/nomsu_environment.lua b/nomsu_environment.lua
index 204b151..d9014c3 100644
--- a/nomsu_environment.lua
+++ b/nomsu_environment.lua
@@ -10,6 +10,7 @@ do
end
local SyntaxTree = require("syntax_tree")
local Files = require("files")
+local Errhand = require("error_handling")
local make_parser = require("parser")
local pretty_error = require("pretty_errors")
local make_tree
@@ -137,6 +138,7 @@ nomsu_environment = Importer({
NomsuCode_from = (function(src, ...)
return NomsuCode:from(src, ...)
end),
+ enhance_error = Errhand.enhance_error,
SOURCE_MAP = { },
getfenv = getfenv,
_1_as_nomsu = tree_to_nomsu,
diff --git a/nomsu_environment.moon b/nomsu_environment.moon
index 522b4c9..f26a7e9 100644
--- a/nomsu_environment.moon
+++ b/nomsu_environment.moon
@@ -4,6 +4,7 @@
{:List, :Dict, :Text} = require 'containers'
SyntaxTree = require "syntax_tree"
Files = require "files"
+Errhand = require "error_handling"
make_parser = require("parser")
pretty_error = require("pretty_errors")
@@ -61,6 +62,7 @@ nomsu_environment = Importer{
:LuaCode, :NomsuCode, :Source
LuaCode_from: ((src, ...)-> LuaCode\from(src, ...)),
NomsuCode_from: ((src, ...)-> NomsuCode\from(src, ...)),
+ enhance_error: Errhand.enhance_error
SOURCE_MAP: {},
getfenv:getfenv,