From 2b925da62d6242d3982ec71d09731c5206ddc7b3 Mon Sep 17 00:00:00 2001 From: Daniel Silverstone Date: Tue, 17 Jul 2012 19:14:11 +0100 Subject: LACE: Ensure engine errors are propagated properly at runtime --- lib/lace/builtin.lua | 2 ++ lib/lace/compiler.lua | 1 + lib/lace/engine.lua | 11 ++++----- test/test-lace.compiler.lua | 1 - test/test-lace.engine-doubledefine.rules | 7 ++++++ test/test-lace.engine-unknownanyof.rules | 5 ++++ test/test-lace.engine-unknowndefine.rules | 3 +++ test/test-lace.engine.lua | 41 ++++++++++++++++++++++++++++++- 8 files changed, 63 insertions(+), 8 deletions(-) create mode 100644 test/test-lace.engine-doubledefine.rules create mode 100644 test/test-lace.engine-unknownanyof.rules create mode 100644 test/test-lace.engine-unknowndefine.rules diff --git a/lib/lace/builtin.lua b/lib/lace/builtin.lua index 507e864..2f03549 100644 --- a/lib/lace/builtin.lua +++ b/lib/lace/builtin.lua @@ -27,6 +27,7 @@ local function run_conditions(exec_context, cond, anyof) end local res, msg = engine.test(exec_context, name) if res == nil then + msg.words = {i} return nil, msg end if invert then @@ -68,6 +69,7 @@ local function _do_return(exec_context, result, reason, cond) local pass, msg = run_conditions(exec_context, cond) if pass == nil then -- Pass errors + err.offset(msg, 2) return nil, msg elseif pass == false then -- Conditions failed, return true to continue execution diff --git a/lib/lace/compiler.lua b/lib/lace/compiler.lua index 8100ff3..2b50227 100644 --- a/lib/lace/compiler.lua +++ b/lib/lace/compiler.lua @@ -114,6 +114,7 @@ local function internal_compile_ruleset(compcontext, sourcename, content, suppre return rule, err.augment(msg, ruleset.content, i) end rule.linenr = i + rule.source = ruleset.content ruleset.rules[#ruleset.rules+1] = rule end end diff --git a/lib/lace/engine.lua b/lib/lace/engine.lua index b4db68d..5484c9b 100644 --- a/lib/lace/engine.lua +++ b/lib/lace/engine.lua @@ -30,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 err.error("Unknown definition: " .. name, {2}, true) + return err.error("Unknown definition: " .. name, {1}, true) end -- Otherwise we evaluate the definition and return it return defn.fn(exec_context, unpack(defn.args)) @@ -46,7 +46,7 @@ local function internal_run_ruleset(ruleset, exec_context) dlace.linenr = rule.linenr local result, msg = rule.fn(exec_context, unpack(rule.args)) if not result then - return false, msg + return false, err.augment(msg, rule.source, rule.linenr) elseif result ~= true then -- Explicit result, return it return result, msg @@ -63,15 +63,14 @@ local function run_ruleset(ruleset, exec_context) return internal_run_ruleset(ruleset, exec_context) end, debug.traceback) if not ok then - return nil, ret + local _, msg = err.error(ret, {1}) + return nil, err.render(err.augment(msg, ruleset.rules[1].source, ruleset.rules[1].linenr)) end assert(ret ~= "", "It should not be possible for a ruleset to fail to return a result") if type(msg) == "table" then - -- TODO: Extract position information etc from error and - -- formulate a gorgeous multiline error message. - msg = msg.msg or "Empty error" + msg = err.render(msg) end return ret, msg diff --git a/test/test-lace.compiler.lua b/test/test-lace.compiler.lua index 42936eb..acd7e5d 100644 --- a/test/test-lace.compiler.lua +++ b/test/test-lace.compiler.lua @@ -368,7 +368,6 @@ function suite.error_in_include2() assert(result == false, "Errors compiling should return false") assert(type(msg) == "string", "Compilation errors should be strings") assert(msg:find("\n"), "Compilation errors are multiline") - print(msg) local line1, line2, line3, line4 = msg:match("^([^\n]*)\n([^\n]*)\n([^\n]*)\n([^\n]*)$") assert(line1:find("NOTFOUND"), "The first line must mention the error") assert(line2 == "real-errorininclude2 :: 3", "The second line is where the error happened") diff --git a/test/test-lace.engine-doubledefine.rules b/test/test-lace.engine-doubledefine.rules new file mode 100644 index 0000000..81dc401 --- /dev/null +++ b/test/test-lace.engine-doubledefine.rules @@ -0,0 +1,7 @@ +-- Expect an error on line 5 word 2 + +define fish equal state one + +define fish equal state two + +allow "anyway" diff --git a/test/test-lace.engine-unknownanyof.rules b/test/test-lace.engine-unknownanyof.rules new file mode 100644 index 0000000..2ea2141 --- /dev/null +++ b/test/test-lace.engine-unknownanyof.rules @@ -0,0 +1,5 @@ +-- Expect error at line 5 word 3 + +define fish anyof pants cake + +allow anyway fish diff --git a/test/test-lace.engine-unknowndefine.rules b/test/test-lace.engine-unknowndefine.rules new file mode 100644 index 0000000..42a2fc5 --- /dev/null +++ b/test/test-lace.engine-unknowndefine.rules @@ -0,0 +1,3 @@ +-- Expect error on line 3, word 2 + +allow anyway fish diff --git a/test/test-lace.engine.lua b/test/test-lace.engine.lua index f8faaf8..d0a0b6f 100644 --- a/test/test-lace.engine.lua +++ b/test/test-lace.engine.lua @@ -85,7 +85,7 @@ end function suite.check_error_propagates() local function _explode() - return { fn = function() return false, "EXPLODE" end, args = {} } + return { fn = function() return lace.error.error("EXPLODE", {1}) end, args = {} } end local compctx = {_lace={commands={explode=_explode}}} local ruleset, msg = lace.compiler.compile(compctx, "src", "explode\nallow because") @@ -206,6 +206,45 @@ function suite.test_runtime_error() assert(msg:find("woah"), "Did not generate the right error: " .. msg) end +function suite.doubledefine() + local ruleset, msg = lace.compiler.compile(comp_context, "doubledefine") + assert(type(ruleset) == "table", "Ruleset did not compile") + local ectx = {error = true} + local result, msg = lace.engine.run(ruleset, ectx) + assert(result == false, "Did not error out") + local line1, line2, line3, line4 = msg:match("^([^\n]*)\n([^\n]*)\n([^\n]*)\n([^\n]*)$") + assert(line1:find("redefine fish"), "The first line must mention the error") + assert(line2 == "real-doubledefine :: 5", "The second line is where the error happened") + assert(line3 == "define fish equal state two", "The third line is the original line") + assert(line4 == " ^^^^ ", "The fourth line highlights relevant words") +end + +function suite.unknowndefine() + local ruleset, msg = lace.compiler.compile(comp_context, "unknowndefine") + assert(type(ruleset) == "table", "Ruleset did not compile") + local ectx = {error = true} + local result, msg = lace.engine.run(ruleset, ectx) + assert(result == false, "Did not error out") + local line1, line2, line3, line4 = msg:match("^([^\n]*)\n([^\n]*)\n([^\n]*)\n([^\n]*)$") + assert(line1:find("definition: fish"), "The first line must mention the error") + assert(line2 == "real-unknowndefine :: 3", "The second line is where the error happened") + assert(line3 == "allow anyway fish", "The third line is the original line") + assert(line4 == " ^^^^", "The fourth line highlights relevant words") +end + +function suite.unknownanyof() + local ruleset, msg = lace.compiler.compile(comp_context, "unknownanyof") + assert(type(ruleset) == "table", "Ruleset did not compile") + local ectx = {error = true} + local result, msg = lace.engine.run(ruleset, ectx) + assert(result == false, "Did not error out") + local line1, line2, line3, line4 = msg:match("^([^\n]*)\n([^\n]*)\n([^\n]*)\n([^\n]*)$") + assert(line1:find("definition: pants"), "The first line must mention the error") + assert(line2 == "real-unknownanyof :: 5", "The second line is where the error happened") + assert(line3 == "allow anyway fish", "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) -- cgit v1.2.1