up
This commit is contained in:
parent
1e2b09b6fb
commit
7ddea43ab4
22 changed files with 382 additions and 343 deletions
|
@ -102,8 +102,7 @@ There are only 5 of them in JavaScript:
|
|||
: Enables full unicode support. The flag enables correct processing of surrogate pairs. More about that in the chapter <info:regexp-unicode>.
|
||||
|
||||
`y`
|
||||
: Sticky mode (covered in [todo])
|
||||
|
||||
: Sticky mode (covered in the [next chapter](info:regexp-methods#y-flag))
|
||||
|
||||
|
||||
## The "i" flag
|
||||
|
|
|
@ -345,6 +345,42 @@ alert( regexp.exec(str).index ); // 34, the search starts from the 30th position
|
|||
```
|
||||
````
|
||||
|
||||
## The "y" flag [#y-flag]
|
||||
|
||||
The `y` flag means that the search should find a match exactly at the position specified by the property `regexp.lastIndex` and only there.
|
||||
|
||||
In other words, normally the search is made in the whole string: `pattern:/javascript/` looks for "javascript" everywhere in the string.
|
||||
|
||||
But when a regexp has the `y` flag, then it only looks for the match at the position specified in `regexp.lastIndex` (`0` by default).
|
||||
|
||||
For instance:
|
||||
|
||||
```js run
|
||||
let str = "I love JavaScript!";
|
||||
|
||||
let reg = /javascript/iy;
|
||||
|
||||
alert( reg.lastIndex ); // 0 (default)
|
||||
alert( str.match(reg) ); // null, not found at position 0
|
||||
|
||||
reg.lastIndex = 7;
|
||||
alert( str.match(reg) ); // JavaScript (right, that word starts at position 7)
|
||||
|
||||
// for any other reg.lastIndex the result is null
|
||||
```
|
||||
|
||||
The regexp `pattern:/javascript/iy` can only be found if we set `reg.lastIndex=7`, because due to `y` flag the engine only tries to find it in the single place within a string -- from the `reg.lastIndex` position.
|
||||
|
||||
So, what's the point? Where do we apply that?
|
||||
|
||||
The reason is performance.
|
||||
|
||||
The `y` flag works great for parsers -- programs that need to "read" the text and build in-memory syntax structure or perform actions from it. For that we move along the text and apply regular expressions to see what we have next: a string? A number? Something else?
|
||||
|
||||
The `y` flag allows to apply a regular expression (or many of them one-by-one) exactly at the given position and when we understand what's there, we can move on -- step by step examining the text.
|
||||
|
||||
Without the flag the regexp engine always searches till the end of the text, that takes time, especially if the text is large. So our parser would be very slow. The `y` flag is exactly the right thing here.
|
||||
|
||||
## Summary, recipes
|
||||
|
||||
Methods become much easier to understand if we separate them by their use in real-life tasks.
|
||||
|
@ -365,4 +401,9 @@ To search and replace:
|
|||
To split the string:
|
||||
: - `str.split(str|reg)`
|
||||
|
||||
Now we know the methods and can use regular expressions. But we need to learn their syntax and capabilities, so let's move on.
|
||||
We also covered two flags:
|
||||
|
||||
- The `g` flag to find all matches (global search),
|
||||
- The `y` flag to search at exactly the given position inside the text.
|
||||
|
||||
Now we know the methods and can use regular expressions. But we need to learn their syntax, so let's move on.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Bracket groups
|
||||
# Capturing groups
|
||||
|
||||
A part of the pattern can be enclosed in parentheses `pattern:(...)`. That's called a "capturing group".
|
||||
|
||||
|
@ -21,6 +21,35 @@ Without parentheses, the pattern `pattern:/go+/` means `subject:g`, followed by
|
|||
|
||||
Parentheses group the word `pattern:(go)` together.
|
||||
|
||||
Let's make something more complex -- a regexp to match an email.
|
||||
|
||||
Examples of emails:
|
||||
|
||||
```
|
||||
my@mail.com
|
||||
john.smith@site.com.uk
|
||||
```
|
||||
|
||||
The pattern: `pattern:[-.\w]+@([\w-]+\.)+[\w-]{2,20}`.
|
||||
|
||||
- The first part before `@` may include wordly characters, a dot and a dash `pattern:[-.\w]+`, like `match:john.smith`.
|
||||
- Then `pattern:@`
|
||||
- And then the domain. May be a second-level domain `site.com` or with subdomains like `host.site.com.uk`. We can match it as "a word followed by a dot" repeated one or more times for subdomains: `match:mail.` or `match:site.com.`, and then "a word" for the last part: `match:.com` or `match:.uk`.
|
||||
|
||||
The word followed by a dot is `pattern:(\w+\.)+` (repeated). The last word should not have a dot at the end, so it's just `\w{2,20}`. The quantifier `pattern:{2,20}` limits the length, because domain zones are like `.uk` or `.com` or `.museum`, but can't be longer than 20 characters.
|
||||
|
||||
So the domain pattern is `pattern:(\w+\.)+\w{2,20}`. Now we replace `\w` with `[\w-]`, because dashes are also allowed in domains, and we get the final result.
|
||||
|
||||
That regexp is not perfect, but usually works. It's short and good enough to fix errors or occasional mistypes.
|
||||
|
||||
For instance, here we can find all emails in the string:
|
||||
|
||||
```js run
|
||||
let reg = /[-.\w]+@([\w-]+\.)+[\w-]{2,20}/g;
|
||||
|
||||
alert("my@mail.com @ his@site.com.uk".match(reg)); // my@mail.com,his@site.com.uk
|
||||
```
|
||||
|
||||
|
||||
## Contents of parentheses
|
||||
|
||||
|
|
|
@ -1,33 +1,33 @@
|
|||
Сначала неправильный способ.
|
||||
|
||||
Если перечислить языки один за другим через `|`, то получится совсем не то:
|
||||
The first idea can be to list the languages with `|` in-between.
|
||||
|
||||
But that doesn't work right:
|
||||
|
||||
```js run
|
||||
var reg = /Java|JavaScript|PHP|C|C\+\+/g;
|
||||
let reg = /Java|JavaScript|PHP|C|C\+\+/g;
|
||||
|
||||
var str = "Java, JavaScript, PHP, C, C++";
|
||||
let str = "Java, JavaScript, PHP, C, C++";
|
||||
|
||||
alert( str.match(reg) ); // Java,Java,PHP,C,C
|
||||
```
|
||||
|
||||
Как видно, движок регулярных выражений ищет альтернации в порядке их перечисления. То есть, он сначала смотрит, есть ли `match:Java`, а если нет -- ищет `match:JavaScript`.
|
||||
The regular expression engine looks for alternations one-by-one. That is: first it checks if we have `match:Java`, otherwise -- looks for `match:JavaScript` and so on.
|
||||
|
||||
Естественно, при этом `match:JavaScript` не будет найдено никогда.
|
||||
As a result, `match:JavaScript` can never be found, just because `match:Java` is checked first.
|
||||
|
||||
То же самое -- с языками `match:C` и `match:C++`.
|
||||
The same with `match:C` and `match:C++`.
|
||||
|
||||
Есть два решения проблемы:
|
||||
There are two solutions for that problem:
|
||||
|
||||
1. Поменять порядок, чтобы более длинное совпадение проверялось первым: `pattern:JavaScript|Java|C\+\+|C|PHP`.
|
||||
2. Соединить длинный вариант с коротким: `pattern:Java(Script)?|C(\+\+)?|PHP`.
|
||||
1. Change the order to check the longer match first: `pattern:JavaScript|Java|C\+\+|C|PHP`.
|
||||
2. Merge variants with the same start: `pattern:Java(Script)?|C(\+\+)?|PHP`.
|
||||
|
||||
В действии:
|
||||
In action:
|
||||
|
||||
```js run
|
||||
var reg = /Java(Script)?|C(\+\+)?|PHP/g;
|
||||
let reg = /Java(Script)?|C(\+\+)?|PHP/g;
|
||||
|
||||
var str = "Java, JavaScript, PHP, C, C++";
|
||||
let str = "Java, JavaScript, PHP, C, C++";
|
||||
|
||||
alert( str.match(reg) ); // Java,JavaScript,PHP,C,C++
|
||||
```
|
||||
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
# Найдите языки программирования
|
||||
# Find programming languages
|
||||
|
||||
Существует много языков программирования, например Java, JavaScript, PHP, C, C++.
|
||||
There are many programming languages, for instance Java, JavaScript, PHP, C, C++.
|
||||
|
||||
Напишите регулярное выражение, которое найдёт их все в строке "Java JavaScript PHP C++ C"
|
||||
Create a regexp that finds them in the string `subject:Java JavaScript PHP C++ C`:
|
||||
|
||||
```js
|
||||
let reg = /your regexp/g;
|
||||
|
||||
alert("Java JavaScript PHP C++ C".match(reg)); // Java JavaScript PHP C++ C
|
||||
```
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
Решение задачи: `pattern:/"(\\.|[^"\\])*"/g`.
|
||||
The solution: `pattern:/"(\\.|[^"\\])*"/g`.
|
||||
|
||||
То есть:
|
||||
Step by step:
|
||||
|
||||
- Сначала ищем кавычку `pattern:"`
|
||||
- Затем, если далее слэш `pattern:\\` (удвоение слэша -- техническое, для вставки в регэксп, на самом деле там один слэш), то после него также подойдёт любой символ (точка).
|
||||
- Если не слэш, то берём любой символ, кроме кавычек (которые будут означать конец строки) и слэша (чтобы предотвратить одинокие слэши, сам по себе единственный слэш не нужен, он должен экранировать какой-то символ) `pattern:[^"\\]`
|
||||
- ...И так жадно, до закрывающей кавычки.
|
||||
- First we look for an opening quote `pattern:"`
|
||||
- Then if we have a backslash `pattern:\\` (we technically have to double it in the pattern, because it is a special character, so that's a single backslash in fact), then any character is fine after it (a dot).
|
||||
- Otherwise we take any character except a quote (that would mean the end of the string) and a backslash (to prevent lonely backslashes, the backslash is only used with some other symbol after it): `pattern:[^"\\]`
|
||||
- ...And so on till the closing quote.
|
||||
|
||||
В действии:
|
||||
In action:
|
||||
|
||||
```js run
|
||||
var re = /"(\\.|[^"\\])*"/g;
|
||||
var str = '.. "test me" .. "Скажи \\"Привет\\"!" .. "\\r\\n\\\\" ..';
|
||||
let reg = /"(\\.|[^"\\])*"/g;
|
||||
let str = ' .. "test me" .. "Say \\"Hello\\"!" .. "\\\\ \\"" .. ';
|
||||
|
||||
alert( str.match(re) ); // "test me","Скажи \"Привет\"!","\r\n\\"
|
||||
alert( str.match(reg) ); // "test me","Say \"Hello\"!","\\ \""
|
||||
```
|
||||
|
|
|
@ -1,25 +1,32 @@
|
|||
# Найдите строки в кавычках
|
||||
# Find quoted strings
|
||||
|
||||
Найдите в тексте при помощи регэкспа строки в двойных кавычках `subject:"..."`.
|
||||
Create a regexp to find strings in double quotes `subject:"..."`.
|
||||
|
||||
В строке поддерживается экранирование при помощи слеша -- примерно в таком же виде, как в обычных строках JavaScript. То есть, строка может содержать любые символы, экранированные слэшем, в частности: `subject:\"`, `subject:\n`, и даже сам слэш в экранированном виде: `subject:\\`.
|
||||
The important part is that strings should support escaping, in the same way as JavaScript strings do. For instance, quotes can be inserted as `subject:\"` a newline as `subject:\n`, and the slash itself as `subject:\\`.
|
||||
|
||||
Здесь особо важно, что двойная кавычка после слэша не оканчивает строку, а считается её частью. В этом и состоит основная сложность задачи, которая без этого условия была бы элементарной.
|
||||
|
||||
Пример совпадающих строк:
|
||||
```js
|
||||
.. *!*"test me"*/!* .. (обычная строка)
|
||||
.. *!*"Скажи \"Привет\"!"*/!* ... (строка с кавычками внутри)
|
||||
.. *!*"\r\n\\"*/!* .. (строка со спец. символами и слэшем внутри)
|
||||
let str = "Just like \"here\".";
|
||||
```
|
||||
|
||||
Заметим, что в JavaScript такие строки удобнее всего задавать в одинарных кавычках, и слеши придётся удвоить (в одинарных кавычках они являются экранирующими символами):
|
||||
For us it's important that an escaped quote `subject:\"` does not end a string.
|
||||
|
||||
So we should look from one quote to the other ignoring escaped quotes on the way.
|
||||
|
||||
That's the essential part of the task, otherwise it would be trivial.
|
||||
|
||||
Examples of strings to match:
|
||||
```js
|
||||
.. *!*"test me"*/!* ..
|
||||
.. *!*"Say \"Hello\"!"*/!* ... (escaped quotes inside)
|
||||
.. *!*"\\"*/!* .. (double slash inside)
|
||||
.. *!*"\\ \""*/!* .. (double slash and an escaped quote inside)
|
||||
```
|
||||
|
||||
In JavaScript we need to double the slashes to pass them right into the string, like this:
|
||||
|
||||
Пример задания тестовой строки в JavaScript:
|
||||
```js run
|
||||
var str = ' .. "test me" .. "Скажи \\"Привет\\"!" .. "\\r\\n\\\\" .. ';
|
||||
let str = ' .. "test me" .. "Say \\"Hello\\"!" .. "\\\\ \\"" .. ';
|
||||
|
||||
// эта строка будет такой:
|
||||
alert(str); // .. "test me" .. "Скажи \"Привет\"!" .. "\r\n\\" ..
|
||||
// the in-memory string
|
||||
alert(str); // .. "test me" .. "Say \"Hello\"!" .. "\\ \"" ..
|
||||
```
|
||||
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
|
||||
Начало шаблона очевидно: `pattern:<style`.
|
||||
The pattern start is obvious: `pattern:<style`.
|
||||
|
||||
А вот дальше... Мы не можем написать просто `pattern:<style.*?>`, так как `match:<styler>` удовлетворяет этому регэкспу.
|
||||
...But then we can't simply write `pattern:<style.*?>`, because `match:<styler>` would match it.
|
||||
|
||||
Нужно уточнить его. После `match:<style` должен быть либо пробел, после которого может быть что-то ещё, либо закрытие тега.
|
||||
We need either a space after `match:<style` and then optionally something else or the ending `match:>`.
|
||||
|
||||
На языке регэкспов: `pattern:<style(>|\s.*?>)`.
|
||||
In the regexp language: `pattern:<style(>|\s.*?>)`.
|
||||
|
||||
В действии:
|
||||
In action:
|
||||
|
||||
```js run
|
||||
var re = /<style(>|\s.*?>)/g;
|
||||
let reg = /<style(>|\s.*?>)/g;
|
||||
|
||||
alert( "<style> <styler> <style test>".match(re) ); // <style>, <style test>
|
||||
alert( '<style> <styler> <style test="...">'.match(reg) ); // <style>, <style test="...">
|
||||
```
|
||||
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
# Найдите тег style
|
||||
# Find the full tag
|
||||
|
||||
Напишите регулярное выражение, которое будет искать в тексте тег `<style>`. Подходят как обычный тег `<style>`, так и вариант с атрибутами `<style type="...">`.
|
||||
Write a regexp to find the tag `<style...>`. It should match the full tag: it may have no attributes `<style>` or have several of them `<style type="..." id="...">`.
|
||||
|
||||
Но регулярное выражение не должно находить `<styler>`!
|
||||
...But the regexp should not match `<styler>`!
|
||||
|
||||
Использование:
|
||||
For instance:
|
||||
|
||||
```js
|
||||
var re = ваш регэксп
|
||||
let reg = /your regexp/g;
|
||||
|
||||
alert( "<style> <styler> <style test>".match(re) ); // <style>, <style test>
|
||||
alert( '<style> <styler> <style test="...">'.match(reg) ); // <style>, <style test="...">
|
||||
```
|
||||
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
|
||||
Нам нужна строка, которая начинается -- и тут же кончается. То есть, пустая.
|
||||
|
||||
Или, если быть ближе к механике регэкспов, то движок сначала будет искать в тексте начальную позицию `pattern:^`, а как только найдёт её -- будет ожидать конечной `pattern:$`.
|
||||
|
||||
Заметим, что и `pattern:^` и `pattern:$` не требуют наличия символов. Это -- проверки. В пустой строке движок сначала проверит первую, а потом -- вторую -- и зафиксирует совпадение.
|
|
@ -1,4 +0,0 @@
|
|||
# Регэксп ^$
|
||||
|
||||
Предложите строку, которая подойдёт под регулярное выражение `pattern:^$`.
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
Двузначное шестнадцатиричное число -- это `pattern:[0-9a-f]{2}` (с учётом флага `pattern:/i`).
|
||||
|
||||
Нам нужно одно такое число, и за ним ещё 5 с двоеточиями перед ними: `pattern:[0-9a-f]{2}(:[0-9a-f]{2}){5}`
|
||||
|
||||
И, наконец, совпадение должно начинаться в начале строки и заканчиваться -- в конце. То есть, строка целиком должна подходить под шаблон. Для этого обернём шаблон в `pattern:^...$`.
|
||||
|
||||
Итого, в действии:
|
||||
|
||||
```js run
|
||||
var re = /^[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}$/i;
|
||||
|
||||
alert( re.test('01:32:54:67:89:AB') ); // true
|
||||
|
||||
alert( re.test('0132546789AB') ); // false (нет двоеточий)
|
||||
|
||||
alert( re.test('01:32:54:67:89') ); // false (5 чисел, а не 6)
|
||||
|
||||
alert( re.test('01:32:54:67:89:ZZ') ) // false (ZZ в конце)
|
||||
```
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
# Проверьте MAC-адрес
|
||||
|
||||
MAC-адрес сетевого интерфейса состоит из шести двузначных шестнадцатеричных чисел, разделённых двоеточием.
|
||||
|
||||
Например: `subject:'01:32:54:67:89:AB'`.
|
||||
|
||||
Напишите регулярное выражение, которое по строке проверяет, является ли она корректным MAC-адресом.
|
||||
|
||||
Использование:
|
||||
```js
|
||||
var re = ваш регэксп
|
||||
|
||||
alert( re.test('01:32:54:67:89:AB') ); // true
|
||||
|
||||
alert( re.test('0132546789AB') ); // false (нет двоеточий)
|
||||
|
||||
alert( re.test('01:32:54:67:89') ); // false (5 чисел, а не 6)
|
||||
|
||||
alert( re.test('01:32:54:67:89:ZZ') ) // false (ZZ в конце)
|
||||
```
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
# Начало строки ^ и конец $
|
||||
|
||||
Знак каретки `pattern:'^'` и доллара `pattern:'$'` имеют в регулярном выражении особый смысл. Их называют "якорями" (anchor - англ.).
|
||||
|
||||
[cut]
|
||||
|
||||
Каретка `pattern:^` совпадает в начале текста, а доллар `pattern:$` -- в конце.
|
||||
|
||||
**Якоря являются не символами, а проверками.**
|
||||
|
||||
До этого мы говорили о регулярных выражениях, которые ищут один или несколько символов. Если совпадение есть -- эти символы включаются в результат.
|
||||
|
||||
А якоря -- не такие. Когда поиск ходит до якоря -- он проверяет, есть ли соответствие, если есть -- продолжает идти по шаблону, не прибавляя ничего к результату.
|
||||
|
||||
Каретку `pattern:^` обычно используют, чтобы указать, что регулярное выражение необходимо проверить именно с начала текста.
|
||||
|
||||
Например, без каретки найдёт все числа:
|
||||
|
||||
```js run
|
||||
var str = '100500 попугаев съели 500100 бананов!';
|
||||
alert( str.match(/\d+/ig) ); // 100500, 500100 (нашло все числа)
|
||||
```
|
||||
|
||||
А с кареткой -- только первое:
|
||||
|
||||
```js run
|
||||
var str = '100500 попугаев съели 500100 бананов!';
|
||||
alert( str.match(/^\d+/ig) ); // 100500 (только в начале строки)*!*
|
||||
```
|
||||
|
||||
Знак доллара `pattern:$` используют, чтобы указать, что паттерн должен заканчиваться в конце текста.
|
||||
|
||||
Аналогичный пример с долларом для поиска числа в конце:
|
||||
|
||||
```js run
|
||||
var str = '100500 попугаев съели 500100';
|
||||
alert( str.match(/\d+$/ig) ); // 500100
|
||||
```
|
||||
|
||||
Оба якоря используют одновременно, если требуется, чтобы шаблон охватывал текст с начала и до конца. Обычно это требуется при валидации.
|
||||
|
||||
Например, мы хотим проверить, что в переменной `num` хранится именно десятичная дробь.
|
||||
|
||||
Ей соответствует регэксп `pattern:\d+\.\d+`. Но простой поиск найдёт дробь в любом тексте:
|
||||
|
||||
```js run
|
||||
var num = "ля-ля 12.34";
|
||||
alert( num.match(/\d+\.\d+/ig) ); // 12.34
|
||||
```
|
||||
|
||||
Наша же задача -- проверить, что `num` *целиком* соответствует паттерну `pattern:\d+\.\d+`.
|
||||
|
||||
Для этого обернём шаблон в якоря `pattern:^...$`:
|
||||
|
||||
```js run
|
||||
var num = "ля-ля 12.34";
|
||||
alert( num.match(/^\d+\.\d+$/ig) ); // null, не дробь
|
||||
|
||||
var num = "12.34";
|
||||
alert( num.match(/^\d+\.\d+$/ig) ); // 12.34, дробь!
|
||||
```
|
||||
|
||||
Теперь поиск ищет начало текста, за которым идёт число, затем точка, ещё число и конец текста. Это как раз то, что нужно.
|
||||
|
||||
## TODO
|
||||
|
||||
Add y flag here
|
||||
|
||||
|
||||
## The "y" flag
|
||||
|
||||
The `y` flag means that the match should start exactly at the position specified by the property `regexp.lastIndex`.
|
||||
|
||||
In other words, normally the search is made in the whole string: `pattern:/love/` checks if the word "love" is found on the first position in the string, then in the second etc. In the example above we have a match on the 2nd position.
|
||||
|
||||
But with the `y` flag we need to specify the position in `regexp.lastIndex` (`0` by default), and the match must be exactly on that position.
|
||||
|
||||
Here, the example:
|
||||
|
||||
```js run
|
||||
let str = "I love Javascript!";
|
||||
|
||||
alert( str.search(/love/y) ); // -1 (not found)
|
||||
alert( str.search(/LOVE/i) ); // 2
|
||||
```
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
The empty string is the only match: it starts and immediately finishes.
|
||||
|
||||
The task once again demonstrates that anchors are not characters, but tests.
|
||||
|
||||
The string is empty `""`. The engine first matches the `pattern:^` (input start), yes it's there, and then immediately the end `pattern:$`, it's here too. So there's a match.
|
|
@ -0,0 +1,3 @@
|
|||
# Regexp ^$
|
||||
|
||||
Which string matches the pattern `pattern:^$`?
|
|
@ -0,0 +1,21 @@
|
|||
A two-digit hex number is `pattern:[0-9a-f]{2}` (assuming the `pattern:i` flag is enabled).
|
||||
|
||||
We need that number `NN`, and then `:NN` repeated 5 times (more numbers);
|
||||
|
||||
The regexp is: `pattern:[0-9a-f]{2}(:[0-9a-f]{2}){5}`
|
||||
|
||||
Now let's show that the match should capture all the text: start at the beginning and end at the end. That's done by wrapping the pattern in `pattern:^...$`.
|
||||
|
||||
Finally:
|
||||
|
||||
```js run
|
||||
let reg = /^[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}$/i;
|
||||
|
||||
alert( reg.test('01:32:54:67:89:AB') ); // true
|
||||
|
||||
alert( reg.test('0132546789AB') ); // false (no colons)
|
||||
|
||||
alert( reg.test('01:32:54:67:89') ); // false (5 numbers, need 6)
|
||||
|
||||
alert( reg.test('01:32:54:67:89:ZZ') ) // false (ZZ in the end)
|
||||
```
|
|
@ -0,0 +1,20 @@
|
|||
# Check MAC-address
|
||||
|
||||
[MAC-address](https://en.wikipedia.org/wiki/MAC_address) of a network interface consists of 6 two-digit hex numbers separated by a colon.
|
||||
|
||||
For instance: `subject:'01:32:54:67:89:AB'`.
|
||||
|
||||
Write a regexp that checks whether a string is MAC-address.
|
||||
|
||||
Usage:
|
||||
```js
|
||||
let reg = /your regexp/;
|
||||
|
||||
alert( reg.test('01:32:54:67:89:AB') ); // true
|
||||
|
||||
alert( reg.test('0132546789AB') ); // false (no colons)
|
||||
|
||||
alert( reg.test('01:32:54:67:89') ); // false (5 numbers, must be 6)
|
||||
|
||||
alert( reg.test('01:32:54:67:89:ZZ') ) // false (ZZ ad the end)
|
||||
```
|
|
@ -0,0 +1,57 @@
|
|||
# String start ^ and finish $
|
||||
|
||||
The caret `pattern:'^'` and dollar `pattern:'$'` characters have special meaning in a regexp. They are called "anchors".
|
||||
|
||||
[cut]
|
||||
|
||||
The caret `pattern:^` matches at the end of the text, and the dollar `pattern:$` -- in the end.
|
||||
|
||||
For instance, let's test if the text starts with `Mary`:
|
||||
|
||||
```js run
|
||||
let str1 = 'Mary had a little lamb, it's fleece was white as snow';
|
||||
let str2 = 'Everywhere Mary went, the lamp was sure to go';
|
||||
|
||||
alert( /^Mary/.test(str1) ); // true
|
||||
alert( /^Mary/.test(str2) ); // false
|
||||
```
|
||||
|
||||
The pattern `pattern:^Mary` means: "the string start and then Mary".
|
||||
|
||||
Now let's test whether the text ends with an email.
|
||||
|
||||
To match an email, we can use a regexp `pattern:[-.\w]+@([\w-]+\.)+[\w-]{2,20}`. It's not perfect, but mostly works.
|
||||
|
||||
To test whether the string ends with the email, let's add `pattern:$` to the pattern:
|
||||
|
||||
```js run
|
||||
let reg = /[-.\w]+@([\w-]+\.)+[\w-]{2,20}$/g;
|
||||
|
||||
let str1 = 'My email is mail@site.com';
|
||||
let str2 = 'Everywhere Mary went, the lamp was sure to go';
|
||||
|
||||
alert( reg.test(str1) ); // true
|
||||
alert( reg.test(str2) ); // false
|
||||
```
|
||||
|
||||
We can use both anchors together to check whether the string exactly follows the pattern. That's often used for validation.
|
||||
|
||||
For instance we want to check that `str` is exactly a color in the form `#` plus 6 hex digits. The pattern for the color is `pattern:#[0-9a-f]{6}`.
|
||||
|
||||
To check that the *whole string* exactly matches it, we add `pattern:^...$`:
|
||||
|
||||
```js run
|
||||
let str = "#abcdef";
|
||||
|
||||
alert( /^#[0-9a-f]{6}$/i.test(str) ); // true
|
||||
```
|
||||
|
||||
The regexp engine looks for the text start, then the color, and then immediately the text end. Just what we need.
|
||||
|
||||
```smart header="Anchors have zero length"
|
||||
Anchors just like `\b` are tests. They have zero-width.
|
||||
|
||||
In other words, they do not match a character, but rather force the regexp engine to check the condition (text start/end).
|
||||
```
|
||||
|
||||
The behavior of anchors changes if there's a flag `pattern:m` (multiline mode). We'll explore it in the next chapter.
|
|
@ -1,85 +1,78 @@
|
|||
# Многострочный режим, флаг "m"
|
||||
# Multiline mode, flag "m"
|
||||
|
||||
Многострочный режим включается, если у регэкспа есть флаг `pattern:/m`.
|
||||
The multiline mode is enabled by the flag `pattern:/.../m`.
|
||||
|
||||
[cut]
|
||||
|
||||
В этом случае изменяется поведение `pattern:^` и `pattern:$`.
|
||||
It only affects the behavior of `pattern:^` and `pattern:$`.
|
||||
|
||||
В многострочном режиме якоря означают не только начало/конец текста, но и начало/конец строки.
|
||||
In the multiline mode they match not only at the beginning and end of the string, but also at start/end of line.
|
||||
|
||||
## Начало строки ^
|
||||
## Line start ^
|
||||
|
||||
В примере ниже текст состоит из нескольких строк. Паттерн `pattern:/^\d+/gm` берёт число с начала каждой строки:
|
||||
In the example below the text has multiple lines. The pattern `pattern:/^\d+/gm` takes a number from the beginning of each one:
|
||||
|
||||
```js run
|
||||
var str = '1е место: Винни\n' +
|
||||
'2е место: Пятачок\n' +
|
||||
'33е место: Слонопотам';
|
||||
let str = `1st place: Winnie
|
||||
2nd place: Piglet
|
||||
33rd place: Eeyore`;
|
||||
|
||||
*!*
|
||||
alert( str.match(/^\d+/gm) ); // 1, 2, 33
|
||||
*/!*
|
||||
```
|
||||
|
||||
Обратим внимание -- без флага `pattern:/m` было бы найдено только первое число:
|
||||
Without the flag `pattern:/.../m` only the first number is matched:
|
||||
|
||||
|
||||
```js run
|
||||
var str = '1е место: Винни\n' +
|
||||
'2е место: Пятачок\n' +
|
||||
'33е место: Слонопотам';
|
||||
let str = `1st place: Winnie
|
||||
2nd place: Piglet
|
||||
33rd place: Eeyore`;
|
||||
|
||||
*!*
|
||||
alert( str.match(/^\d+/g) ); // 1
|
||||
*/!*
|
||||
```
|
||||
|
||||
Это потому что в обычном режиме каретка `pattern:^` -- это только начало текста, а в многострочном -- начало любой строки.
|
||||
That's because by default a caret `pattern:^` only matches at the beginning of the text, and in the multiline mode -- at the start of a line.
|
||||
|
||||
Движок регулярных выражений двигается по тексту, и как только видит начало строки, начинает искать там `pattern:\d+`.
|
||||
The regular expression engine moves along the text and looks for a string start `pattern:^`, when finds -- continues to match the rest of the pattern `pattern:\d+`.
|
||||
|
||||
## Конец строки $
|
||||
## Line end $
|
||||
|
||||
Символ доллара `pattern:$` ведёт себя аналогично.
|
||||
The dollar sign `pattern:$` behaves similarly.
|
||||
|
||||
Регулярное выражение `pattern:[а-я]+$` в следующем примере находит последнее слово в каждой строке:
|
||||
The regular expression `pattern:\w+$` finds the last word in every line
|
||||
|
||||
```js run
|
||||
var str = '1е место: Винни\n' +
|
||||
'2е место: Пятачок\n' +
|
||||
'33е место: Слонопотам';
|
||||
let str = `1st place: Winnie
|
||||
2nd place: Piglet
|
||||
33rd place: Eeyore`;
|
||||
|
||||
alert( str.match(/[а-я]+$/gim) ); // Винни,Пятачок,Слонопотам
|
||||
alert( str.match(/\w+$/gim) ); // Winnie,Piglet,Eeyore
|
||||
```
|
||||
|
||||
Без флага `pattern:m` якорь `pattern:$` обозначал бы конец всего текста, и было бы найдено только последнее слово.
|
||||
Without the `pattern:/.../m` flag the dollar `pattern:$` would only match the end of the whole string, so only the very last word would be found.
|
||||
|
||||
````smart header="Якорь `$` против `\n`"
|
||||
Для того, чтобы найти конец строки, можно использовать не только `$`, но и символ `\n`.
|
||||
## Anchors ^$ versus \n
|
||||
|
||||
Но, в отличие от `$`, символ `\n` во-первых берёт символ в результат, а во-вторых -- не совпадает в конце текста (если, конечно, последний символ -- не конец строки).
|
||||
To find a newline, we can use not only `pattern:^` and `pattern:$`, but also the newline character `\n`.
|
||||
|
||||
Посмотрим, что будет с примером выше, если вместо `pattern:[а-я]+$` использовать `pattern:[а-я]+\n`:
|
||||
The first difference is that unlike anchors, the character `\n` "consumes" the newline character and adds it to the result.
|
||||
|
||||
For instance, here we use it instead of `pattern:$`:
|
||||
|
||||
```js run
|
||||
var str = '1е место: Винни\n' +
|
||||
'2е место: Пятачок\n' +
|
||||
'33е место: Слонопотам';
|
||||
let str = `1st place: Winnie
|
||||
2nd place: Piglet
|
||||
33rd place: Eeyore`;
|
||||
|
||||
alert( str.match(/[а-я]+\n/gim) );
|
||||
/*
|
||||
Винни
|
||||
,Пятачок
|
||||
*/
|
||||
alert( str.match(/\w+\n/gim) ); // Winnie\n,Piglet\n
|
||||
```
|
||||
|
||||
Всего два результата: `match:Винни\n` (с символом перевода строки) и `match:Пятачок\n`. Последнее слово "Слонопотам" здесь не даёт совпадения, так как после него нет перевода строки.
|
||||
````
|
||||
Here every match is a word plus a newline character.
|
||||
|
||||
## Итого
|
||||
|
||||
В мультистрочном режиме:
|
||||
|
||||
- Символ `^` означает начало строки.
|
||||
- Символ `$` означает конец строки.
|
||||
|
||||
Оба символа являются проверками, они не добавляют ничего к результату. Про них также говорят, что "они имеют нулевую длину".
|
||||
And one more difference -- the newline `\n` does not match at the string end. That's why `Eeyore` is not found in the example above.
|
||||
|
||||
So, anchors are usually better, they are closer to what we want to get.
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
# Предпросмотр (неготово)
|
||||
|
||||
Требуется добавить главу про предпросмотр lookahead.
|
||||
|
|
@ -1,129 +1,127 @@
|
|||
# Чёрная дыра бэктрекинга
|
||||
# Infinite backtracking problem
|
||||
|
||||
Некоторые регулярные выражения, с виду являясь простыми, могут выполняться оооочень долго, и даже "подвешивать" интерпретатор JavaScript.
|
||||
Some regular expressions are looking simple, but can execute veeeeeery long time, and even "hang" the JavaScript engine.
|
||||
|
||||
Рано или поздно, с этим сталкивается любой разработчик, потому что нечаянно создать такое регулярное выражение -- легче лёгкого.
|
||||
Sooner or later all developers occasionally meets this behavior.
|
||||
|
||||
Типична ситуация, когда регулярное выражение до поры до времени работает нормально, и вдруг на каком-то тексте как начнёт "подвешивать" интерпретатор и есть 100% процессора.
|
||||
The typical situation -- a regular expression works fine for some time, and then starts to "hang" the script and make it consume 100% of CPU.
|
||||
|
||||
Это может стать уязвимостью. Например, если JavaScript выполняется на сервере, то при разборе данных, присланных посетителем, он может зависнуть, если использует подобный регэксп. На клиенте тоже возможно подобное, при использовании регэкспа для подсветки синтаксиса.
|
||||
That may even be a vulnerability. For instance, if JavaScript is on the server and uses regular expressions on user data. There were many vulnerabilities of that kind even in widely distributed systems.
|
||||
|
||||
Такие уязвимости "убивали" почтовые сервера и системы обмена сообщениями и до появления JavaScript, и наверно будут "убивать" и после его исчезновения. Так что мы просто обязаны с ними разобраться.
|
||||
So the problem is definitely worth to deal with.
|
||||
|
||||
[cut]
|
||||
|
||||
## Пример
|
||||
## Example
|
||||
|
||||
План изложения у нас будет таким:
|
||||
The plan will be like this:
|
||||
|
||||
1. Сначала посмотрим на проблему в реальной ситуации.
|
||||
2. Потом упростим реальную ситуацию до "корней" и увидим, откуда она берётся.
|
||||
1. First we see the problem how it may occur.
|
||||
2. Then we simplify the situation and see why it occurs.
|
||||
3. Then we fix it.
|
||||
|
||||
Рассмотрим, например, поиск по HTML.
|
||||
For instance let's consider searching tags in HTML.
|
||||
|
||||
Мы хотим найти теги с атрибутами, то есть совпадения вида `subject:<a href="..." class=doc ...>`.
|
||||
We want to find all tags, with or without attributes -- like `subject:<a href="..." class="doc" ...>`. We need the regexp to work reliably, because HTML comes from the internet and can be messy.
|
||||
|
||||
Самый простой способ это сделать -- `pattern:<[^>]*>`. Но он же и не совсем корректный, так как тег может выглядеть так: `subject:<a test="<>" href="#">`. То есть, внутри "закавыченного" атрибута может быть символ `>`. Простейший регэксп на нём остановится и найдёт `match:<a test="<>`.
|
||||
In particular, we need it to match tags like `<a test="<>" href="#">` -- with `<` and `>` in attributes. That's allowed by [HTML standard](https://html.spec.whatwg.org/multipage/syntax.html#syntax-attributes).
|
||||
|
||||
Соответствие:
|
||||
```
|
||||
<[^>]*....>
|
||||
<a test="<>" href="#">
|
||||
```
|
||||
|
||||
А нам нужен весь тег.
|
||||
|
||||
Для того, чтобы правильно обрабатывать такие ситуации, нужно учесть их в регулярном выражении. Оно будет иметь вид `pattern:<тег (ключ=значение)*>`.
|
||||
|
||||
Если перевести на язык регэкспов, то: `pattern:<\w+(\s*\w+=(\w+|"[^"]*")\s*)*>`:
|
||||
|
||||
1. `pattern:<\w+` -- начало тега
|
||||
2. `pattern:(\s*\w+=(\w+|"[^"]*")\s*)*` -- произвольное количество пар вида `слово=значение`, где "значение" может быть также словом `pattern:\w+`, либо строкой в кавычках `pattern:"[^"]*"`.
|
||||
|
||||
Мы пока не учитываем все детали грамматики HTML, ведь строки возможны и в 'одинарных' кавычках, но на данный момент этого достаточно. Главное, что регулярное выражение получилось в меру простым и понятным.
|
||||
|
||||
Испытаем полученный регэксп в действии:
|
||||
Now we can see that a simple regexp like `pattern:<[^>]+>` doesn't work, because it stops at the first `>`, and we need to ignore `<>` inside an attribute.
|
||||
|
||||
```js run
|
||||
var reg = /<\w+(\s*\w+=(\w+|"[^"]*")\s*)*>/g;
|
||||
// the match doesn't reach the end of the tag - wrong!
|
||||
alert( '<a test="<>" href="#">'.match(/<[^>]+>/) ); // <a test="<>
|
||||
```
|
||||
|
||||
var str='...<a test="<>" href="#">... <b>...';
|
||||
We need the whole tag.
|
||||
|
||||
To correctly handle such situations we need a more complex regular expression. It will have the form `pattern:<tag (key=value)*>`.
|
||||
|
||||
In the regexp language that is: `pattern:<\w+(\s*\w+=(\w+|"[^"]*")\s*)*>`:
|
||||
|
||||
1. `pattern:<\w+` -- is the tag start,
|
||||
2. `pattern:(\s*\w+=(\w+|"[^"]*")\s*)*` -- is an arbitrary number of pairs `word=value`, where the value can be either a word `pattern:\w+` or a quoted string `pattern:"[^"]*"`.
|
||||
|
||||
That doesn't yet support the details of HTML grammer, for instance strings can be in 'single' quotes, but these can be added later, so that's somewhat close to real life. For now we want the regexp to be simple.
|
||||
|
||||
Let's try it in action:
|
||||
|
||||
```js run
|
||||
let reg = /<\w+(\s*\w+=(\w+|"[^"]*")\s*)*>/g;
|
||||
|
||||
let str='...<a test="<>" href="#">... <b>...';
|
||||
|
||||
alert( str.match(reg) ); // <a test="<>" href="#">, <b>
|
||||
```
|
||||
|
||||
Отлично, всё работает! Нашло как длинный тег `match:<a test="<>" href="#">`, так и одинокий `match:<b>`.
|
||||
Great, it works! It found both the long tag `match:<a test="<>" href="#">` and the short one `match:<b>`.
|
||||
|
||||
А теперь -- демонстрация проблемы.
|
||||
Now let's see the problem.
|
||||
|
||||
Если запустить пример ниже, то он может подвесить браузер:
|
||||
If you run the example below, it may hang the browser (or another JavaScript engine):
|
||||
|
||||
```js run
|
||||
var reg = /<\w+(\s*\w+=(\w+|"[^"]*")\s*)*>/g;
|
||||
let reg = /<\w+(\s*\w+=(\w+|"[^"]*")\s*)*>/g;
|
||||
|
||||
var str = "<tag a=b a=b a=b a=b a=b a=b a=b a=b \
|
||||
a=b a=b a=b a=b a=b a=b a=b a=b a=b a=b a=b a=b a=b";
|
||||
let str = `<tag a=b a=b a=b a=b a=b a=b a=b a=b
|
||||
a=b a=b a=b a=b a=b a=b a=b a=b a=b a=b a=b a=b a=b`;
|
||||
|
||||
*!*
|
||||
// Этот поиск будет выполняться очень, очень долго
|
||||
// The search will take a long long time
|
||||
alert( str.match(reg) );
|
||||
*/!*
|
||||
```
|
||||
|
||||
Некоторые движки регулярных выражений могут в разумное время разобраться с таким поиском, но большинство -- нет.
|
||||
Some regexp engines can handle that search, but most of them don't.
|
||||
|
||||
В чём дело? Почему несложное регулярное выражение на такой небольшой строке "виснет" наглухо?
|
||||
What's the matter? Why a simple regular expression on such a small string "hangs"?
|
||||
|
||||
Упростим ситуацию, удалив тег и возможность указывать строки в кавычках:
|
||||
Let's simplify the situation by removing the tag and quoted strings, we'll look only for attributes:
|
||||
|
||||
```js run
|
||||
// только атрибуты, разделённые пробелами
|
||||
var reg = /<(\s*\w+=\w+\s*)*>/g;
|
||||
// only search for space-delimited attributes
|
||||
let reg = /<(\s*\w+=\w+\s*)*>/g;
|
||||
|
||||
var str = "<a=b a=b a=b a=b a=b a=b a=b a=b \
|
||||
a=b a=b a=b a=b a=b a=b a=b a=b a=b a=b a=b a=b a=b";
|
||||
let str = `<a=b a=b a=b a=b a=b a=b a=b a=b
|
||||
a=b a=b a=b a=b a=b a=b a=b a=b a=b a=b a=b a=b a=b`;
|
||||
|
||||
*!*
|
||||
// Этот поиск будет выполняться очень, очень долго
|
||||
// the search will take a long, long time
|
||||
alert( str.match(reg) );
|
||||
*/!*
|
||||
```
|
||||
|
||||
То же самое.
|
||||
The same.
|
||||
|
||||
На этом мы закончим с демонстрацией "практического примера" и перейдём к разбору происходящего.
|
||||
Here we end the demo of the problem and start looking into what's going on.
|
||||
|
||||
## Бектрекинг
|
||||
## Backtracking
|
||||
|
||||
В качестве ещё более простого регулярного выражения, рассмотрим `pattern:(\d+)*$`.
|
||||
To make an example even simpler, let's consider `pattern:(\d+)*$`.
|
||||
|
||||
В большинстве движков регэкспов, например в Chrome или IE, этот поиск выполняется очень долго (осторожно, может "подвесить" браузер):
|
||||
In most regexp engines that search takes a very long time (careful -- can hang):
|
||||
|
||||
```js run
|
||||
alert( '12345678901234567890123456789123456789z'.match(/(\d+)*$/) );
|
||||
```
|
||||
|
||||
В чём же дело, что не так с регэкспом?
|
||||
So what's wrong with the regexp?
|
||||
|
||||
Внимательный читатель, посмотрев на него, наверняка удивится, ведь он "какой-то странный". Квантификатор `pattern:*` здесь выглядит лишним.
|
||||
Actually, it looks a little bit strange. The quantifier `pattern:*` looks extraneous. If we want a number, we can use `pattern:\d+$`.
|
||||
|
||||
Если хочется найти число, то с тем же успехом можно искать `pattern:\d+$`.
|
||||
Yes, the regexp is artificial, but the reason why it is slow is the same as those we saw above. So let's understand it.
|
||||
|
||||
Да, этот регэксп носит искусственный характер, но, разобравшись с ним, мы поймём и практический пример, данный выше. Причина их медленной работы одинакова.
|
||||
What happen during the search of `pattern:(\d+)*$` in the line `subject:123456789z`?
|
||||
|
||||
В целом, с регэкспом "всё так", синтаксис вполне допустимый. Проблема в том, как выполняется поиск по нему.
|
||||
|
||||
Посмотрим, что происходит при поиске в строке `subject:123456789z`:
|
||||
|
||||
1. Первым делом, движок регэкспов пытается найти `pattern:\d+`. Плюс `pattern:+` является жадным по умолчанию, так что он хватает все цифры, какие может:
|
||||
1. First, the regexp engine tries to find a number `pattern:\d+`. The plus `pattern:+` is greedy by default, so it consumes all digits:
|
||||
|
||||
```
|
||||
\d+.......
|
||||
(123456789)z
|
||||
```
|
||||
2. Затем движок пытается применить звёздочку вокруг скобок `pattern:(\d+)*`, но больше цифр нет, так что звёздочка не даёт повторений.
|
||||
2. Then it tries to apply the start around the parentheses `pattern:(\d+)*`, but there are no more digits, so it the star doesn't give anything.
|
||||
|
||||
Затем в шаблоне идёт символ конца строки `pattern:$`, а в тексте -- символ `subject:z`.
|
||||
Then the pattern has the string end anchor `pattern:$`, and in the text we have `subject:z`.
|
||||
|
||||
```
|
||||
X
|
||||
|
@ -131,17 +129,17 @@ alert( '12345678901234567890123456789123456789z'.match(/(\d+)*$/) );
|
|||
(123456789)z
|
||||
```
|
||||
|
||||
Соответствия нет.
|
||||
3. Так как соответствие не найдено, то "жадный" плюс `pattern:+` отступает на один символ (бэктрекинг).
|
||||
No match!
|
||||
3. There's no match, so the greedy quantifier `pattern:+` decreases the count of repetitions (backtracks).
|
||||
|
||||
Теперь `\d+` -- это все цифры, за исключением последней:
|
||||
Now `\d+` is not all digits, but all except the last one:
|
||||
```
|
||||
\d+.......
|
||||
(12345678)9z
|
||||
```
|
||||
4. После бэктрекинга, `pattern:\d+` содержит всё число, кроме последней цифры. Движок снова пытается найти совпадение, уже с новой позиции (`9`).
|
||||
4. Now the engine tries to continue the search from the new position (`9`).
|
||||
|
||||
Звёздочка `pattern:(\d+)*` теперь может быть применена -- она даёт число `match:9`:
|
||||
The start `pattern:(\d+)*` can now be applied -- it gives the number `match:9`:
|
||||
|
||||
```
|
||||
|
||||
|
@ -149,7 +147,7 @@ alert( '12345678901234567890123456789123456789z'.match(/(\d+)*$/) );
|
|||
(12345678)(9)z
|
||||
```
|
||||
|
||||
Движок пытается найти `$`, но это ему не удаётся -- на его пути опять `z`:
|
||||
The engine tries to match `$` again, but fails, because meets `subject:z`:
|
||||
|
||||
```
|
||||
X
|
||||
|
@ -157,8 +155,8 @@ alert( '12345678901234567890123456789123456789z'.match(/(\d+)*$/) );
|
|||
(12345678)(9)z
|
||||
```
|
||||
|
||||
Так как совпадения нет, то поисковой движок отступает назад ещё раз.
|
||||
5. Теперь первое число `pattern:\d+` будет содержать 7 цифр, а остаток строки `subject:89` становится вторым `pattern:\d+`:
|
||||
There's no match, so the engine will continue backtracking.
|
||||
5. Now the first number `pattern:\d+` will have 7 digits, and the rest of the string `subject:89` becomes the second `pattern:\d+`:
|
||||
|
||||
```
|
||||
X
|
||||
|
@ -166,16 +164,16 @@ alert( '12345678901234567890123456789123456789z'.match(/(\d+)*$/) );
|
|||
(1234567)(89)z
|
||||
```
|
||||
|
||||
Увы, всё ещё нет соответствия для `pattern:$`.
|
||||
...Still no match for `pattern:$`.
|
||||
|
||||
Поисковой движок снова должен отступить назад. При этом последний жадный квантификатор отпускает символ. В данном случае это означает, что укорачивается второй `pattern:\d+`, до одного символа `subject:8`, и звёздочка забирает следующий `subject:9`.
|
||||
The search engine backtracks again. Backtracking generally works like this: the last greedy quantifier decreases the number of repetitions until it can. Then the previous greedy quantifier decreases, and so on. In our case the last greedy quantifier is the second `pattern:\d+`, from `subject:89` to `subject:8`, and then the star takes `subject:9`:
|
||||
|
||||
```
|
||||
X
|
||||
\d+......\d+\d+
|
||||
(1234567)(8)(9)z
|
||||
```
|
||||
6. ...И снова неудача. Второе и третье `pattern:\d+` отступили по-максимуму, так что сокращается снова первое число, до `subject:123456`, а звёздочка берёт оставшееся:
|
||||
6. ...Fail again. The second and third `pattern:\d+` backtracked to the end, so the first quantifier shortens the match to `subject:123456`, and the star takes the rest:
|
||||
|
||||
```
|
||||
X
|
||||
|
@ -183,46 +181,49 @@ alert( '12345678901234567890123456789123456789z'.match(/(\d+)*$/) );
|
|||
(123456)(789)z
|
||||
```
|
||||
|
||||
Снова нет совпадения. Процесс повторяется, последний жадный квантификатор `pattern:+` отпускает один символ (`9`):
|
||||
Again no match. The process repeats: the last greedy quantifier releases one character (`9`):
|
||||
|
||||
```
|
||||
X
|
||||
\d+.....\d+ \d+
|
||||
(123456)(78)(9)z
|
||||
```
|
||||
7. ...И так далее.
|
||||
7. ...And so on.
|
||||
|
||||
Получается, что движок регулярных выражений перебирает все комбинации из `123456789` и их подпоследовательности. А таких комбинаций очень много.
|
||||
The regular expression engine goes through all combinations of `123456789` and their subsequences. There are a lot of them, that's why it takes so long.
|
||||
|
||||
На этом месте умный читатель может воскликнуть: "Во всём виноват бэктрекинг? Давайте включим ленивый режим -- и не будет никакого бэктрекинга!"
|
||||
A smart guy can say here: "Backtracking? Let's turn on the lazy mode -- and no more backtracking!".
|
||||
|
||||
Что ж, заменим `pattern:\d+` на `pattern:\d+?` и посмотрим (аккуратно, может подвесить браузер):
|
||||
Let's replace `pattern:\d+` with `pattern:\d+?` and see if it works (careful, can hang the browser)
|
||||
|
||||
```js run
|
||||
// sloooooowwwwww
|
||||
alert( '12345678901234567890123456789123456789z'.match(/(\d+?)*$/) );
|
||||
```
|
||||
|
||||
Не помогло!
|
||||
No, it doesn't.
|
||||
|
||||
**Ленивые регулярные выражения делают то же самое, но в обратном порядке.**
|
||||
Lazy quantifiers actually do the same, but in the reverse order. Just think about how the search engine would work in this case.
|
||||
|
||||
Просто подумайте о том, как будет в этом случае работать поисковой движок.
|
||||
Some regular expression engines have tricky built-in checks to detect infinite backtracking or other means to work around them, but there's no universal solution.
|
||||
|
||||
Некоторые движки регулярных выражений содержат хитрые проверки и конечные автоматы, которые позволяют избежать бесконечного перебора или кардинально ускорить его, но не все движки и не всегда.
|
||||
In the example above, when we search `pattern:<(\s*\w+=\w+\s*)*>` in the string `subject:<a=b a=b a=b a=b` -- the similar thing happens.
|
||||
|
||||
Возвращаясь к примеру выше -- при поиске `pattern:<(\s*\w+=\w+\s*)*>` в строке `subject:<a=b a=b a=b a=b` происходит то же самое.
|
||||
The string has no `>` at the end, so the match is impossible, but the regexp engine does not know about it. The search backtracks trying different combinations of `pattern:(\s*\w+=\w+\s*)`:
|
||||
|
||||
Поиск успешно начинается, выбирается некая комбинация из `pattern:\s*\w+=\w+\s*`, которая, так как в конце нет `>`, оказывается не подходящей. Движок честно отступает, пробует другую комбинацию -- и так далее.
|
||||
```
|
||||
(a=b a=b a=b) (a=b)
|
||||
(a=b a=b) (a=b a=b)
|
||||
...
|
||||
```
|
||||
|
||||
## Что делать?
|
||||
## How to fix?
|
||||
|
||||
Проблема -- в сверхмноговариантном переборе.
|
||||
The problem -- too many variants in backtracking even if we don't need them.
|
||||
|
||||
Движок регулярных выражений перебирает кучу возможных вариантов скобок там, где это не нужно.
|
||||
For instance, in the pattern `pattern:(\d+)*$` we (people) can easily see that `pattern:(\d+)` does not need to backtrack.
|
||||
|
||||
Например, в регэкспе `pattern:(\d+)*$` нам (людям) очевидно, что в `pattern:(\d+)` откатываться не нужно. От того, что вместо одного `pattern:\d+` у нас два независимых `pattern:\d+\d+`, ничего не изменится.
|
||||
|
||||
Без разницы:
|
||||
Decreasing the count of `pattern:\d+` can not help to find a match, there's no matter between these two:
|
||||
|
||||
```
|
||||
\d+........
|
||||
|
@ -259,14 +260,14 @@ alert( '12345678901234567890123456789123456789z'.match(/(\d+?)*$/) );
|
|||
|
||||
```js run
|
||||
// регэксп для пары атрибут=значение
|
||||
var attr = /(\s*\w+=(\w+|"[^"]*")\s*)/
|
||||
let attr = /(\s*\w+=(\w+|"[^"]*")\s*)/
|
||||
|
||||
// используем его внутри регэкспа для тега
|
||||
var reg = new RegExp('<\\w+(?=(' + attr.source + '*))\\1>', 'g');
|
||||
let reg = new RegExp('<\\w+(?=(' + attr.source + '*))\\1>', 'g');
|
||||
|
||||
var good = '...<a test="<>" href="#">... <b>...';
|
||||
let good = '...<a test="<>" href="#">... <b>...';
|
||||
|
||||
var bad = "<tag a=b a=b a=b a=b a=b a=b a=b a=b\
|
||||
let bad = "<tag a=b a=b a=b a=b a=b a=b a=b a=b\
|
||||
a=b a=b a=b a=b a=b a=b a=b a=b a=b a=b a=b a=b a=b";
|
||||
|
||||
alert( good.match(reg) ); // <a test="<>" href="#">, <b>
|
||||
|
@ -274,4 +275,3 @@ alert( bad.match(reg) ); // null (нет результатов, быстро)
|
|||
```
|
||||
|
||||
Отлично, всё работает! Нашло как длинный тег `match:<a test="<>" href="#">`, так и одинокий `match:<b>`.
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue