Приветствую уважаемые.

«Ехали регулярные выражения, через регулярные выражения, видят регулярные выражения, в регулярных выражениях, регулярные выражения — регулярные выражения, регулярные выражения, регулярные выражения...»

Нет. Это не бред сумасшедшего. Именно так я хотел назвать мой небольшой обзор на тему поиска регулярных выражений с помощью регулярных выражений. Что по сути тоже не меньший бред. Даже не знаю может ли вам такое в жизни пригодиться. Лучше конечно избегать таких ситуаций когда надо искать непонятно что, непонятно где. Ведь что такое регулярное выражение? Да почти всё что угодно!

Вам может показаться странным, но:

.это, например, вполне себе регулярное выражение:.
(Или это тоже может быть (можете даже проверить))
~это~
<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) – при совпадении снова проверять на совпадение. В данном случае мы ищем, например, совпадение (?# / в случае нахождения захватываем до закрывающей скобки.

Итого, на данный момент, мы имеем следующее:

Регулярное выражение для поиска регулярных выражений
/#Ограничитель 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)


  1. ZyXI
    13.04.2016 19:20
    +2

    А где хаб «ненормальное программирование»? Обычно для таких вещей пишут что?то вроде парсера: ищем с помощью той же регулярки начальный символ, потом по символу проходим по регулярному выражению (здесь, скорее всего, будет какой?нибудь конечный автомат). Или не по символу, а регулярками ищем следующий «интересный» символ: т.е. одну из скобок (если ограничитель — скобки), обратную косую черту, ограничитель, на основании найденного принимаем решение: изменить состояние, вернуть найденную регулярку, просто искать дальше.


    1. hopmaster
      13.04.2016 23:40
      +1

      Ай, это скучно)))


    1. hopmaster
      13.04.2016 23:57

      Добавил хаб «ненормальное программирование», просто не знал что есть такой)


  1. mwizard
    13.04.2016 19:27
    -1

    А почему именно регуляркой искать, почему не парсить конечным автоматом? Будет ведь проще даже не на порядок, а на два, и, в отличие от этой регулярки, потом еще и поддерживать можно будет.


    1. khim
      13.04.2016 21:40
      +5

      Я вас разочарую, но нет, не проще. Отловить регулярное выражение конечным автоматом, увы, нельзя. В принципе. Строго говоря и регулярными выражениями нельзя — но математическими. Скорее всего теми, которые в PCRE можно, так как они «шире» (и, соответственно, в конечные автоматы не конвертируются).


      1. zagayevskiy
        13.04.2016 22:05

        Так-то регулярные выражения и ДКА эквивалентны.


        1. chersanya
          13.04.2016 22:36
          +5

          В предыдущем комментарии написано же — то, что обычно называется регулярными выражениями в языках программирования имеет более широкие возможности, чем «математические» регулярные выражения.


  1. Ogoun
    13.04.2016 19:31
    +1

    Данный комментарий вполне себе является полноценным регулярным выражением


    1. mwizard
      13.04.2016 19:33

      Не является, открывающие символы отсутствуют.


      1. Ogoun
        13.04.2016 19:38
        +1

        var reg = new Regex("Данный комментарий вполне себе является полноценным регулярным выражением");
        var match = reg.Match("вфытдтфыплдвфыьпр Данный комментарий вполне себе является полноценным регулярным выражением лвофылповфыповфы джповдфы");
        Console.WriteLine(match.Value);
        Console.ReadKey();
        


        Вывод:


        Так что зависит от инструмента. В моем является.


        1. mwizard
          13.04.2016 19:41
          +1

          Нет, я понимаю, о чем вы. Можно и в Python сделать re.compile("foobar"). Просто автор ищет регулярки /в перловом (?:стиле)/ugi, о чем он и пишет в начале статьи.


    1. hopmaster
      13.04.2016 23:43
      +1

      Ну если настолько усложнить условия, то у меня в первую же секунду было бы готово решение /(.*)/s


      1. ZyXI
        14.04.2016 01:18

        Не?а. (unbalanced — не регулярное выражение в синтаксисе PCRE. Но всё равно будет несколько проще.


  1. bejibx
    13.04.2016 19:57
    +5

    Вот уж действительно — «Однажды у программиста появилась проблема и он решил использовать регулярные выражения чтобы её решить. Теперь у него 2 проблемы.»


    1. hopmaster
      13.04.2016 23:44
      +4

      ну это на первых порах))) потом они только помогают))


  1. valemak
    13.04.2016 20:28
    +4

    Кто нибудь в курсе, почему я автору не могу поставить плюс в карму? Появляется странное сообщение: «Нельзя голосовать за пользователей, у которых нет размещённых публикаций»

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


    1. hopmaster
      13.04.2016 23:51
      +2

      Я кстати даже никому не могу поставить ни плюса ни минуса — кармы 0,0. Может защита какая-нибудь.


      1. khim
        13.04.2016 23:58
        +1

        То, что вы ничего не можете — это нормально: чтобы голосовать нужно иметь довольно много кармы. А вот что вам нельзя поставить ни плюса, ни минуса — это странно.


        1. Antelle
          14.04.2016 00:01
          +1

          А у меня поставился только что.


          1. hopmaster
            14.04.2016 00:33

            уже аж целых три прилетело)


            1. valemak
              14.04.2016 08:54

              Ну всё, процесс пошёл.

              Странно, что нельзя было сразу.


              1. hopmaster
                14.04.2016 09:23

                А редактирование этого поста мне не закроют с течением времени?


                1. valemak
                  14.04.2016 09:50

                  Редактирование публикации — нет.


                  1. hopmaster
                    14.04.2016 10:59

                    класс


                1. PapaBubaDiop
                  14.04.2016 09:58
                  +1

                  Не закроют. В любой момент содержимое поста Вы сможете изменить на противоположное или вообще удалить все символы и даже статью.


                  1. hopmaster
                    14.04.2016 10:59

                    спасибо, плюсик потом поставлю, а то пишут уже что заряда нет)))


  1. dpr
    14.04.2016 05:37
    +2

    Экранизировать — осуществлять экранизацию. Экранизация — интерпретация средствами кино произведений другого вида искусства. Казалось бы, при чем здесь программирование?

    Экранировать — значений несколько. Одно из них «экранирование символов» — замена в тексте управляющих символов на соответствующие текстовые подстановки.

    Извините. Но замена одного слова совершенно другим, это не просто опечатка.


    1. hopmaster
      14.04.2016 05:38

      Всё верно. Спасибо, исправил.


    1. lorc
      14.04.2016 12:44

      о, а вам тоже не нравится когда вместо слова «функциональность» используют математический термин «функционал»?


  1. Aingis
    14.04.2016 16:22

    Я верно же понимаю, что это регулярное выражение прекрасно находит само себя?


    1. 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);
      ?>