lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


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)