diff --git a/10-regular-expressions-javascript/10-regexp-backreferences/article.md b/10-regular-expressions-javascript/10-regexp-backreferences/article.md index 2fd40485..46c9e21b 100644 --- a/10-regular-expressions-javascript/10-regexp-backreferences/article.md +++ b/10-regular-expressions-javascript/10-regexp-backreferences/article.md @@ -5,18 +5,22 @@ На скобочные группы можно ссылаться как в самом паттерне, так и в строке замены. [cut] -## Группа в замене +## Группа в строке замены -Ссылки в строке замены имеют вид `$n`, где `n` -- это номер скобочной группы. Вместо `$n` подставляется содержимое соответствующей скобки: +Ссылки в строке замены имеют вид `$n`, где `n` -- это номер скобочной группы. + +Вместо `$n` подставляется содержимое соответствующей скобки: ```js //+ run var name = "Александр Пушкин"; -name = name.replace(/([а-яё]+) ([а-яё]+)/i, "$2, $1"); +name = name.replace(/([а-яё]+) ([а-яё]+)/i, *!*"$2, $1"*/!*); alert( name ); // Пушкин, Александр ``` +В примере выше вместо $2 подставляется второе найденное слово, а вместо $1 -- первое. + ## Группа в шаблоне Выше был пример использования содержимого групп в строке замены. Это удобно, когда нужно реорганизовать содержимое или создать новое с использованием старого. @@ -27,11 +31,11 @@ alert( name ); // Пушкин, Александр Как такие строки искать? -Можно в регэкспе предусмотреть произвольные кавычки: `['"](.*?)['"]`. Такой регэксп найдёт строки вида "...", '...', но он даст неверный ответ в случае, если одна кавычка ненароком оказалась внутри другой, как например в строке "She's the one": +Можно в регэкспе предусмотреть произвольные кавычки: `['"](.*?)['"]`. Такой регэксп найдёт строки вида "...", '...', но он даст неверный ответ в случае, если одна кавычка ненароком оказалась внутри другой, как например в строке "She's the one!": ```js //+ run -str = "He said:\"She's the one\"."; +str = "He said: \"She's the one!\"."; reg = /['"](.*?)['"]/g; @@ -45,14 +49,14 @@ alert( str.match(reg) ); // "She' ```js //+ run -str = "He said:\"She's the one\"."; +str = "He said: \"She's the one!\"."; reg = /(['"])(.*?)\1/g; -alert( str.match(reg) ); // "She's the one" +alert( str.match(reg) ); // "She's the one!" ``` -Теперь работает верно! +Теперь работает верно! Движок регулярных выражений, найдя первое скобочное выражение -- кавычку (['"]), запоминает его и далее \1 означает "найти то же самое, что в первой скобочной группе". Обратим внимание на два нюанса: diff --git a/10-regular-expressions-javascript/11-regexp-infinite-backtracking-problem/article.md b/10-regular-expressions-javascript/11-regexp-infinite-backtracking-problem/article.md deleted file mode 100644 index 455ad673..00000000 --- a/10-regular-expressions-javascript/11-regexp-infinite-backtracking-problem/article.md +++ /dev/null @@ -1,82 +0,0 @@ -# Чёрная дыра бэктрекинга [todo] - -Некоторые регулярные выражения, с виду являясь простыми, могут выполняться оооочень долго, и даже подвешивать браузер. - -[cut] -Например, попробуйте пример ниже в Chrome или IE (осторожно, подвесит браузер!): - -```js -//+ run -alert( '123456789012345678901234567890z'.match(/(\d+)*$/) ); -``` - -Некоторые движки регулярных выражений (Firefox) справляются с таким регэкспом, а некоторые (IE, Chrome) -- нет. - -В чём же дело, что не так с регэкспом? - -Да с регэкспом-то всё так, синтаксис вполне допустимый. Проблема в том, как выполняется поиск по нему. - -Для краткости рассмотрим более короткую строку: 1234567890z: - -
    -
  1. Первым делом, движок регэкспов пытается найти \d+. Плюс + является жадным по умолчанию, так что он хватает все цифры, какие может. - -Затем движок пытается применить звёздочку вокруг скобок (\d+)*, но больше цифр нет, так что звёздочка не даёт повторений. - -После этого в паттерне остаётся $, а в тексте -- символ z. - - - -Так как соответствия нет, то жадный плюс + отступает на один символ (бэктрекинг, зелёная стрелка на рисунке выше). -
  2. -
  3. После бэктрекинга, \d+ содержит всё число, кроме последней цифры. Затем движок снова пытается найти совпадение, уже с новой позиции (`9`). - -Звёздочка (\d+)* теперь может быть применена -- она даёт ещё одно число 9: - - - -Движок пытается найти `$`, но это ему не удаётся -- на его пути опять `z`: - - - -Так как совпадения нет, то поисковой движок отступает назад ещё раз. -
  4. -
  5. Теперь первое число \d+ будет содержать 8 цифр, а остаток строки 90 становится вторым \d+: - - - -Увы, всё ещё нет соответствия для $. - -Поисковой движок снова должен отступить назад. При этом последний жадный квантификатор отпускает символ. В данном случае это означает, что укорачивается второй \d+, до одного символа 9. -
  6. -
  7. Теперь движок регулярных выражений снова может применить звёздочку и находит третье число \d+: - - - -...И снова неудача. Второе и третье \d+ отступили по-максимуму, так что сокращается снова первое число. -
  8. -
  9. Теперь есть 7 цифр в первом \d+. Поисковой движок видит место для второго \d+, теперь уже с позиции 8: - - - -Так как совпадения нет, второй \d+ отступает назад.... -
  10. -
  11. ...И так далее, легко видеть, что поисковой движок будет перебирать *все возможные комбинации* \d+ в числе. А их много.
  12. -
