[Date Prev][Date Next][Thread Prev][Thread Next]
[Date Index]
[Thread Index]
- Subject: Re: Elegant design for creating error messages in LPEG parser
- From: Sean Conner <sean@...>
- Date: Tue, 2 Apr 2019 16:31:02 -0400
It was thus said that the Great joy mondal once stated:
> What is the idiomatic way to create error messages in LPEG ?
I don't think there *is* an idiomatic way to create error messages.
> To be specific I would like to create error messages for incomplete
> bracket completion.
>
> Example :
>
> > [ 1 2 3 -- ERROR ! MISSING ] AT LINE 20
>
> > ( 1 2 3 -- ERROR ! MISSING ) AT LINE 35
>
> > { 1 2 3 -- ERROR ! MISSING } AT LINE 12
>
> > 1 2 3 ] -- ERROR ! EXTRA ] AT LINE 42
>
> > [ ( 1 2 3 ] -- ERROR ! MISSING ) AT LINE 71
>
> I have figured out that I need to use lpeg.Carg to pass a table to log
> line number but how can a pattern fail in a certain way as to let its
> error be know ?
Okay, so we're parsing stuff like:
[ 1 2 3 4 ]
( 1 2 3 4 )
[ 1 2 ( 3 4 ) 5 6 ]
The idea (I think) is to keep track of not only which bracket we're using,
but the depth as well and report back any errors. The trick to this is to
include code that explicitely checks for the error conditions and record
them. Here's a solution:
local lpeg = require "lpeg"
local WS = lpeg.P" "
+ lpeg.P"\t"
+ lpeg.Cmt(lpeg.P"\n" * lpeg.Carg(1),function(_,position,state)
-- ---------------------------------------------------------
-- Track what line we're on. At match time, bump the line
-- number stored in our state data. We return the position
-- to carry on but not actually capture anything as we don't
-- want to capture the newline.
-- ---------------------------------------------------------
state.line = state.line + 1
return position
end)
-- -----------------------------------------------------------------------
-- For each opening brace (square or paren), track the stating line in a
-- stack. As we close each brace, the stack is popped, but when we expect
-- an ending brace and don't get one, we know the starting line. Again, this
-- is done at match time.
-- ----------------------------------------------------------------------
local SO = lpeg.Cmt(lpeg.P"[" * lpeg.Carg(1),function(_,position,state)
table.insert(state.stack,state.line)
return position,{}
end)
local PO = lpeg.Cmt(lpeg.P"(" * lpeg.Carg(1),function(_,position,state)
table.insert(state.stack,state.line)
return position,{}
end)
-- -------------------------------------------------------------------------
-- For each closing brace, pop the line-tracking stack. If we get the wrong
-- ending brace, record the error in our state table. Multiple errors won't
-- be reported, but it would be easy enough to stack error messages in a
-- stack if need be. Again, all done at match time.
-- -------------------------------------------------------------------------
local SC = lpeg.Cmt(lpeg.P"]" * lpeg.Carg(1),function(_,position,state)
table.remove(state.stack)
return position
end)
+ lpeg.Cmt(lpeg.Carg(1),function(_,position,state)
state.error = string.format("Missing ']' at line %d (start line %d)",state.line,state.stack[#state.stack])
table.remove(state.stack)
return position
end)
local PC = lpeg.Cmt(lpeg.P")" * lpeg.Carg(1),function(_,position,state)
table.remove(state.stack)
return position
end)
+ lpeg.Cmt(lpeg.Carg(1),function(_,position,state)
state.error = string.format("Missing ')' at line %d (start line %d)",state.line,state.stack[#state.stack])
table.remove(state.stack)
return position
end)
local number = lpeg.R"09"^1 / tonumber
local function accumulate(acc,i)
table.insert(acc,i)
return acc
end
local item = lpeg.P {
'item',
item = WS^0 * (lpeg.V"square" + lpeg.V"paren" + number) * WS^0,
square = lpeg.Cf(SO * lpeg.V"item"^0,accumulate) * SC,
paren = lpeg.Cf(PO * lpeg.V"item"^0,accumulate) * PC,
}
local list = lpeg.Cf(lpeg.Ct"" * item^0,accumulate)
local state = { line = 1 , stack = {}}
local test = [[
[ 1 2 3 4 ]
( 1 2 3 4 )
[ 1 2 ( 3 4 ) 5 6 ]
1 2 3 4
]]
local x = list:match(test,1,state)
state = { line = 1 , stack = {}}
test = [[
[1 2 3 4]
[1 2 3
[1 2 3 4]
]]
x = list:match(test,1,state)
After each match, you can check if state.error exists. So for that last
example, it would be:
Missing ']' at line 4 (start line 2)
x may not necessarily be nil, so the best bet is to check for state.error.
Yes, it's kind of annoying to add proper error checking, but it can be
done.
-spc (Yes, there's some repetition that could be removed, but I wanted to
make the example as explicit as possible)