2

I'm writing an LPeg-based parser. How can I make it so a parsing error returns nil, errmsg?

I know I can use error(), but as far as I know that creates a normal error, not nil, errmsg.

The code is pretty long, but the relevant part is this:

local eof = lpeg.P(-1)
local nl = (lpeg.P "\r")^-1 * lpeg.P "\n" + lpeg.P "\\n" + eof -- \r for winblows compat
local nlnoeof = (lpeg.P "\r")^-1 * lpeg.P "\n" + lpeg.P "\\n"
local ws = lpeg.S(" \t")
local inlineComment = lpeg.P("`") * (1 - (lpeg.S("`") + nl * nl)) ^ 0 * lpeg.P("`")
local wsc = ws + inlineComment -- comments count as whitespace
local backslashEscaped
= lpeg.P("\\ ") / " " -- escaped spaces
+ lpeg.P("\\\\") / "\\" -- escaped escape character
+ lpeg.P("\\#") / "#"
+ lpeg.P("\\>") / ">"
+ lpeg.P("\\`") / "`"
+ lpeg.P("\\n") -- \\n newlines count as backslash escaped
+ lpeg.P("\\") * lpeg.P(function(_, i)
    error("Unknown backslash escape at position " .. i) -- this error() is what I wanna get rid of.
  end)
local Line = lpeg.C((wsc + (backslashEscaped + 1 - nl))^0) / function(x) return x end * nl * lpeg.Cp()

I want Line:match(...) to return nil, errmsg when there's an invalid escape.

SoniEx2
  • 1,864
  • 3
  • 27
  • 40
  • what are you trying to achieve? Is it minima example? Did you try to `return`? – Jakuje Jul 29 '16 at 20:04
  • the `error()` produces an error instead of `nil, errmsg`. I want `Line:match()` and `Data:match()` to return `nil, errmsg` if there's an error with backslash escapes. – SoniEx2 Jul 29 '16 at 20:44
  • @Jakuje Actually, I only need `Line:match()` to return `nil, errmsg` when there's an invalid escape. – SoniEx2 Jul 29 '16 at 22:59

1 Answers1

0

LPeg itself doesn't provide specific functions to help you with error reporting. A quick fix to your problem would be to make a protected call (pcall) to match like this:

local function parse(text)
  local ok, result = pcall(function () return Line:match(text) end)
  if ok then
    return result
  else
    -- `result` will contain the error thrown. If it is a string
    -- Lua will add additional information to it (filename and line number).
    -- If you do not want this, throw a table instead like `{ msg = "error" }`
    -- and access the message using `result.msg`
    return nil, result
  end
end

However, this will also catch any other error, which you probably don't want. A better solution would be to use LPegLabel instead. LPegLabel is an extension of LPeg that adds support for labeled failures. Just replace require"lpeg" with require"lpeglabel" and then use lpeg.T(L) to throw labels where L is an integer from 1-255 (0 is used for regular PEG failures).

local unknown_escape = 1
local backslashEscaped = ... + lpeg.P("\\") * lpeg.T(unknown_escape)

Now Line:match(...) will return nil, label, suffix if there is a label thrown (suffix is the remaining unprocessed input, which you can use to compute for the error position via its length). With this, you can print out the appropriate error message based on the label. For more complex grammars, you would probably want a more systematic way of mapping the error labels and messages. Please check the documentation found in the readme of the LPegLabel repository to see examples of how one may do so.

LPegLabel also allows you to catch the labels in the grammar by the way (via labeled choice); this is useful for implementing things like error recovery. For more information on labeled failures and examples, please check the documentation.

  • Is it still LPeg if it's not LPeg? I'm pretty sure I can't use LPegLabel in things that only come with standard LPeg built-in. – SoniEx2 Sep 04 '16 at 02:10
  • @SoniEx2 LPegLabel isn't LPeg but it can be used as a drop-in replacement for it. The first part of my answer (using `pcall`) is just standard Lua so you can use that with LPeg. – undecidabot Sep 04 '16 at 13:40
  • Get Luvit to bundle LPegLabel and I might use the second part of this answer. (Can't you rewrite LPegLabel to bolt onto plain LPeg?) – SoniEx2 Sep 04 '16 at 13:45
  • @SoniEx2 I suppose it's not impossible to rewrite LPegLabel to bolt onto plain LPeg, but it would be awfully inefficient (not to mention kludgy). Given that LPegLabel is a project under LabLua, I am hoping that LPegLabel will be merged into LPeg one day. – undecidabot Sep 05 '16 at 04:07