Шаблон LPeg, который сопоставляет строки без последовательных дефисов

#lua #lpeg

#lua #lpeg

Вопрос:

Я пытаюсь написать шаблон LPeg для сопоставления строк, которые:

  • начинается с буквы
  • после этого содержать буквенно-цифровые символы
  • не содержит двух или более последовательных дефисов (например, запрещает test--string )

Для справки, регулярное выражение [a-zA-Z](-?[a-zA-Z0-9])* соответствует тому, что я ищу.

Вот код, с которым я работаю, для справки:

 require "lpeg"
P,R,C = lpeg.P,lpeg.R,lpeg.C

dash  = P"-"
ucase  = R"AZ"
lcase  = R"az"
digit  = R"09"
letter = ucase   lcase
alphanum = letter   digit

str_match = C(letter * ((dash^-1) * alphanum)^0)

strs = {
    "1too",
    "too0",
    "t-t-t",
    "t-t--t",
    "t--t-t",
    "t-1-t",
    "t--t",
    "t-one1",
    "1-1",
    "t-1",
    "t",
    "tt",
    "t1",
    "1",
}

for _,v in ipairs(strs) do
    if lpeg.match(str_match,v) ~= nil then
        print(v," => match!")
    else
        print(v," => no match")
    end
end
  

Однако, к моему большому разочарованию, я получаю следующий вывод:

 1too     => no match
too0     => match!
t-t-t    => match!
t-t--t   => match!
t--t-t   => match!
t-1-t    => match!
t--t     => match!
t-one1   => match!
1-1      => no match
t-1      => match!
t        => match!
tt       => match!
t1       => match!
1        => no match
  

Несмотря на то, что выводит код, t-t--t , t--t-t и t--t не должны совпадать.

Комментарии:

1. Взгляните поближе на ((dash^-1) * alphanum)^0 ! Вы говорите «Группируйте возможность не более одного тире, за которым следует ровно один буквенно-цифровой символ, и все это может повторяться 0 или более раз».. Таким образом, ваш шаблон может просто завершиться ошибкой в начальной букве.

Ответ №1:

В вашем шаблоне letter * ((dash^-1) * alphanum)^0 lpeg попытается сопоставить с префиксом строки. Для случаев, когда вы не ожидали совпадения

t-t—t
t—t-t
t—t

Часть, выделенная жирным шрифтом, — это место, где ваш шаблон успешно соответствует. lpeg.match возвращает последнюю позицию (которая является числом), которую он смог проанализировать с использованием вашего шаблона, если ничего не было захвачено. В приведенных выше 3 случаях фиксируется соответствующая часть, которая объясняет ошибочный вывод, который вы видите.

Если вы просто сопоставляете каждую строку по одному, вы можете изменить свой шаблон, чтобы убедиться, что после синтаксического анализа не осталось символов.

 str_match = C(letter * ((dash^-1) * alphanum)^0) * -1
  

Аналогично используя lpeg.re модуль

 re_pat = re.compile "{ %a ('-'? %w)* } !."
  

Для сопоставления потока или нахождения всех вхождений шаблона в целевой строке, сложите правила грамматики вместе следующим образом

 stream_parse = re.compile
[[
  stream_match  <- ((str_match / skip_nonmatch) delim)* str_match?
  str_match     <- { %a ('-'? %w)* } (amp;delim / !.)
  skip_nonmatch <- !str_match (!delim .)*

  delim         <- %s 
]]
  

Любые совпадения будут зафиксированы и возвращены. Если совпадений нет, вы либо получите ответ nil , либо число, указывающее, где в строке шаблон прекратил синтаксический анализ.

Редактировать: В случаях, когда вам нужно, чтобы синтаксический анализ возвращался nil при отсутствии совпадения, эта настройка грамматики должна сработать

 stream_parse = re.compile
[[
  stream_match  <- (str_match / skip_nonmatch  amp;str_match) 
  str_match     <- { %a ('-'? %w)* } (amp;delim / !.)
  skip_nonmatch <- !str_match (!delim .)* delim

  delim         <- %s 
]]