«Ехали регулярные выражения, через регулярные выражения, видят регулярные выражения, в регулярных выражениях, регулярные выражения — регулярные выражения, регулярные выражения, регулярные выражения...»
Нет. Это не бред сумасшедшего. Именно так я хотел назвать мой небольшой обзор на тему поиска регулярных выражений с помощью регулярных выражений. Что по сути тоже не меньший бред. Даже не знаю может ли вам такое в жизни пригодиться. Лучше конечно избегать таких ситуаций когда надо искать непонятно что, непонятно где. Ведь что такое регулярное выражение? Да почти всё что угодно!
Вам может показаться странным, но:
.это, например, вполне себе регулярное выражение:.
(Или это тоже может быть (можете даже проверить))
~это~
<script src="И это - регулярка, вполне рабочая и может быть даже кому нибудь очень необходимая.js">
Но давайте без паники, попробуем приступить, может что и выйдет приличное.
Регулярное выражение – это нечто в ограничителях и возможно с модификаторами в конце. К примеру, что-нибудь такое:
/регулярное выражение/isux
Ограничителем в регулярном выражении PCRE может являться не-цифра, не-буква, не-пробельный символ, не обратный слеш. [^\s\w\\] К тому же этот символ одновременно должен быть из ASCII: [[:ascii:]], иначе можно поймать всякие интересности ?типа? ?таких? как …эти…
Не надо меня только спрашивать, кому такое может в голову прийти.
Существуют также парные ограничители: ()[]{}<>. Т.е. первым ограничителем не может являться закрывающий парный ограничитель: [^\s\w\\\)\]\}\>]
Итого имеем условие поиска для первого ограничителя:
(?=[[:ascii:]]) [^\s\w\\\)\]\}\>]
К сожалению, мы не сможет проверить, какой именно символ попал к нам в качестве первого ограничителя, но мы можем ловить в скобки отдельно парные <,(,[,{ символы:
(\<) |
(\() |
(\[) |
(\{) |
((?=[[:ascii:]])[^\s\w\\\)\]\}\>])
)
#А потом поставить к нему подходящий закрывающий ограничитель:
(.*)
(?(2)\>)
(?(3)\))
(?(4)\])
(?(5)\})
(?(6)\6)
#Ну и можно залакировать всё это дело модификаторами:
([mixXsuUAJ]*)
/xs
Данное регулярное выражение найдёт и распотрошит на: ([1] => ограничитель, [7] => шаблон, [8] =>модификаторы) только одно регулярное выражение. Т.к. используется жадный квантификатор .* который кушает всё до конца, а потом только бэктрекает до ближайшего совпадения. При большом желании оно может распотрошить само себя.
Настоящая жесть начинается тогда, когда нам нужно найти и распотрошить не одно регулярное выражение в одном тексте.
Во-первых, нужно использовать ленивый квантификатор (.*?)
Во-вторых, нужно искать совпадение с неэкранированным ограничителем, который, в свою очередь, может оказаться волею судеб закомментированным. А как вам вариант ограничителя с экранированным обратным слешем перед ним? / \\ \/ \\/is
Добро пожаловать в ад:
((?#ignore comments like this in the regular expression)
(?(6)
(?(?=
(?:(?!\6).|(?<=\\)\6)*[^\\][\(][\?][\#])
(?:(?!\6).|(?<=\\)\6)*[^\\][\(][\?][\#]
[^\)]*
(?-1)
))
.*?)
Немного поясню данный код:
1) Мы не можем искать [^\6], т.к. в символьном классе наш указатель теряет свою волшебную силу. Но благодаря опережающей негативной проверке мы можем проверить любой символ: [^\6]* => ((?!\6).)*
2) (?(?=строка)строка) – может показаться бессмысленным, но это необходимо в случаях когда нужно что-то добавить.
3) (?-1) – при совпадении снова проверять на совпадение. В данном случае мы ищем, например, совпадение (?# / в случае нахождения захватываем до закрывающей скобки.
Итого, на данный момент, мы имеем следующее:
((\<)|(\()|(\[)|(\{)|
((?=[[:ascii:]])[^\s\w\\\)\]\}\>]))
#Шаблон
((?#ignore comments like this in the regular expression)
(?(6)
(?(?=(?:(?!\6).|(?<=\\)\6)*[^\\][\(][\?][\#])
(?:(?!\6).|(?<=\\)\6)*[^\\][\(][\?][\#]
[^\)]*(?-1)))
.*?)
#Ограничитель 2
#экранированные обратные слеши +
#неэкранированный ограничитель
(?(2)(?<!(?<!(?<!(?<!(?<!(?<!(?<!(?<!\\)\\)\\)\\)\\)\\)\\)\\)\>)
(?(3)(?<!(?<!(?<!(?<!(?<!(?<!(?<!(?<!\\)\\)\\)\\)\\)\\)\\)\\)\))
(?(4)(?<!(?<!(?<!(?<!(?<!(?<!(?<!(?<!\\)\\)\\)\\)\\)\\)\\)\\)\])
(?(5)(?<!(?<!(?<!(?<!(?<!(?<!(?<!(?<!\\)\\)\\)\\)\\)\\)\\)\\)\})
(?(6)(?<!(?<!(?<!(?<!(?<!(?<!(?<!(?<!\\)\\)\\)\\)\\)\\)\\)\\)\6)
#Модификаторы шаблонов
#PHP [mixXsuUAJ] JavaScript [gmi] python [gmixsu]
((?(6)(?:[mixXsuUAJ]*)|(?(?=.*?[mixXsuUAJ]+)[mixXsuUAJ]+)))/xs
Энтузиазм у меня ещё не угас, но навалилась работа. Если у кого есть желание — можно помучиться.
Текущие цели и задачи:
1) Еще не решена проблема с ограничителями-скобками. К сожалению для нас скобки можно не экранировать внутри:
((регулярное)(выражение))isu
2) Нужно добавить игнорирование ограничителей между # и переводом строки
3) Закрывающие парные ограничители в комментариях
4) Красиво решить проблему с экранированными \ перед последним ограничителем.
Ссылка на последний вариант, для желающих помочь довести дело до конца
Спасибо за внимательное внимание компьютерные маньяки.
Комментарии (31)
mwizard
13.04.2016 19:27-1А почему именно регуляркой искать, почему не парсить конечным автоматом? Будет ведь проще даже не на порядок, а на два, и, в отличие от этой регулярки, потом еще и поддерживать можно будет.
khim
13.04.2016 21:40+5Я вас разочарую, но нет, не проще. Отловить регулярное выражение конечным автоматом, увы, нельзя. В принципе. Строго говоря и регулярными выражениями нельзя — но математическими. Скорее всего теми, которые в PCRE можно, так как они «шире» (и, соответственно, в конечные автоматы не конвертируются).
zagayevskiy
13.04.2016 22:05Так-то регулярные выражения и ДКА эквивалентны.
chersanya
13.04.2016 22:36+5В предыдущем комментарии написано же — то, что обычно называется регулярными выражениями в языках программирования имеет более широкие возможности, чем «математические» регулярные выражения.
Ogoun
13.04.2016 19:31+1Данный комментарий вполне себе является полноценным регулярным выражением
mwizard
13.04.2016 19:33Не является, открывающие символы отсутствуют.
Ogoun
13.04.2016 19:38+1var reg = new Regex("Данный комментарий вполне себе является полноценным регулярным выражением"); var match = reg.Match("вфытдтфыплдвфыьпр Данный комментарий вполне себе является полноценным регулярным выражением лвофылповфыповфы джповдфы"); Console.WriteLine(match.Value); Console.ReadKey();
Вывод:
Так что зависит от инструмента. В моем является.mwizard
13.04.2016 19:41+1Нет, я понимаю, о чем вы. Можно и в Python сделать
re.compile("foobar")
. Просто автор ищет регулярки/в перловом (?:стиле)/ugi
, о чем он и пишет в начале статьи.
valemak
13.04.2016 20:28+4Кто нибудь в курсе, почему я автору не могу поставить плюс в карму? Появляется странное сообщение: «Нельзя голосовать за пользователей, у которых нет размещённых публикаций»
(Вообще часто наблюдаю такой глюк для авторов, только что опубликовавшихся из песочницы и которым пытаюсь плюсануть в карму. Вроде как одна публикацияя уже есть, но сообщается что нет...)hopmaster
13.04.2016 23:51+2Я кстати даже никому не могу поставить ни плюса ни минуса — кармы 0,0. Может защита какая-нибудь.
khim
13.04.2016 23:58+1То, что вы ничего не можете — это нормально: чтобы голосовать нужно иметь довольно много кармы. А вот что вам нельзя поставить ни плюса, ни минуса — это странно.
Antelle
14.04.2016 00:01+1А у меня поставился только что.
hopmaster
14.04.2016 00:33уже аж целых три прилетело)
valemak
14.04.2016 08:54Ну всё, процесс пошёл.
Странно, что нельзя было сразу.hopmaster
14.04.2016 09:23А редактирование этого поста мне не закроют с течением времени?
PapaBubaDiop
14.04.2016 09:58+1Не закроют. В любой момент содержимое поста Вы сможете изменить на противоположное или вообще удалить все символы и даже статью.
dpr
14.04.2016 05:37+2Экранизировать — осуществлять экранизацию. Экранизация — интерпретация средствами кино произведений другого вида искусства. Казалось бы, при чем здесь программирование?
Экранировать — значений несколько. Одно из них «экранирование символов» — замена в тексте управляющих символов на соответствующие текстовые подстановки.
Извините. Но замена одного слова совершенно другим, это не просто опечатка.lorc
14.04.2016 12:44о, а вам тоже не нравится когда вместо слова «функциональность» используют математический термин «функционал»?
Aingis
14.04.2016 16:22Я верно же понимаю, что это регулярное выражение прекрасно находит само себя?
hopmaster
15.04.2016 03:18Первая найдёт, вторая теоретически — да: https://regex101.com/r/bZ4bR2/17 (34679шага), а практически, могут возникнуть всякие неприятности при стандартных настройках, например 101 ошибка: ERR_CONNECTION_RESET.
php<?php $re = <<<REG /#©Yuri Khmelenko #Ограничитель 1 ((\\<)|(\\()|(\\[)|(\\{)| ((?=[[:ascii:]])[^\\s\\w\\\\\\)\\]\\}\\>])) #Шаблон ((?#ignore comments like this in the regular expression) (?(6) (?(?=(?:(?!\\6).|(?<=\\\\)\\6)*[^\\\\][\\(][\\?][\\#]) (?:(?!\\6).|(?<=\\\\)\\6)*[^\\\\][\\(][\\?][\\#] [^\\)]*(?-1))) .*?) #Ограничитель 2 #экранизированные обратные слеши + #неэкранизированный ограничитель (?(2)(?<!(?<!(?<!(?<!(?<!(?<!(?<!(?<!\\\\)\\\\)\\\\)\\\\)\\\\)\\\\)\\\\)\\\\)\\>) (?(3)(?<!(?<!(?<!(?<!(?<!(?<!(?<!(?<!\\\\)\\\\)\\\\)\\\\)\\\\)\\\\)\\\\)\\\\)\\)) (?(4)(?<!(?<!(?<!(?<!(?<!(?<!(?<!(?<!\\\\)\\\\)\\\\)\\\\)\\\\)\\\\)\\\\)\\\\)\\]) (?(5)(?<!(?<!(?<!(?<!(?<!(?<!(?<!(?<!\\\\)\\\\)\\\\)\\\\)\\\\)\\\\)\\\\)\\\\)\\}) (?(6)(?<!(?<!(?<!(?<!(?<!(?<!(?<!(?<!\\\\)\\\\)\\\\)\\\\)\\\\)\\\\)\\\\)\\\\)\\6) #Модификаторы шаблонов #PHP [mixXsuUAJ] JavaScript [gmi] python [gmixsu] ((?(6)(?:[mixXsuUAJ]*)|(?(?=.*?[mixXsuUAJ]+)[mixXsuUAJ]+)))/xs REG; preg_match_all($re, $re, $matches); var_dump($matches); ?>
ZyXI
А где хаб «ненормальное программирование»? Обычно для таких вещей пишут что?то вроде парсера: ищем с помощью той же регулярки начальный символ, потом по символу проходим по регулярному выражению (здесь, скорее всего, будет какой?нибудь конечный автомат). Или не по символу, а регулярками ищем следующий «интересный» символ: т.е. одну из скобок (если ограничитель — скобки), обратную косую черту, ограничитель, на основании найденного принимаем решение: изменить состояние, вернуть найденную регулярку, просто искать дальше.
hopmaster
Ай, это скучно)))
hopmaster
Добавил хаб «ненормальное программирование», просто не знал что есть такой)