From a6c4eee4b3f1f48c136c35cd10a74873b1d31715 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Reuh=20Fildadut?= Date: Fri, 18 Sep 2020 23:59:22 +0200 Subject: [PATCH] Show source file in error messages --- anselme.lua | 14 +++++-- interpreter/expression.lua | 2 + interpreter/interpreter.lua | 16 +++---- parser/common.lua | 2 +- parser/expression.lua | 42 +++++++++---------- parser/postparser.lua | 16 +++---- parser/preparser.lua | 38 ++++++++--------- test.ans | 3 ++ test/run.lua | 2 + test/tests/define override function.lua | 4 +- test/tests/define override variable.lua | 4 +- test/tests/function args arity check fail.lua | 4 +- test/tests/function arity conflict.lua | 4 +- test/tests/function scope wrong.lua | 4 +- 14 files changed, 84 insertions(+), 71 deletions(-) diff --git a/anselme.lua b/anselme.lua index e0930fb..5783faf 100644 --- a/anselme.lua +++ b/anselme.lua @@ -7,6 +7,12 @@ local anselme = { } package.loaded[...] = anselme +-- TODO: for type checking functions: +-- pour éliminer totalement les undefined +-- ne type checker/compiler les fonctions que lorsqu'elles sont apppelées, en déterminant leur type à ce moment là selon les arguments donnés +-- (du coup la surcharge ne se fera que selon l'arité. Mais ça sera 100% type checké) +-- PB: les listes ont des types mixés (cf les varargs) + afficher des warnings dans le selecteur de variante lorsqu'un type de retour manque + -- load libs local preparse = require((...):gsub("anselme$", "parser.preparser")) local postparse = require((...):gsub("anselme$", "parser.postparser")) @@ -60,7 +66,7 @@ local interpreter_methods = { local namespace = self:current_namespace() -- replace state with interrupted state local exp, err = expression(expr, self.state.interpreter.global_state, namespace or "") - if not exp then return "error", ("%s; during interrupt %q at line %s"):format(err, expr, line and line.line or 0) end + if not exp then return "error", ("%s; during interrupt %q at %s"):format(err, expr, line and line.source or "unknown") end local r, e = self.vm:run(exp) if not r then return "error", e end self.state = r.state @@ -157,15 +163,15 @@ local vm_mt = { --- load code -- return self in case of success -- returns nil, err in case of error - loadstring = function(self, str, name) - local s, e = preparse(self.state, str, name or "") + loadstring = function(self, str, name, source) + local s, e = preparse(self.state, str, name or "", source) if not s then return s, e end return self end, loadfile = function(self, path, name) local f, e = io.open(path, "r") if not f then return f, e end - local s, err = self:loadstring(f:read("*a"), name or "") + local s, err = self:loadstring(f:read("*a"), name, path) f:close() if not s then return s, err end return self diff --git a/interpreter/expression.lua b/interpreter/expression.lua index c9de076..7b24771 100644 --- a/interpreter/expression.lua +++ b/interpreter/expression.lua @@ -3,6 +3,8 @@ local flush_state, to_lua, from_lua, eval_text local run, run_block +local unpack = table.unpack or unpack + --- evaluate an expression -- returns evaluated value if success -- returns nil, error if error diff --git a/interpreter/interpreter.lua b/interpreter/interpreter.lua index ad6f1bd..b0259d4 100644 --- a/interpreter/interpreter.lua +++ b/interpreter/interpreter.lua @@ -41,21 +41,21 @@ local function run_line(state, line) local skipped = false if line.condition then local v, e = eval(state, line.condition) - if not v then return v, ("%s; at line %s"):format(e, line.line) end + if not v then return v, ("%s; at %s"):format(e, line.source) end skipped = not truthy(v) end if not skipped then -- tag decorator if line.tag then local v, e = eval(state, line.tag) - if not v then return v, ("%s; in tag decorator at line %s"):format(e, line.line) end + if not v then return v, ("%s; in tag decorator at %s"):format(e, line.source) end tags:push(state, v) end -- line types if line.type == "condition" then state.interpreter.last_condition_success = nil local v, e = eval(state, line.expression) - if not v then return v, ("%s; at line %s"):format(e, line.line) end + if not v then return v, ("%s; at %s"):format(e, line.source) end if truthy(v) then state.interpreter.last_condition_success = true v, e = run_block(state, line.child) @@ -65,7 +65,7 @@ local function run_line(state, line) elseif line.type == "else-condition" then if not state.interpreter.last_condition_success then local v, e = eval(state, line.expression) - if not v then return v, ("%s; at line %s"):format(e, line.line) end + if not v then return v, ("%s; at %s"):format(e, line.source) end if truthy(v) then state.interpreter.last_condition_success = true v, e = run_block(state, line.child) @@ -81,7 +81,7 @@ local function run_line(state, line) elseif line.type == "tag" then if line.expression then local v, e = eval(state, line.expression) - if not v then return v, ("%s; at line %s"):format(e, line.line) end + if not v then return v, ("%s; at %s"):format(e, line.source) end tags:push(state, v) end local v, e = run_block(state, line.child) @@ -92,12 +92,12 @@ local function run_line(state, line) local v, e if line.expression then v, e = eval(state, line.expression) - if not v then return v, ("%s; at line %s"):format(e, line.line) end + if not v then return v, ("%s; at %s"):format(e, line.source) end end return v elseif line.type == "text" then local t, er = eval_text(state, line.text) - if not t then return t, ("%s; at line %s"):format(er, line.line) end + if not t then return t, ("%s; at %s"):format(er, line.source) end write_event(state, "text", t) elseif line.type == "flush_events" then while state.interpreter.event_buffer do @@ -122,7 +122,7 @@ local function run_line(state, line) end end elseif line.type ~= "paragraph" then - return nil, ("unknown line type %q; line %s"):format(line.type, line.line) + return nil, ("unknown line type %q; at %s"):format(line.type, line.source) end -- tag decorator if line.tag then diff --git a/parser/common.lua b/parser/common.lua index 77de890..c45612c 100644 --- a/parser/common.lua +++ b/parser/common.lua @@ -5,7 +5,7 @@ local escapeCache = {} local common common = { --- valid identifier pattern - identifier_pattern = "[^%%%/%*%+%-%(%)%!%&%|%=%$%?%>%<%:%{%}%[%]%,]+", + identifier_pattern = "[^%%%/%*%+%-%(%)%!%&%|%=%$%?%>%<%:%{%}%[%]%,%\"]+", --- escape a string to be used as an exact match pattern escape = function(str) if not escapeCache[str] then diff --git a/parser/expression.lua b/parser/expression.lua index 36ff8d6..efe43e6 100644 --- a/parser/expression.lua +++ b/parser/expression.lua @@ -91,27 +91,6 @@ local function expression(s, state, namespace, currentPriority, operatingOn) elseif s:match("^"..identifier_pattern) then local name, r = s:match("^("..identifier_pattern..")(.-)$") name = format_identifier(name, state) - -- functions - local funcs, ffqm = find(state.functions, namespace, name) - if funcs then - local args, explicit_call - if r:match("^%b()") then - explicit_call = true - local content, rem = r:match("^(%b())(.*)$") - content = content:gsub("^%(", ""):gsub("%)$", "") - r = rem - -- get arguments - if content:match("[^%s]") then - local err - args, err = expression(content, state, namespace) - if not args then return args, err end - end - end - -- find compatible variant - local variant, err = find_function_variant(ffqm, state, args, explicit_call) - if not variant then return variant, err end - return expression(r, state, namespace, currentPriority, variant) - end -- variables local var, vfqm = find(state.variables, namespace, name) if var then @@ -133,6 +112,27 @@ local function expression(s, state, namespace, currentPriority, operatingOn) }) end end + -- functions + local funcs, ffqm = find(state.functions, namespace, name) + if funcs then + local args, explicit_call + if r:match("^%b()") then + explicit_call = true + local content, rem = r:match("^(%b())(.*)$") + content = content:gsub("^%(", ""):gsub("%)$", "") + r = rem + -- get arguments + if content:match("[^%s]") then + local err + args, err = expression(content, state, namespace) + if not args then return args, err end + end + end + -- find compatible variant + local variant, err = find_function_variant(ffqm, state, args, explicit_call) + if not variant then return variant, err end + return expression(r, state, namespace, currentPriority, variant) + end return nil, ("unknown identifier %q"):format(name) end -- unops diff --git a/parser/postparser.lua b/parser/postparser.lua index b83aa69..10dd62a 100644 --- a/parser/postparser.lua +++ b/parser/postparser.lua @@ -10,8 +10,8 @@ local function parse(state) if line.condition then if line.condition:match("[^%s]") then local exp, rem = expression(line.condition, state, namespace) - if not exp then return nil, ("%s; at line %s"):format(rem, line.line) end - if rem:match("[^%s]") then return nil, ("expected end of expression before %q in condition decorator; at line %s"):format(rem, line.line) end + if not exp then return nil, ("%s; at %s"):format(rem, line.source) end + if rem:match("[^%s]") then return nil, ("expected end of expression before %q in condition decorator; at %s"):format(rem, line.source) end line.condition = exp else line.condition = nil @@ -20,8 +20,8 @@ local function parse(state) if line.tag then if line.tag:match("[^%s]") then local exp, rem = expression(line.tag, state, namespace) - if not exp then return nil, ("%s; at line %s"):format(rem, line.line) end - if rem:match("[^%s]") then return nil, ("expected end of expression before %q in condition decorator; at line %s"):format(rem, line.line) end + if not exp then return nil, ("%s; at %s"):format(rem, line.source) end + if rem:match("[^%s]") then return nil, ("expected end of expression before %q in condition decorator; at %s"):format(rem, line.source) end line.tag = exp else line.tag = nil @@ -31,8 +31,8 @@ local function parse(state) if line.expression then if line.expression:match("[^%s]") then local exp, rem = expression(line.expression, state, namespace) - if not exp then return nil, ("%s; at line %s"):format(rem, line.line) end - if rem:match("[^%s]") then return nil, ("expected end of expression before %q; at line %s"):format(rem, line.line) end + if not exp then return nil, ("%s; at %s"):format(rem, line.source) end + if rem:match("[^%s]") then return nil, ("expected end of expression before %q; at %s"):format(rem, line.source) end line.expression = exp else line.expression = nil @@ -45,7 +45,7 @@ local function parse(state) if not variant.return_type then variant.return_type = return_type elseif variant.return_type ~= return_type then - return nil, ("trying to return a %s in a function that returns a %s; at line %s"):format(return_type, variant.return_type, line.line) + return nil, ("trying to return a %s in a function that returns a %s; at %s"):format(return_type, variant.return_type, line.source) end end end @@ -53,7 +53,7 @@ local function parse(state) -- text if line.text then local txt, err = parse_text(line.text, state, namespace) - if err then return nil, ("%s; at line %s"):format(err, line.line) end + if err then return nil, ("%s; at %s"):format(err, line.source) end line.text = txt end end diff --git a/parser/preparser.lua b/parser/preparser.lua index 02a175c..3cd5b5a 100644 --- a/parser/preparser.lua +++ b/parser/preparser.lua @@ -7,7 +7,7 @@ local eval local function parse_line(line, state, namespace) local l = line.content local r = { - line = line.line + source = line.source } -- comment if l:match("^%(") then @@ -113,7 +113,7 @@ local function parse_line(line, state, namespace) -- define function and variables r.namespace = fqm.."." r.name = fqm - if state.variables[fqm] then return nil, ("trying to define %s %s, but a variable with the same name exists; at line %s"):format(r.type, fqm, line.line) end + if state.variables[fqm] then return nil, ("trying to define %s %s, but a variable with the same name exists; at %s"):format(r.type, fqm, line.source) end r.variant = { arity = arity, types = {}, @@ -144,13 +144,13 @@ local function parse_line(line, state, namespace) min, max = variant.arity, r.variant.arity end if min == vmin and max == vmax then - return nil, ("trying to define %s %s with arity [%s;%s], but another function with the arity exist; at line %s"):format(r.type, fqm, min, max, line.line) + return nil, ("trying to define %s %s with arity [%s;%s], but another function with the arity exist; at %s"):format(r.type, fqm, min, max, line.source) end end -- add table.insert(state.functions[fqm], r.variant) end - -- set type check information + -- define args and set type check information for i, param in ipairs(r.params) do if not state.variables[param] then state.variables[param] = { @@ -166,9 +166,9 @@ local function parse_line(line, state, namespace) r.type = "definition" r.remove_from_block_ast = true local exp, rem = expression(l:match("^:(.*)$"), state, namespace) -- expression parsing is done directly to get type information - if not exp then return nil, ("%s; at line %s"):format(rem, line.line) end + if not exp then return nil, ("%s; at %s"):format(rem, line.source) end local fqm = ("%s%s"):format(namespace, format_identifier(rem, state)) - if state.functions[fqm] then return nil, ("trying to define variable %s, but a function with the same name exists; at line %s"):format(fqm, line.line) end + if state.functions[fqm] then return nil, ("trying to define variable %s, but a function with the same name exists; at %s"):format(fqm, line.source) end if not state.variables[fqm] or state.variables[fqm].type == "undefined argument" then local v, e = eval(state, exp) if not v then return v, e end @@ -179,7 +179,7 @@ local function parse_line(line, state, namespace) end state.variables[fqm] = v elseif state.variables[fqm].type ~= exp.type then - return nil, ("trying to define variable %s of type %s but a it is already defined with type %s; at line %s"):format(fqm, exp.type, state.variables[fqm].type, line.line) + return nil, ("trying to define variable %s of type %s but a it is already defined with type %s; at %s"):format(fqm, exp.type, state.variables[fqm].type, line.source) end -- tag elseif l:match("^%#") then @@ -200,7 +200,7 @@ local function parse_line(line, state, namespace) else r.type = "flush_events" end - if not r.type then return nil, ("unknown line %s type"):format(line.line) end + if not r.type then return nil, ("unknown line %s type"):format(line.source) end return r end @@ -224,7 +224,7 @@ local function parse_block(indented, state, namespace, parent_function, last_eve if ast.type == "flush" then last_event = nil end if ast.push_event then if last_event and ast.push_event ~= last_event then - table.insert(block, { line = l.line, type = "flush_events" }) + table.insert(block, { source = l.source, type = "flush_events" }) end last_event = ast.push_event end @@ -232,9 +232,9 @@ local function parse_block(indented, state, namespace, parent_function, last_eve ast.parent_position = #block+1 if ast.replace_with then if indented[i+1].content then - table.insert(indented, i+1, { content = ast.replace_with, line = l.line }) + table.insert(indented, i+1, { content = ast.replace_with, source = l.source }) else - table.insert(indented, i+2, { content = ast.replace_with, line = l.line }) -- if line has children + table.insert(indented, i+2, { content = ast.replace_with, source = l.source }) -- if line has children end else table.insert(block, ast) @@ -247,7 +247,7 @@ local function parse_block(indented, state, namespace, parent_function, last_eve -- indented (ignore block comments) elseif lastLine.type ~= "comment" then if not lastLine.child then - return nil, ("line %s (%s) can't have children"):format(lastLine.line, lastLine.type) + return nil, ("line %s (%s) can't have children"):format(lastLine.source, lastLine.type) else local r, e = parse_block(l, state, lastLine.namespace or namespace, lastLine.type == "function" and lastLine or parent_function, last_event) if not r then return r, e end @@ -262,7 +262,7 @@ end --- returns the nested list of lines {content="", line=1}, grouped by indentation -- multiple empty lines are merged -- * list, last line -local function parse_indent(lines, i, indentLevel, insert_empty_line) +local function parse_indent(lines, source, i, indentLevel, insert_empty_line) i = i or 1 indentLevel = indentLevel or 0 local indented = {} @@ -271,13 +271,13 @@ local function parse_indent(lines, i, indentLevel, insert_empty_line) local indent, line = lines[i]:match("^(%s*)(.*)$") if #indent == indentLevel then if insert_empty_line then - table.insert(indented, { content = "", line = insert_empty_line }) + table.insert(indented, { content = "", source = ("%s:%s"):format(source, insert_empty_line) }) insert_empty_line = false end - table.insert(indented, { content = line, line = i }) + table.insert(indented, { content = line, source = ("%s:%s"):format(source, i) }) elseif #indent > indentLevel then local t - t, i = parse_indent(lines, i, #indent, insert_empty_line) + t, i = parse_indent(lines, source, i, #indent, insert_empty_line) table.insert(indented, t) else return indented, i-1 @@ -303,17 +303,17 @@ end -- (wait for other files to be parsed before doing this with postparse) -- * state: in case of success -- * nil, err: in case of error -local function parse(state, s, name) +local function parse(state, s, name, source) -- parse lines local lines = parse_lines(s) - local indented = parse_indent(lines) + local indented = parse_indent(lines, source or name) -- wrap in named function if neccessary if name ~= "" then if not name:match("^"..identifier_pattern.."$") then return nil, ("invalid function name %q"):format(name) end indented = { - { content = "$ "..name, line = 0 }, + { content = "$ "..name, source = ("%s:%s"):format(source or name, 0) }, indented } end diff --git a/test.ans b/test.ans index 6a8300d..217d1b5 100644 --- a/test.ans +++ b/test.ans @@ -1,3 +1,6 @@ $ f(a, b) $ f(x) + kk {x+1} + +~ f(6) \ No newline at end of file diff --git a/test/run.lua b/test/run.lua index cae72c2..d683df5 100644 --- a/test/run.lua +++ b/test/run.lua @@ -182,6 +182,7 @@ else print(inspect(result)) print("is not equal to") print(inspect(output)) + print("") end else success = success + 1 @@ -192,6 +193,7 @@ else print(e) print("result was:") print(inspect(result)) + print("") end end end diff --git a/test/tests/define override function.lua b/test/tests/define override function.lua index fcf37f1..5550f10 100644 --- a/test/tests/define override function.lua +++ b/test/tests/define override function.lua @@ -1,6 +1,6 @@ local _={} -_[1]={"error","trying to define variable define override function.a, but a function with the same name exists; at line 3"} +_[1]={"error","trying to define variable define override function.a, but a function with the same name exists; at test/tests/define override function.ans:3"} return {_[1]} --[[ -{ "error", "trying to define variable define override function.a, but a function with the same name exists; at line 3" } +{ "error", "trying to define variable define override function.a, but a function with the same name exists; at test/tests/define override function.ans:3" } ]]-- \ No newline at end of file diff --git a/test/tests/define override variable.lua b/test/tests/define override variable.lua index d9347ae..3aa0713 100644 --- a/test/tests/define override variable.lua +++ b/test/tests/define override variable.lua @@ -1,6 +1,6 @@ local _={} -_[1]={"error","trying to define function define override variable.a, but a variable with the same name exists; at line 3"} +_[1]={"error","trying to define function define override variable.a, but a variable with the same name exists; at test/tests/define override variable.ans:3"} return {_[1]} --[[ -{ "error", "trying to define function define override variable.a, but a variable with the same name exists; at line 3" } +{ "error", "trying to define function define override variable.a, but a variable with the same name exists; at test/tests/define override variable.ans:3" } ]]-- \ No newline at end of file diff --git a/test/tests/function args arity check fail.lua b/test/tests/function args arity check fail.lua index 79de81a..5623842 100644 --- a/test/tests/function args arity check fail.lua +++ b/test/tests/function args arity check fail.lua @@ -1,6 +1,6 @@ local _={} -_[1]={"error","function \"function args arity check fail.f\" expected 2 arguments but received 1; at line 4"} +_[1]={"error","function \"function args arity check fail.f\" expected 2 arguments but received 1; at test/tests/function args arity check fail.ans:4"} return {_[1]} --[[ -{ "error", 'function "function args arity check fail.f" expected 2 arguments but received 1; at line 4' } +{ "error", 'function "function args arity check fail.f" expected 2 arguments but received 1; at test/tests/function args arity check fail.ans:4' } ]]-- \ No newline at end of file diff --git a/test/tests/function arity conflict.lua b/test/tests/function arity conflict.lua index b3d9ab0..5a130e5 100644 --- a/test/tests/function arity conflict.lua +++ b/test/tests/function arity conflict.lua @@ -1,6 +1,6 @@ local _={} -_[1]={"error","trying to define function function arity conflict.f with arity [2;2], but another function with the arity exist; at line 5"} +_[1]={"error","trying to define function function arity conflict.f with arity [2;2], but another function with the arity exist; at test/tests/function arity conflict.ans:5"} return {_[1]} --[[ -{ "error", "trying to define function function arity conflict.f with arity [2;2], but another function with the arity exist; at line 5" } +{ "error", "trying to define function function arity conflict.f with arity [2;2], but another function with the arity exist; at test/tests/function arity conflict.ans:5" } ]]-- \ No newline at end of file diff --git a/test/tests/function scope wrong.lua b/test/tests/function scope wrong.lua index e51ad16..75f005e 100644 --- a/test/tests/function scope wrong.lua +++ b/test/tests/function scope wrong.lua @@ -1,6 +1,6 @@ local _={} -_[1]={"error","unknown identifier \"b\"; at line 4"} +_[1]={"error","unknown identifier \"b\"; at test/tests/function scope wrong.ans:4"} return {_[1]} --[[ -{ "error", 'unknown identifier "b"; at line 4' } +{ "error", 'unknown identifier "b"; at test/tests/function scope wrong.ans:4' } ]]-- \ No newline at end of file