- -На этом месте умный читатель может воскликнуть: "Бэктрекинг? Давайте включим ленивый режим -- и не будет никакого бэктрекинга!" - -Что ж, заменим \d+ на \d+? и посмотрим (аккуратно, может подвесить браузер): - -```js -//+ run -alert( '123456789012345678901234567890z'.match(/(\d+?)*$/) ); -``` - -Не помогло! - -**Ленивые регулярные выражения делают то же самое, но в обратном порядке.** - -Просто подумайте о том, как будет в этом случае работать поисковой движок. - -Некоторые движки регулярных выражений, например Firefox, содержат хитрые проверки, в дополнение к алгоритму выше, которые позволяют избежать бесконечного перебора или кардинально ускорить его, но все движки и не всегда. diff --git a/10-regular-expressions-javascript/12-regexp-alternation/article.md b/10-regular-expressions-javascript/12-regexp-alternation/article.md index adb1adc9..764a58d8 100644 --- a/10-regular-expressions-javascript/12-regexp-alternation/article.md +++ b/10-regular-expressions-javascript/12-regexp-alternation/article.md @@ -6,7 +6,9 @@ Например, нам нужно найти языки программирования: HTML, PHP, Java и JavaScript. -Соответствующее регулярное выражение: /html|php/java(script)?/: +Соответствующее регулярное выражение: html|php|java(script)?. + +Пример использования: ```js //+ run @@ -14,13 +16,11 @@ var reg = /html|php|css|java(script)?/gi var str = "Сначала появился HTML, затем CSS, потом JavaScript" -alert(str.match(reg)) // 'HTML', 'CSS', 'JavaScript' +alert( str.match(reg) ) // 'HTML', 'CSS', 'JavaScript' ``` -**Альтернация имеет очень низкий приоритет.** - -Чтобы регэксп находил одновременно gray и grey, можно использовать gr(a|e)y или gr[ae]y, но не gra|ey. Последний регэксп применит альтернацию к подвыражениям: gra (ИЛИ) ey. - - +Мы уже знаем похожую вещь -- квадратные скобки. Они позволяют выбирать между символами, например gr[ae]y найдёт gray, либо grey. +Альтернация работает уже не посимвольно, а на уровне фраз и подвыражений. Регэксп A|B|C обозначает поиск одного из выражений: `A`, `B` или `C`, причём в качестве выражений могут быть другие, сколь угодно сложные регэкспы. +Для указания границ альтернации используют скобки `(...)`, например: before(XXX|YYY)after будет искать beforeXXXafter или beforeYYYafter. \ No newline at end of file diff --git a/10-regular-expressions-javascript/13-regexp-ahchors-and-multiline-mode/article.md b/10-regular-expressions-javascript/13-regexp-ahchors-and-multiline-mode/article.md index 3ac236a6..c187bd53 100644 --- a/10-regular-expressions-javascript/13-regexp-ahchors-and-multiline-mode/article.md +++ b/10-regular-expressions-javascript/13-regexp-ahchors-and-multiline-mode/article.md @@ -5,43 +5,45 @@ Каретка ^ совпадает в начале текста, а доллар $ -- в конце. -**Якоря являются не символами, а проверками. Они совпадают на "позиции".** +**Якоря являются не символами, а проверками.** -Это очень важное отличие по сравнению с символьными классами. Если движок регулярных выражений видит ^ -- он *проверяет* начало текста, $ -- проверяет конец текста, при этом *никаких символов к совпадению не добавляется*. +До этого мы говорили о регулярных выражениях, которые ищут один или несколько символов. Если совпадение есть -- эти символы включаются в результат. + +А якоря -- не такие. Когда поиск ходит до якоря -- он проверяет, есть ли соответствие, если есть -- продолжает идти по шаблону, не прибавляя ничего к результату. Каретку ^ обычно используют, чтобы указать, что регулярное выражение необходимо проверить именно с начала текста. -Например, без каретки: +Например, без каретки найдёт все числа: ```js //+ run var str = '100500 попугаев съели 500100 бананов!'; -alert(str.match(/\d+/ig) // 100500, 500100 (все числа) +alert( str.match(/\d+/ig) ); // 100500, 500100 (нашло все числа) ``` -А с кареткой: +А с кареткой -- только первое: ```js //+ run var str = '100500 попугаев съели 500100 бананов!'; -alert(str.match(/^\d+/ig) // 100500 (только в начале строки)*!* +alert( str.match(/^\d+/ig) ); // 100500 (только в начале строки)*!* ``` Знак доллара $ используют, чтобы указать, что паттерн должен заканчиваться в конце текста. -Тот же пример с долларом: +Аналогичный пример с долларом для поиска числа в конце: ```js //+ run -var str = '100500 попугаев съели 500100 бананов!'; -alert(str.match(/\d+$/ig) // null (в начале строки чисел нет)*!* +var str = '100500 попугаев съели 500100'; +alert( str.match(/\d+$/ig) ); // 500100 ``` -Якоря используют одновременно, чтобы указать, что паттерн должен охватывать текст с начала и до конца. Обычно это требуется при валидации. +Оба якоря используют одновременно, если требуется, чтобы шаблон охватывал текст с начала и до конца. Обычно это требуется при валидации. -Например, мы хотим проверить, что в переменной `num` хранится именно десятичная дробь. +Например, мы хотим проверить, что в переменной `num` хранится именно десятичная дробь. -Ей соответствует регэксп \d+\.\d+. Но простая проверка найдёт дробь в любом тексте: +Ей соответствует регэксп \d+\.\d+. Но простой поиск найдёт дробь в любом тексте: ```js //+ run @@ -49,7 +51,9 @@ var num = "ля-ля 12.34"; alert( num.match(/\d+\.\d+/ig) ); // 12.34 ``` -Если мы хотим проверить, что `num` *целиком* соответствует паттерну \d+\.\d+, то укажем якоря по обе стороны от него: +Наша же задача -- проверить, что `num` *целиком* соответствует паттерну \d+\.\d+. + +Для этого обернём шаблон в якоря ^...$: ```js //+ run @@ -60,3 +64,5 @@ var num = "12.34"; alert( num.match(/^\d+\.\d+$/ig) ); // 12.34, дробь! ``` +Теперь поиск ищет начало текста, за которым идёт число, затем точка, ещё число и конец текста. Это как раз то, что нужно. + diff --git a/10-regular-expressions-javascript/14-regexp-multiline-mode/article.md b/10-regular-expressions-javascript/14-regexp-multiline-mode/article.md index 1f0e0d19..918a85eb 100644 --- a/10-regular-expressions-javascript/14-regexp-multiline-mode/article.md +++ b/10-regular-expressions-javascript/14-regexp-multiline-mode/article.md @@ -5,55 +5,85 @@ В этом случае изменяется поведение ^ и $. -**В многострочном режиме якоря означают не только начало/конец текста, но и начало/конец строки.** +В многострочном режиме якоря означают не только начало/конец текста, но и начало/конец строки. + +## Начало строки ^ В примере ниже текст состоит из нескольких строк. Паттерн /^\d+/gm берёт число с начала каждой строки: ```js //+ run -var str = '1е место: Винни-пух\n' + +var str = '1е место: Винни\n' + '2е место: Пятачок\n' + '33е место: Слонопотам'; -alert( str.match(/^\d+/gm) ); // 1, 2, 33*!* +*!* +alert( str.match(/^\d+/gm) ); // 1, 2, 33 +*/!* ``` -Обратим внимание -- без флага /m было бы только первое число: +Обратим внимание -- без флага /m было бы найдено только первое число: ```js //+ run -var str = '1е место: Винни-пух\n' + +var str = '1е место: Винни\n' + '2е место: Пятачок\n' + '33е место: Слонопотам'; alert( str.match(/^\d+/g) ); // 1 ``` -Это потому что в обычном режиме каретка ^ -- это только начало текста. +Это потому что в обычном режиме каретка ^ -- это только начало текста, а в многострочном -- начало любой строки. -Символ доллара $ ведёт себя точно так же. +Движок регулярных выражений двигается по тексту, и как только видит начало строки, начинает искать там \d+. -Следующий пример находит последнее слово в строке: +## Конец строки $ -TODO: указать на коренное отличие $ от \n: доллар не матчит символ, а \n матчит!!!! +Символ доллара $ ведёт себя аналогично. + +Регулярное выражение [а-я]+$ в следующем примере находит последнее слово в каждой строке: ```js //+ run -showMatch( - '1st: *!*John*!*\n' + - '2nd: *!*Mary*/!*\n' + - '33rd: *!*Peter*/!*', /\w+$/gm ) // John, Mary, Peter +var str = '1е место: Винни\n' + + '2е место: Пятачок\n' + + '33е место: Слонопотам'; + +alert( str.match(/[а-я]+$/gim) ); // Винни,Пятачок,Слонопотам ``` -Please note that $ as well as ^ doesn't add \n to the match. They only check that the position is right. +Без флага m якорь $ обозначал бы конец всего текста, и было бы найдено только последнее слово. +[smart header="Якорь `$` против `\n`"] +Для того, чтобы найти конец строки, можно использовать не только `$`, но и символ `\n`. -[summary] +Но, в отличие от `$`, символ `\n` во-первых берёт символ в результат, а во-вторых -- не совпадает в конце текста (если, конечно, последний символ -- не конец строки). + +Посмотрим, что будет с примером выше, если вместо [а-я]+$ использовать [а-я]+\n: + +```js +//+ run +var str = '1е место: Винни\n' + + '2е место: Пятачок\n' + + '33е место: Слонопотам'; + +alert( str.match(/[а-я]+\n/gim) ); +/* +Винни +,Пятачок +*/ +``` + +Всего два результата: Винни\n (с символом перевода строки) и Пятачок\n. Последнее слово "Слонопотам" здесь не даёт совпадения, так как после него нет перевода строки. +[/smart] + +## Итого + +В мультистрочном режиме: -**For both anchors, the regexp engine only checks the position, and doesn't match a character.** -[/summary] +Оба символа являются проверками, они не добавляют ничего к результату. Про них также говорят, что "они имеют нулевую длину". diff --git a/10-regular-expressions-javascript/15-regexp-word-boundary/article.md b/10-regular-expressions-javascript/15-regexp-word-boundary/article.md index 50c64d53..74de2292 100644 --- a/10-regular-expressions-javascript/15-regexp-word-boundary/article.md +++ b/10-regular-expressions-javascript/15-regexp-word-boundary/article.md @@ -1,4 +1,4 @@ -# Word boundary +# Word boundary [todo] Another position check is a *word boundary* \b. It doesn't match a character, but matches in situations when a wordly character follows a non-wordly or vice versa. A "non-wordly" may also be text start or end. [cut] diff --git a/10-regular-expressions-javascript/16-regexp-practice/article.md b/10-regular-expressions-javascript/16-regexp-practice/article.md deleted file mode 100644 index e30a77b3..00000000 --- a/10-regular-expressions-javascript/16-regexp-practice/article.md +++ /dev/null @@ -1,7 +0,0 @@ -# Practice - -Here you found tasks which help in understanding regexp construction principles. -[cut] - - -The tutorial is not finished. Till now, we have it up to this part... \ No newline at end of file diff --git a/10-regular-expressions-javascript/16-regexp-todo/article.md b/10-regular-expressions-javascript/16-regexp-todo/article.md new file mode 100644 index 00000000..b825f026 --- /dev/null +++ b/10-regular-expressions-javascript/16-regexp-todo/article.md @@ -0,0 +1,5 @@ +# ToDo + +Требуется добавить главы про предпросмотр lookahead. + +Какие-то ещё? \ No newline at end of file diff --git a/10-regular-expressions-javascript/17-regexp-infinite-backtracking-problem/article.md b/10-regular-expressions-javascript/17-regexp-infinite-backtracking-problem/article.md new file mode 100644 index 00000000..df264aa9 --- /dev/null +++ b/10-regular-expressions-javascript/17-regexp-infinite-backtracking-problem/article.md @@ -0,0 +1,303 @@ +# Чёрная дыра бэктрекинга + +Некоторые регулярные выражения, с виду являясь простыми, могут выполняться оооочень долго, и даже "подвешивать" интерпретатор JavaScript. + +Рано или поздно, с этим сталкивается любой разработчик, потому что нечаянно создать такое регулярное выражение -- легче лёгкого. + +Типична ситуация, когда регулярное выражение до поры до времени работает нормально, и вдруг на каком-то тексте как начнёт "подвешивать" интерпретатор и есть 100% процессора. + +Это может стать уязвимостью. Например, если JavaScript выполняется на сервере, то при разборе данных, присланных посетителем, он может зависнуть, если использует подобный регэксп. На клиенте тоже возможно подобное, при использовании регэкспа для подсветки синтаксиса. + +Такие уязвимости "убивали" почтовые сервера и системы обмена сообщениями и до появления JavaScript, и наверно будут "убивать" и после его исчезновения. Так что мы просто обязаны с ними разобраться. + +[cut] + +## Пример + +План изложения у нас будет таким: + +
    +
  1. Сначала посмотрим на проблему в реальной ситуации.
  2. +
  3. Потом упростим реальную ситуацию до "корней" и увидим, откуда она берётся.
  4. +
