summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Silverstone <dsilvers@digital-scurf.org>2012-07-17 17:38:03 +0100
committerDaniel Silverstone <dsilvers@digital-scurf.org>2012-07-17 17:38:03 +0100
commit141e868d75c56ede9165d361bcdc84e162443753 (patch)
tree9fb392cb97ef8ec509f230fac2463d3a16e8d294
parent49ab9c94a11ac988f34c63c8cf96dd2ba56b2ad7 (diff)
downloadlace-141e868d75c56ede9165d361bcdc84e162443753.tar.gz
LACE: More error normalisation, and error rendering capability
-rw-r--r--lib/lace/builtin.lua6
-rw-r--r--lib/lace/compiler.lua2
-rw-r--r--lib/lace/engine.lua8
-rw-r--r--lib/lace/error.lua65
-rw-r--r--test/test-lace.error.lua59
5 files changed, 125 insertions, 15 deletions
diff --git a/lib/lace/builtin.lua b/lib/lace/builtin.lua
index 03fe7bc..e6c02f1 100644
--- a/lib/lace/builtin.lua
+++ b/lib/lace/builtin.lua
@@ -183,11 +183,7 @@ function builtin.define(compcontext, define, name, controltype, ...)
local ctrltab, msg = controlfn(compcontext, controltype, ...)
if type(ctrltab) ~= "table" then
-- offset all the words in the error by 2 (for define and name)
- if msg.words then
- for i = 1, #msg.words do
- msg.words[i] = msg.words[i] + 2
- end
- end
+ msg = err.offset(msg, 2)
return false, msg
end
diff --git a/lib/lace/compiler.lua b/lib/lace/compiler.lua
index 4fd975b..1b0b21a 100644
--- a/lib/lace/compiler.lua
+++ b/lib/lace/compiler.lua
@@ -124,7 +124,7 @@ local function internal_compile_ruleset(compcontext, sourcename, content, suppre
-- There's no unconditional result and no default, fake up a default and
-- then use it.
if not suppress_default and not uncond and not result then
- return false, { msg = "No result set whatsoever", words = {} }
+ return err.error("No result set whatsoever")
end
if not suppress_default and not uncond then
diff --git a/lib/lace/engine.lua b/lib/lace/engine.lua
index dcf8a68..b4db68d 100644
--- a/lib/lace/engine.lua
+++ b/lib/lace/engine.lua
@@ -7,9 +7,7 @@
-- For licence terms, see COPYING
--
-local function _error(str, words)
- return { msg = str, words = words }
-end
+local err = require 'lace.error'
local function _dlace(ctx)
local ret = ctx._lace or {}
@@ -21,7 +19,7 @@ local function set_define(exec_context, name, defn)
local dlace = _dlace(exec_context)
dlace.defs = dlace.defs or {}
if dlace.defs[name] then
- return false, _error("Attempted to redefine " .. name, {2})
+ return err.error("Attempted to redefine " .. name, {2})
end
dlace.defs[name] = defn
return true
@@ -32,7 +30,7 @@ local function test_define(exec_context, name)
dlace.defs = dlace.defs or {}
local defn = dlace.defs[name]
if not defn then
- return nil, _error("Unknown definition: " .. name)
+ return err.error("Unknown definition: " .. name, {2}, true)
end
-- Otherwise we evaluate the definition and return it
return defn.fn(exec_context, unpack(defn.args))
diff --git a/lib/lace/error.lua b/lib/lace/error.lua
index d52da1d..d71d21d 100644
--- a/lib/lace/error.lua
+++ b/lib/lace/error.lua
@@ -7,10 +7,67 @@
-- For licence terms, see COPYING
--
-local function _error(str, words)
- return false, { msg = str, words = words or {} }
+local function _error(str, words, rnil)
+ local ret = false
+ if rnil then
+ ret = nil
+ end
+ return ret, { msg = str, words = words or {} }
+end
+
+local function _offset(err, offs)
+ if not err.words then
+ err.words = {}
+ end
+ for k, w in ipairs(err.words) do
+ err.words[k] = w + offs
+ end
+ return err
+end
+
+local function _augment(err, source, linenr)
+ err.source = source
+ err.linenr = linenr
+end
+
+local function _render(err)
+ -- A rendered error has four lines
+ -- The first line is the error message
+ local ret = { err.msg }
+ -- The second is the source filename and line
+ ret[2] = err.source.source .. " :: " .. tostring(err.linenr)
+ -- The third line is the line of the input
+ local srcline = err.source.lines[err.linenr] or {
+ original = "???", content = { {spos = 1, epos = 3, str = "???"} }
+ }
+ ret[3] = srcline.original
+ -- The fourth line is the highlight for each word in question
+ local wordset = {}
+ for _, word in ipairs(err.words) do
+ wordset[word] = true
+ end
+ local hlstr = ""
+ local cpos = 1
+ for w, info in ipairs(srcline.content) do
+ -- Ensure that we're up to the start position of the word
+ while (cpos < info.spos) do
+ hlstr = hlstr .. " "
+ cpos = cpos + 1
+ end
+ -- Highlight this word if appropriate
+ while (cpos <= info.epos) do
+ hlstr = hlstr .. (wordset[w] and "^" or " ")
+ cpos = cpos + 1
+ end
+ end
+ ret[4] = hlstr
+ -- The rendered error is those four strings joined by newlines
+ return table.concat(ret, "\n")
end
return {
- error = _error
-} \ No newline at end of file
+ error = _error,
+ offset = _offset,
+ augment = _augment,
+ render = _render,
+}
diff --git a/test/test-lace.error.lua b/test/test-lace.error.lua
index 4c15acd..471935b 100644
--- a/test/test-lace.error.lua
+++ b/test/test-lace.error.lua
@@ -38,6 +38,65 @@ function suite.error_formation()
assert(ret2.words == words, "Words should be passed through")
end
+function suite.error_offset()
+ local words = {3,5}
+ local f, err = error.error("msg", words)
+ local nerr = error.offset(err, 2)
+ assert(nerr == err, "Offset should return the same error it was given")
+ assert(words[1] == 5, "Offset should alter the first word")
+ assert(words[2] == 7, "Offset should alter all the words")
+end
+
+function suite.error_augmentation()
+ local f, err = error.error("msg")
+ local src = {}
+ error.augment(err, src, 10)
+ assert(err.source == src, "Augmented errors should contain their source data")
+ assert(err.linenr == 10, "Augmented errors should contain their error line")
+end
+
+function suite.error_render()
+ local f, err = error.error("msg", {1, 3})
+ local src = { source = "SOURCE", lines = {
+ {
+ original = " ORIG LINE FISH",
+ content = {
+ { spos = 3, epos = 6, str = "ORIG" },
+ { spos = 8, epos = 11, str = "LINE" },
+ { spos = 13, epos = 16, str = "FISH" },
+ }
+ }
+ }
+ }
+ error.augment(err, src, 1)
+ local estr = error.render(err)
+ local line1, line2, line3, line4 = estr:match("^([^\n]*)\n([^\n]*)\n([^\n]*)\n([^\n]*)$")
+ assert(line1, "There is a line 1")
+ assert(line2, "There is a line 2")
+ assert(line3, "There is a line 3")
+ assert(line4, "There is a line 4")
+ assert(line1 == "msg", "The first line should be the error message")
+ assert(line2 == "SOURCE :: 1", "The second line is where the error happened")
+ assert(line3 == src.lines[1].original, "The third line is the original line")
+ assert(line4 == " ^^^^ ^^^^", "The fourth line highlights relevant words")
+end
+
+function suite.error_render_bad_line()
+ local f, err = error.error("msg", {1, 3})
+ local src = { source = "SOURCE", lines = { } }
+ error.augment(err, src, 1)
+ local estr = error.render(err)
+ local line1, line2, line3, line4 = estr:match("^([^\n]*)\n([^\n]*)\n([^\n]*)\n([^\n]*)$")
+ assert(line1, "There is a line 1")
+ assert(line2, "There is a line 2")
+ assert(line3, "There is a line 3")
+ assert(line4, "There is a line 4")
+ assert(line1 == "msg", "The first line should be the error message")
+ assert(line2 == "SOURCE :: 1", "The second line is where the error happened")
+ assert(line3 == "???", "The third line is the original line")
+ assert(line4 == "^^^", "The fourth line highlights relevant words")
+end
+
local count_ok = 0
for _, testname in ipairs(testnames) do
-- print("Run: " .. testname)