如何用 LPeg 发出解析错误信号?

我正在编写基于 LPeg 的解析器。如何使解析错误返回 nil, errmsg

我知道可以使用 error(),但据我所知,它创建的只是一个普通的错误,而不是 nil, errmsg

代码很长,但相关部分如下:

local eof = lpeg.P(-1)
local nl = (lpeg.P "\r")^-1 * lpeg.P "\n" + lpeg.P "\\n" + eof -- \r 用于Windows兼容
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 -- 注释被视为空格
local backslashEscaped
= lpeg.P("\\ ") / " " -- 转义空格
+ lpeg.P("\\\\") / "\\" -- 转义转义字符
+ lpeg.P("\\#") / "#"
+ lpeg.P("\\>") / ">"
+ lpeg.P("\\`") / "`"
+ lpeg.P("\\n") -- \\n 换行符被视为反斜杠转义
+ lpeg.P("\\") * lpeg.P(function(_, i)
    error("Unknown backslash escape at position " .. i) -- 这个 error() 是我想去掉的。
  end)
local Line = lpeg.C((wsc + (backslashEscaped + 1 - nl))^0) / function(x) return x end * nl * lpeg.Cp()

当出现无效转义时,我希望Line:match(...)返回nil,errmsg

点赞
用户6788541
用户6788541

LPeg本身没有提供特定的函数帮助您进行错误报告。解决您的问题的快速方法是以这种方式进行匹配受保护的调用(pcall)

local function parse(text)
  local ok, result = pcall(function () return Line:match(text) end)
  if ok then
    return result
  else
     -- `result`将包含所抛出的错误。如果它是一个字符串,
     --Lua将增加额外的信息(文件名和行号)。
     --如果您不想这样做,则应抛出类似于`{msg =“error”}`的表
     --并使用`result.msg`访问消息
    return nil, result
  end
end

然而,这也会捕获任何其他错误,这可能不是您想要的。更好的解决方案是使用LPegLabel。LPegLabel是LPeg的扩展,它添加了对标记失败的支持。只需将require“lpeg”替换为require“lpeglabel”,然后使用lpeg.T(L)来抛出标签,其中L是从1到255的整数(0用于常规PEG故障)。

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

现在,如果有标签被抛出,Line:match(...)将返回nil,label,suffixsuffix是剩余未处理的输入,您可以使用它通过其长度计算出错误位置)。有了这个,您可以根据标签打印出适当的错误消息。对于更复杂的语法,您可能需要更系统地映射错误标签和消息的方法。请检查LPegLabel库自述中的文档,以查看如何执行此操作的示例。

LPegLabel还允许您通过标记选择(labeled choice)在语法中捕获标签;这对于实现诸如错误恢复之类的事情非常有用。有关标记故障和示例的更多信息,请查阅文档。

2016-09-03 05:30:41