+ +Рассмотрим, например, поиск по HTML. + +Мы хотим найти теги с атрибутами, то есть совпадения вида <a href="..." class=doc ...>. + +Самый простой способ это сделать -- <[^>]*>. Но он же и не совсем корректный, так как тег может выглядеть так: <a test="<>" href="#">. То есть, внутри "закавыченного" атрибута может быть символ `>`. Простейший регэксп на нём остановится и найдёт <a test="<>. + +Соответствие: +``` +<[^>]*....> + +``` + +А нам нужен весь тег. + +Для того, чтобы правильно обрабатывать такие ситуации, нужно учесть их в регулярном выражении. Оно будет иметь вид <тег (ключ=значение)*>. + +Если перевести на язык регэкспов, то: <\w+(\s*\w+=(\w+|"[^"]*")\s*)*>: +
    +
  1. <\w+ -- начало тега
  2. +
  3. (\s*\w+=(\w+|"[^"]*")\s*)* -- произвольное количество пар вида `слово=значение`, где "значение" может быть также словом \w+, либо строкой в кавычках "[^"]*".
  4. +
+ + +Мы пока не учитываем все детали грамматики HTML, ведь строки возможны и в 'одинарных' кавычках, но на данный момент этого достаточно. Главное, что регулярное выражение получилось в меру простым и понятным. + + +Испытаем полученный регэксп в действии: + +```js +//+ run +var reg = /<\w+(\s*\w+=(\w+|"[^"]*")\s*)*>/g; + +var str='...
... ...'; + +alert( str.match(reg) ); // , +``` + +Отлично, всё работает! Нашло как длинный тег <a test="<>" href="#">, так и одинокий <b>. + +А теперь -- демонстрация проблемы. + +Если запустить пример ниже, то он может подвесить браузер: + +```js +//+ run +var reg = /<\w+(\s*\w+=(\w+|"[^"]*")\s*)*>/g; + +var str = "/g; + +var str = "(\d+)*$. + +В большинстве движков регэкспов, например в Chrome или IE, этот поиск выполняется очень долго (осторожно, может "подвесить" браузер): + +```js +//+ run +alert( '12345678901234567890123456789123456789z'.match(/(\d+)*$/) ); +``` + +В чём же дело, что не так с регэкспом? + +Внимательный читатель, посмотрев на него, наверняка удивится, ведь он "какой-то странный". Квантификатор * здесь выглядит лишним. + +Если хочется найти число, то с тем же успехом можно искать \d+$. + +Да, этот регэксп носит искусственный характер, но, разобравшись с ним, мы поймём и практический пример, данный выше. Причина их медленной работы одинакова. + +В целом, с регэкспом "всё так", синтаксис вполне допустимый. Проблема в том, как выполняется поиск по нему. + +Посмотрим, что происходит при поиске в строке 123456789z: + +
    +
  1. Первым делом, движок регэкспов пытается найти \d+. Плюс + является жадным по умолчанию, так что он хватает все цифры, какие может: + +``` +\d+....... +(123456789)z +``` +
  2. +
  3. Затем движок пытается применить звёздочку вокруг скобок (\d+)*, но больше цифр нет, так что звёздочка не даёт повторений. + +Затем в шаблоне идёт символ конца строки $, а в тексте -- символ z. + +``` + X +\d+........$ +(123456789)z +``` +Соответствия нет. +
  4. +
  5. Так как соответствие не найдено, то "жадный" плюс + отступает на один символ (бэктрекинг). + +Теперь `\d+` -- это все цифры, за исключением последней: +``` +\d+....... +(12345678)9z +``` +
  6. +
  7. После бэктрекинга, \d+ содержит всё число, кроме последней цифры. Движок снова пытается найти совпадение, уже с новой позиции (`9`). + +Звёздочка (\d+)* теперь может быть применена -- она даёт число 9: + +``` + +\d+.......\d+ +(12345678)(9)z +``` +Движок пытается найти `$`, но это ему не удаётся -- на его пути опять `z`: + +``` + X +\d+.......\d+ +(12345678)(9)z +``` + +Так как совпадения нет, то поисковой движок отступает назад ещё раз. +
  8. +
  9. Теперь первое число \d+ будет содержать 7 цифр, а остаток строки 89 становится вторым \d+: + + +``` + X +\d+......\d+ +(1234567)(89)z +``` + +Увы, всё ещё нет соответствия для $. + +Поисковой движок снова должен отступить назад. При этом последний жадный квантификатор отпускает символ. В данном случае это означает, что укорачивается второй \d+, до одного символа 8, и звёздочка забирает следующий 9. + + +``` + X +\d+......\d+\d+ +(1234567)(8)(9)z +``` +
  10. +
  11. ...И снова неудача. Второе и третье \d+ отступили по-максимуму, так что сокращается снова первое число, до 123456, а звёздочка берёт оставшееся: + +``` + X +\d+.......\d+ +(123456)(789)z +``` + +Снова нет совпадения. Процесс повторяется, последний жадный квантификатор + отпускает один символ (`9`): + +``` + X +\d+.....\d+ \d+ +(123456)(78)(9)z +``` +
  12. + +...И так далее. + +Получается, что движок регулярных выражений перебирает все комбинации из `123456789` и их подпоследовательности. А таких комбинаций очень много. + +На этом месте умный читатель может воскликнуть: "Во всём виноват бэктрекинг? Давайте включим ленивый режим -- и не будет никакого бэктрекинга!" + +Что ж, заменим \d+ на \d+? и посмотрим (аккуратно, может подвесить браузер): + +```js +//+ run +alert( '12345678901234567890123456789123456789z'.match(/(\d+?)*$/) ); +``` + +Не помогло! + +**Ленивые регулярные выражения делают то же самое, но в обратном порядке.** + +Просто подумайте о том, как будет в этом случае работать поисковой движок. + +Некоторые движки регулярных выражений содержат хитрые проверки и конечные автоматы, которые позволяют избежать бесконечного перебора или кардинально ускорить его, но все движки и не всегда. + +Возвращаясь к примеру выше -- при поиске <(\s*\w+=\w+\s*)*> в строке <a=b a=b a=b a=b происходит то же самое. + +Поиск успешно начинается, выбирается некая комбинация из \s*\w+=\w+\s*, которая, так как в конце нет `>`, оказывается не подходящей. Движок честно отступает, пробует другую комбинацию -- и так далее. + +## Что делать? + +Проблема -- в сверхмноговариантном переборе. + +Движок регулярных выражений перебирает кучу возможных вариантов скобок там, где это не нужно. + +Например, в регэкспе (\d+)*$ нам (людям) очевидно, что в (\d+) откатываться не нужно. От того, что вместо одного \d+ у нас два независимых \d+\d+, ничего не изменится. + +Без разницы: + +``` +\d+........ +(123456789)z + +\d+...\d+.... +(1234)(56789)z +``` + +Если вернуться к более реальному примеру <(\s*\w+=\w+\s*)*> то +cам алгоритм поиска, который у нас в голове, предусматривает, что мы "просто" ищем тег, а потом пары `атрибут=значение` (сколько получится). + +Никакого "отката" здесь не нужно. + +В современных регулярных выражениях для решения этой проблемы придумали "possessive" (сверхжадные? неоткатные? точный перевод пока не устоялся) квантификаторы, которые вообще не используют бэктрегинг. + +То есть, они даже проще, чем "жадные" -- берут максимальное количество символов и всё. Поиск продолжается дальше. При несовпадении никакого возврата не происходит. + +Это, c стороны уменьшает количество возможных результатов, но с другой стороны -- в ряде случаев очевидно, что возврат (уменьшение количество повторений квантификатора) результата не даст. А только потратит время, что как раз и доставляет проблемы. Как раз такие ситуации и описаны выше. + +Есть и другое средство -- "атомарные скобочные группы", которые запрещают перебор внутри скобок, по сути позволяя добиваться того же, что и сверхжадные квантификаторы, + +К сожалению, в JavaScript они не поддерживаются. + +Однако, можно получить подобный эффект при помощи предпросмотра. Подробное описание соответствия с учётом синтаксиса сверхжадных квантификаторов и атомарных групп есть в статьях [Regex: Emulate Atomic Grouping (and Possessive Quantifiers) with LookAhead](http://instanceof.me/post/52245507631/regex-emulate-atomic-grouping-with-lookahead) и [Mimicking Atomic Groups](http://blog.stevenlevithan.com/archives/mimic-atomic-groups), здесь же мы останемся в рамках синтаксиса JavaScript. + +Взятие максимального количества повторений `a+` без отката выглядит так: (?=(a+))\1. + +То есть, иными словами, предпросмотр ?= ищет максимальное количество повторений a+, доступных с текущей позиции. А затем они "берутся в результат" обратной ссылкой \1. Дальнейший поиск -- после найденных повторений. + +Откат в этой логике принципе не предусмотрен, поскольку предпросмотр "откатываться" не умеет. То есть, если предпросмотр нашёл 5 штук a+, и в результате поиск не удался, то он не будет откатываться на 4 повторения. Эта возможность в предпросмотре отсутствует, а в данном случае она как раз и не нужна. + +Исправим регэксп для поиска тега с атрибутами <\w+(\s*\w+=(\w+|"[^"]*")\s*)*>, описанный в начале главы. Используем предпросмотр, чтобы запретить откат на меньшее количество пар `атрибут=значение`: + +```js +//+ run +// регэксп для пары атрибут=значение +var attr = /(\s*\w+=(\w+|"[^"]*")\s*)/ + +// используем его внутри регэкспа для тега +var reg = new RegExp('<\\w+(?=(' + attr.source + '*))\\1>', 'g'); + +var good = '...
    ... ...'; + +var bad = ", +alert( bad.match(reg) ); // null (нет результатов, быстро) +``` + +Отлично, всё работает! Нашло как длинный тег <a test="<>" href="#">, так и одинокий <b>. + + + + + + + diff --git a/10-regular-expressions-javascript/11-regexp-infinite-backtracking-problem/bad_backtrack_greedy1.png b/10-regular-expressions-javascript/17-regexp-infinite-backtracking-problem/bad_backtrack_greedy1.png similarity index 100% rename from 10-regular-expressions-javascript/11-regexp-infinite-backtracking-problem/bad_backtrack_greedy1.png rename to 10-regular-expressions-javascript/17-regexp-infinite-backtracking-problem/bad_backtrack_greedy1.png diff --git a/10-regular-expressions-javascript/11-regexp-infinite-backtracking-problem/bad_backtrack_greedy11.png b/10-regular-expressions-javascript/17-regexp-infinite-backtracking-problem/bad_backtrack_greedy11.png similarity index 100% rename from 10-regular-expressions-javascript/11-regexp-infinite-backtracking-problem/bad_backtrack_greedy11.png rename to 10-regular-expressions-javascript/17-regexp-infinite-backtracking-problem/bad_backtrack_greedy11.png diff --git a/10-regular-expressions-javascript/11-regexp-infinite-backtracking-problem/bad_backtrack_greedy2.png b/10-regular-expressions-javascript/17-regexp-infinite-backtracking-problem/bad_backtrack_greedy2.png similarity index 100% rename from 10-regular-expressions-javascript/11-regexp-infinite-backtracking-problem/bad_backtrack_greedy2.png rename to 10-regular-expressions-javascript/17-regexp-infinite-backtracking-problem/bad_backtrack_greedy2.png diff --git a/10-regular-expressions-javascript/11-regexp-infinite-backtracking-problem/bad_backtrack_greedy3.png b/10-regular-expressions-javascript/17-regexp-infinite-backtracking-problem/bad_backtrack_greedy3.png similarity index 100% rename from 10-regular-expressions-javascript/11-regexp-infinite-backtracking-problem/bad_backtrack_greedy3.png rename to 10-regular-expressions-javascript/17-regexp-infinite-backtracking-problem/bad_backtrack_greedy3.png diff --git a/10-regular-expressions-javascript/11-regexp-infinite-backtracking-problem/bad_backtrack_greedy4.png b/10-regular-expressions-javascript/17-regexp-infinite-backtracking-problem/bad_backtrack_greedy4.png similarity index 100% rename from 10-regular-expressions-javascript/11-regexp-infinite-backtracking-problem/bad_backtrack_greedy4.png rename to 10-regular-expressions-javascript/17-regexp-infinite-backtracking-problem/bad_backtrack_greedy4.png diff --git a/10-regular-expressions-javascript/11-regexp-infinite-backtracking-problem/bad_backtrack_greedy5.png b/10-regular-expressions-javascript/17-regexp-infinite-backtracking-problem/bad_backtrack_greedy5.png similarity index 100% rename from 10-regular-expressions-javascript/11-regexp-infinite-backtracking-problem/bad_backtrack_greedy5.png rename to 10-regular-expressions-javascript/17-regexp-infinite-backtracking-problem/bad_backtrack_greedy5.png diff --git a/10-regular-expressions-javascript/17-regexp-orphans/article.md b/10-regular-expressions-javascript/18-regexp-orphans/article.md similarity index 100% rename from 10-regular-expressions-javascript/17-regexp-orphans/article.md rename to 10-regular-expressions-javascript/18-regexp-orphans/article.md diff --git a/10-regular-expressions-javascript/index.md b/10-regular-expressions-javascript/index.md index f96e0193..2ce09876 100644 --- a/10-regular-expressions-javascript/index.md +++ b/10-regular-expressions-javascript/index.md @@ -1,3 +1,3 @@ -# Регулярные выражения [в работе] +# Регулярные выражения [незавершён] Регулярные выражения -- мощный способ поиска и замены для строк. \ No newline at end of file