Как правильно выполнять предпросмотр с помощью LPeg

Чтобы сопоставить строку, начинающуюся с dog, за которой следует cat (но не потребляя cat), это работает:

local lpeg = require 'lpeg'
local str1 = 'dogcat'
local patt1 = lpeg.C(lpeg.P('dog')) * #lpeg.P('cat')
print(lpeg.match(patt1, str1))

Выход: dog

Чтобы сопоставить строку, начинающуюся с dog, за которой следует любая последовательность символов, а затем следует cat (но не потребляя ее), как регулярное выражение с опережением (dog.+?)(?=cat), я попробовал это:

local str2 = 'dog and cat'
local patt2 = lpeg.C(lpeg.P("dog") * lpeg.P(1) ^ 1) * #lpeg.P("cat")
print(lpeg.match(patt2, str2))

Мой ожидаемый результат — dog and, но он возвращает nil.

Если я отбрасываю упреждающую часть (т. е. использую шаблон lpeg.C(lpeg.P("dog") * lpeg.P(1) ^ 1)), он может успешно сопоставить всю строку. Это означает, что часть * lpeg.P(1) ^ 1 правильно соответствует любой последовательности символов, не так ли?

Как это исправить?


person Yu Hao    schedule 19.11.2014    source источник


Ответы (1)


Вам нужно отрицать «кошку» в каждой позиции в предварительном просмотре, которая может соответствовать:

local patt2 = lpeg.C(lpeg.P"dog" * (lpeg.P(1)-lpeg.P"cat") ^ 1) * #lpeg.P"cat"

Думаю, уместно подключить отладчик, над которым я работал (pegdebug), так как он помогает в случаях как это. Вот вывод, который он генерирует для исходного выражения lpeg:

+   Exp 1   "d"
 +  Dog 1   "d"
 =  Dog 1-3 "dog"
 +  Separator   4   " "
 =  Separator   4-11    " and cat"
 +  Cat 12  ""
 -  Cat 12
-   Exp 1

Вы можете видеть, что выражение Separator "съедает" все символы, включая "cat" и ничего не остается для сопоставления с P"cat".

Вывод измененного выражения выглядит следующим образом:

+   Exp 1   "d"
 +  Dog 1   "d"
 =  Dog 1-3 "dog"
 +  Separator   4   " "
 =  Separator   4-8 " and "
 +  Cat 9   "c"
 =  Cat 9-11    "cat"
=   Exp 1-8 "dog and "
/   Dog 1   0   
/   Separator   4   0   
/   Exp 1   1   "dog and "

Вот полный скрипт:

require 'lpeg'
local peg = require 'pegdebug'
local str2 = 'dog and cat'
local patt2 = lpeg.P(peg.trace { "Exp";
  Exp = lpeg.C(lpeg.V"Dog" * lpeg.V"Separator") * #lpeg.V"Cat";
  Cat = lpeg.P("cat");
  Dog = lpeg.P("dog");
  Separator = (lpeg.P(1) - lpeg.P("cat"))^1;
})
print(lpeg.match(patt2, str2))
person Paul Kulchenko    schedule 19.11.2014
comment
Жаль, что pegdebug нельзя использовать с LPeg.re w /o исходная модификация re.lua. - person wqw; 03.01.2016
comment
Это может быть возможно с некоторым исправлением match и другими методами в re. Патчи приветствуются ;). - person Paul Kulchenko; 04.01.2016
comment
Я использую модифицированную версию Lpeg.re с парой расширений к оригиналу, в т.ч. встроенная поддержка трассировки, аргументы %1, конструкции более высокого порядка -> fun'param' и %patt'param', но нет официального репозитория, который мог бы предложить PR с ними. Модифицированная версия очень хорошо работает с вашим pegdebug.lua. Вот суть, в которой вы можете увидеть разницу с оригиналом. - person wqw; 04.01.2016
comment
Интересный; Я думаю, что это должно быть возможно взломать с помощью некоторого исправления доступа mm.P (судя по точке входа, являющейся / mm.P в шаблоне Grammar). - person Paul Kulchenko; 05.01.2016
comment
Да, я сделал это с довольно небольшой модификацией - обратная ссылка на группу захвата T и вызов функции трассировки до переноса результата в mm.P. В compile функция T передается рядом с G флагом, который они уже реализуют. - person wqw; 05.01.2016