diff options
author | Daniel Silverstone <dsilvers@digital-scurf.org> | 2012-07-17 17:38:03 +0100 |
---|---|---|
committer | Daniel Silverstone <dsilvers@digital-scurf.org> | 2012-07-17 17:38:03 +0100 |
commit | 141e868d75c56ede9165d361bcdc84e162443753 (patch) | |
tree | 9fb392cb97ef8ec509f230fac2463d3a16e8d294 | |
parent | 49ab9c94a11ac988f34c63c8cf96dd2ba56b2ad7 (diff) | |
download | lace-141e868d75c56ede9165d361bcdc84e162443753.tar.gz |
LACE: More error normalisation, and error rendering capability
-rw-r--r-- | lib/lace/builtin.lua | 6 | ||||
-rw-r--r-- | lib/lace/compiler.lua | 2 | ||||
-rw-r--r-- | lib/lace/engine.lua | 8 | ||||
-rw-r--r-- | lib/lace/error.lua | 65 | ||||
-rw-r--r-- | test/test-lace.error.lua | 59 |
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) |