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 Александр Машин once stated:
> Dear all,
> 
> is there an LPEG rule to describe a set of characters, in which 
> characters can be in any order, and each can be present only once or not 
> at all?
> 
> Examples, assuming that allowed flags are 'i', 'm', 's':
> * matching:
> ** 'ims',
> ** 'si',
> ** 'i',
> ** '';
> * not matching:
> ** 'iu' (unknown flag),
> ** 'imi' (i repeats twice).

  There is no predefined rule to do what you ask, but you can write such a
rule:

local lpeg = require "lpeg"

local function F(flag)
  return lpeg.Cmt(
           lpeg.P(flag) * lpeg.Carg(1),
           function(subject,position,arg)
             if not arg[flag] then
               arg[flag] = true
               return position
               end
             end
           )
end

local chkflags = (F"i" + F"m" + F"s")^0 * lpeg.P(-1)

print("ims" , chkflags:match("ims", 1, {}))
print("si"  , chkflags:match("si",  1, {}))
print("i"   , chkflags:match("i",   1, {}))
print(""    , chkflags:match("",    1, {}))
print("iu"  , chkflags:match("iu",  1, {}))
print("imi" , chkflags:match("imi", 1, {}))

  For this to work, you'll need to pass in the starting position and a
table.  The table is used to record the flags we've seen before.  This table
is retrieved by the lpeg.Carg() function.  To ensure the check returns a
match failure, I used lpeg.Cmt() to wrap the flag pattern.  I ended the rule
with an lpeg.P(-1) to ensure all the input has been checked---this is to
catch flags that aren't defined.

  I used lpeg.Carg() to make this easier to write and test.  It could be
written without, but it gets a bit more verbose and harder to test:

local flagi
local flagm
local flags

local chkflags = lpeg.Cmt(lpeg.P"i",function(subject,position,capture)
                                      if not flagi then
                                        flagi = true
                                        return position
                                      end
                                    end)
               + lpeg.Cmt(lpeg.P"m", ... ) -- rest left as an exercise
               + lpeg.Cmt(lpeg.P"s", ... ) -- for the reader to finish

print("ims",chkflags:match "ims") flagi = false flagm = false flags = false
print("si" ,chkflags:match "si")  flagi = ... -- left as exercise

  There are probably other ways this could be done.  This was just the first
thing that came to mind.

  -spc (Hope this helps)