From 4406f0f7455fd6d30bb25f547409aba3490cc72e Mon Sep 17 00:00:00 2001 From: Ilya Kantor Date: Tue, 2 Apr 2019 14:01:44 +0300 Subject: [PATCH 001/101] components --- 01-regexp-introduction/article.md | 127 +++++ 02-regexp-methods/article.md | 458 ++++++++++++++++++ .../1-find-time-hh-mm/solution.md | 6 + .../1-find-time-hh-mm/task.md | 8 + 03-regexp-character-classes/article.md | 265 ++++++++++ .../hello-java-boundaries.png | Bin 0 -> 3676 bytes .../hello-java-boundaries@2x.png | Bin 0 -> 7648 bytes .../love-html5-classes.png | Bin 0 -> 4130 bytes .../love-html5-classes@2x.png | Bin 0 -> 8839 bytes 04-regexp-escaping/article.md | 99 ++++ .../1-find-range-1/solution.md | 12 + .../1-find-range-1/task.md | 5 + .../2-find-time-2-formats/solution.md | 8 + .../2-find-time-2-formats/task.md | 12 + .../article.md | 114 +++++ .../1-find-text-manydots/solution.md | 9 + .../1-find-text-manydots/task.md | 14 + .../2-find-html-colors-6hex/solution.md | 31 ++ .../2-find-html-colors-6hex/task.md | 15 + 07-regexp-quantifiers/article.md | 140 ++++++ .../1-lazy-greedy/solution.md | 6 + .../1-lazy-greedy/task.md | 7 + .../3-find-html-comments/solution.md | 17 + .../3-find-html-comments/task.md | 13 + .../4-find-html-tags-greedy-lazy/solution.md | 10 + .../4-find-html-tags-greedy-lazy/task.md | 15 + 08-regexp-greedy-and-lazy/article.md | 304 ++++++++++++ 08-regexp-greedy-and-lazy/witch_greedy1.png | Bin 0 -> 8282 bytes .../witch_greedy1@2x.png | Bin 0 -> 16080 bytes 08-regexp-greedy-and-lazy/witch_greedy2.png | Bin 0 -> 7992 bytes .../witch_greedy2@2x.png | Bin 0 -> 15173 bytes 08-regexp-greedy-and-lazy/witch_greedy3.png | Bin 0 -> 10386 bytes .../witch_greedy3@2x.png | Bin 0 -> 20129 bytes 08-regexp-greedy-and-lazy/witch_greedy4.png | Bin 0 -> 10320 bytes .../witch_greedy4@2x.png | Bin 0 -> 20025 bytes 08-regexp-greedy-and-lazy/witch_greedy5.png | Bin 0 -> 10306 bytes .../witch_greedy5@2x.png | Bin 0 -> 19944 bytes 08-regexp-greedy-and-lazy/witch_greedy6.png | Bin 0 -> 9815 bytes .../witch_greedy6@2x.png | Bin 0 -> 18949 bytes 08-regexp-greedy-and-lazy/witch_lazy3.png | Bin 0 -> 8109 bytes 08-regexp-greedy-and-lazy/witch_lazy3@2x.png | Bin 0 -> 15379 bytes 08-regexp-greedy-and-lazy/witch_lazy4.png | Bin 0 -> 8256 bytes 08-regexp-greedy-and-lazy/witch_lazy4@2x.png | Bin 0 -> 15691 bytes 08-regexp-greedy-and-lazy/witch_lazy5.png | Bin 0 -> 8501 bytes 08-regexp-greedy-and-lazy/witch_lazy5@2x.png | Bin 0 -> 16282 bytes 08-regexp-greedy-and-lazy/witch_lazy6.png | Bin 0 -> 9288 bytes 08-regexp-greedy-and-lazy/witch_lazy6@2x.png | Bin 0 -> 17667 bytes .../1-find-webcolor-3-or-6/solution.md | 29 ++ .../1-find-webcolor-3-or-6/task.md | 14 + .../solution.md | 18 + .../3-find-decimal-positive-numbers/task.md | 12 + .../4-find-decimal-numbers/solution.md | 11 + .../4-find-decimal-numbers/task.md | 13 + .../5-parse-expression/solution.md | 51 ++ 09-regexp-groups/5-parse-expression/task.md | 28 ++ 09-regexp-groups/article.md | 237 +++++++++ 09-regexp-groups/regexp-nested-groups.png | Bin 0 -> 12523 bytes 09-regexp-groups/regexp-nested-groups@2x.png | Bin 0 -> 25856 bytes 10-regexp-backreferences/article.md | 65 +++ .../01-find-programming-language/solution.md | 33 ++ .../01-find-programming-language/task.md | 11 + .../02-find-matching-bbtags/solution.md | 23 + .../02-find-matching-bbtags/task.md | 48 ++ .../03-match-quoted-string/solution.md | 17 + .../03-match-quoted-string/task.md | 32 ++ .../04-match-exact-tag/solution.md | 16 + .../04-match-exact-tag/task.md | 13 + 11-regexp-alternation/article.md | 59 +++ 12-regexp-anchors/1-start-end/solution.md | 6 + 12-regexp-anchors/1-start-end/task.md | 3 + 12-regexp-anchors/2-test-mac/solution.md | 21 + 12-regexp-anchors/2-test-mac/task.md | 20 + 12-regexp-anchors/article.md | 55 +++ 13-regexp-multiline-mode/article.md | 76 +++ 14-regexp-lookahead-lookbehind/article.md | 105 ++++ .../article.md | 293 +++++++++++ 20-regexp-unicode/article.md | 89 ++++ 21-regexp-unicode-properties/article.md | 86 ++++ 22-regexp-sticky/article.md | 71 +++ index.md | 3 + 80 files changed, 3253 insertions(+) create mode 100644 01-regexp-introduction/article.md create mode 100644 02-regexp-methods/article.md create mode 100644 03-regexp-character-classes/1-find-time-hh-mm/solution.md create mode 100644 03-regexp-character-classes/1-find-time-hh-mm/task.md create mode 100644 03-regexp-character-classes/article.md create mode 100644 03-regexp-character-classes/hello-java-boundaries.png create mode 100644 03-regexp-character-classes/hello-java-boundaries@2x.png create mode 100644 03-regexp-character-classes/love-html5-classes.png create mode 100644 03-regexp-character-classes/love-html5-classes@2x.png create mode 100644 04-regexp-escaping/article.md create mode 100644 05-regexp-character-sets-and-ranges/1-find-range-1/solution.md create mode 100644 05-regexp-character-sets-and-ranges/1-find-range-1/task.md create mode 100644 05-regexp-character-sets-and-ranges/2-find-time-2-formats/solution.md create mode 100644 05-regexp-character-sets-and-ranges/2-find-time-2-formats/task.md create mode 100644 05-regexp-character-sets-and-ranges/article.md create mode 100644 07-regexp-quantifiers/1-find-text-manydots/solution.md create mode 100644 07-regexp-quantifiers/1-find-text-manydots/task.md create mode 100644 07-regexp-quantifiers/2-find-html-colors-6hex/solution.md create mode 100644 07-regexp-quantifiers/2-find-html-colors-6hex/task.md create mode 100644 07-regexp-quantifiers/article.md create mode 100644 08-regexp-greedy-and-lazy/1-lazy-greedy/solution.md create mode 100644 08-regexp-greedy-and-lazy/1-lazy-greedy/task.md create mode 100644 08-regexp-greedy-and-lazy/3-find-html-comments/solution.md create mode 100644 08-regexp-greedy-and-lazy/3-find-html-comments/task.md create mode 100644 08-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/solution.md create mode 100644 08-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/task.md create mode 100644 08-regexp-greedy-and-lazy/article.md create mode 100644 08-regexp-greedy-and-lazy/witch_greedy1.png create mode 100644 08-regexp-greedy-and-lazy/witch_greedy1@2x.png create mode 100644 08-regexp-greedy-and-lazy/witch_greedy2.png create mode 100644 08-regexp-greedy-and-lazy/witch_greedy2@2x.png create mode 100644 08-regexp-greedy-and-lazy/witch_greedy3.png create mode 100644 08-regexp-greedy-and-lazy/witch_greedy3@2x.png create mode 100644 08-regexp-greedy-and-lazy/witch_greedy4.png create mode 100644 08-regexp-greedy-and-lazy/witch_greedy4@2x.png create mode 100644 08-regexp-greedy-and-lazy/witch_greedy5.png create mode 100644 08-regexp-greedy-and-lazy/witch_greedy5@2x.png create mode 100644 08-regexp-greedy-and-lazy/witch_greedy6.png create mode 100644 08-regexp-greedy-and-lazy/witch_greedy6@2x.png create mode 100644 08-regexp-greedy-and-lazy/witch_lazy3.png create mode 100644 08-regexp-greedy-and-lazy/witch_lazy3@2x.png create mode 100644 08-regexp-greedy-and-lazy/witch_lazy4.png create mode 100644 08-regexp-greedy-and-lazy/witch_lazy4@2x.png create mode 100644 08-regexp-greedy-and-lazy/witch_lazy5.png create mode 100644 08-regexp-greedy-and-lazy/witch_lazy5@2x.png create mode 100644 08-regexp-greedy-and-lazy/witch_lazy6.png create mode 100644 08-regexp-greedy-and-lazy/witch_lazy6@2x.png create mode 100644 09-regexp-groups/1-find-webcolor-3-or-6/solution.md create mode 100644 09-regexp-groups/1-find-webcolor-3-or-6/task.md create mode 100644 09-regexp-groups/3-find-decimal-positive-numbers/solution.md create mode 100644 09-regexp-groups/3-find-decimal-positive-numbers/task.md create mode 100644 09-regexp-groups/4-find-decimal-numbers/solution.md create mode 100644 09-regexp-groups/4-find-decimal-numbers/task.md create mode 100644 09-regexp-groups/5-parse-expression/solution.md create mode 100644 09-regexp-groups/5-parse-expression/task.md create mode 100644 09-regexp-groups/article.md create mode 100644 09-regexp-groups/regexp-nested-groups.png create mode 100644 09-regexp-groups/regexp-nested-groups@2x.png create mode 100644 10-regexp-backreferences/article.md create mode 100644 11-regexp-alternation/01-find-programming-language/solution.md create mode 100644 11-regexp-alternation/01-find-programming-language/task.md create mode 100644 11-regexp-alternation/02-find-matching-bbtags/solution.md create mode 100644 11-regexp-alternation/02-find-matching-bbtags/task.md create mode 100644 11-regexp-alternation/03-match-quoted-string/solution.md create mode 100644 11-regexp-alternation/03-match-quoted-string/task.md create mode 100644 11-regexp-alternation/04-match-exact-tag/solution.md create mode 100644 11-regexp-alternation/04-match-exact-tag/task.md create mode 100644 11-regexp-alternation/article.md create mode 100644 12-regexp-anchors/1-start-end/solution.md create mode 100644 12-regexp-anchors/1-start-end/task.md create mode 100644 12-regexp-anchors/2-test-mac/solution.md create mode 100644 12-regexp-anchors/2-test-mac/task.md create mode 100644 12-regexp-anchors/article.md create mode 100644 13-regexp-multiline-mode/article.md create mode 100644 14-regexp-lookahead-lookbehind/article.md create mode 100644 15-regexp-infinite-backtracking-problem/article.md create mode 100644 20-regexp-unicode/article.md create mode 100644 21-regexp-unicode-properties/article.md create mode 100644 22-regexp-sticky/article.md create mode 100644 index.md diff --git a/01-regexp-introduction/article.md b/01-regexp-introduction/article.md new file mode 100644 index 000000000..cc50ba8ea --- /dev/null +++ b/01-regexp-introduction/article.md @@ -0,0 +1,127 @@ +# Patterns and flags + +Regular expressions is a powerful way of searching and replacing inside a string. + +In JavaScript regular expressions are implemented using objects of a built-in `RegExp` class and integrated with strings. + +Please note that regular expressions vary between programming languages. In this tutorial we concentrate on JavaScript. Of course there's a lot in common, but they are a somewhat different in Perl, Ruby, PHP etc. + +## Regular expressions + +A regular expression (also "regexp", or just "reg") consists of a *pattern* and optional *flags*. + +There are two syntaxes to create a regular expression object. + +The long syntax: + +```js +regexp = new RegExp("pattern", "flags"); +``` + +...And the short one, using slashes `"/"`: + +```js +regexp = /pattern/; // no flags +regexp = /pattern/gmi; // with flags g,m and i (to be covered soon) +``` + +Slashes `"/"` tell JavaScript that we are creating a regular expression. They play the same role as quotes for strings. + +## Usage + +To search inside a string, we can use method [search](mdn:js/String/search). + +Here's an example: + +```js run +let str = "I love JavaScript!"; // will search here + +let regexp = /love/; +alert( str.search(regexp) ); // 2 +``` + +The `str.search` method looks for the pattern `pattern:/love/` and returns the position inside the string. As we might guess, `pattern:/love/` is the simplest possible pattern. What it does is a simple substring search. + +The code above is the same as: + +```js run +let str = "I love JavaScript!"; // will search here + +let substr = 'love'; +alert( str.search(substr) ); // 2 +``` + +So searching for `pattern:/love/` is the same as searching for `"love"`. + +But that's only for now. Soon we'll create more complex regular expressions with much more searching power. + +```smart header="Colors" +From here on the color scheme is: + +- regexp -- `pattern:red` +- string (where we search) -- `subject:blue` +- result -- `match:green` +``` + + +````smart header="When to use `new RegExp`?" +Normally we use the short syntax `/.../`. But it does not allow any variable insertions, so we must know the exact regexp at the time of writing the code. + +On the other hand, `new RegExp` allows to construct a pattern dynamically from a string. + +So we can figure out what we need to search and create `new RegExp` from it: + +```js run +let search = prompt("What you want to search?", "love"); +let regexp = new RegExp(search); + +// find whatever the user wants +alert( "I love JavaScript".search(regexp)); +``` +```` + + +## Flags + +Regular expressions may have flags that affect the search. + +There are only 5 of them in JavaScript: + +`i` +: With this flag the search is case-insensitive: no difference between `A` and `a` (see the example below). + +`g` +: With this flag the search looks for all matches, without it -- only the first one (we'll see uses in the next chapter). + +`m` +: Multiline mode (covered in the chapter ). + +`s` +: "Dotall" mode, allows `.` to match newlines (covered in the chapter ). + +`u` +: Enables full unicode support. The flag enables correct processing of surrogate pairs. More about that in the chapter . + +`y` +: Sticky mode (covered in the chapter ) + +We'll cover all these flags further in the tutorial. + +For now, the simplest flag is `i`, here's an example: + +```js run +let str = "I love JavaScript!"; + +alert( str.search(/LOVE/i) ); // 2 (found lowercased) + +alert( str.search(/LOVE/) ); // -1 (nothing found without 'i' flag) +``` + +So the `i` flag already makes regular expressions more powerful than a simple substring search. But there's so much more. We'll cover other flags and features in the next chapters. + + +## Summary + +- A regular expression consists of a pattern and optional flags: `g`, `i`, `m`, `u`, `s`, `y`. +- Without flags and special symbols that we'll study later, the search by a regexp is the same as a substring search. +- The method `str.search(regexp)` returns the index where the match is found or `-1` if there's no match. In the next chapter we'll see other methods. diff --git a/02-regexp-methods/article.md b/02-regexp-methods/article.md new file mode 100644 index 000000000..fa8cbf7b7 --- /dev/null +++ b/02-regexp-methods/article.md @@ -0,0 +1,458 @@ +# Methods of RegExp and String + +There are two sets of methods to deal with regular expressions. + +1. First, regular expressions are objects of the built-in [RegExp](mdn:js/RegExp) class, it provides many methods. +2. Besides that, there are methods in regular strings can work with regexps. + + +## Recipes + +Which method to use depends on what we'd like to do. + +Methods become much easier to understand if we separate them by their use in real-life tasks: + +**To search for all matches:** + +Use regexp `g` flag and: +- Get a flat array of matches -- `str.match(reg)` +- Get an array or matches with details -- `str.matchAll(reg)`. + +**To search for the first match only:** +- Get the full first match -- `str.match(reg)` (without `g` flag). +- Get the string position of the first match -- `str.search(reg)`. +- Check if there's a match -- `regexp.test(str)`. +- Find the match from the given position -- `regexp.exec(str)` (set `regexp.lastIndex` to position). + +**To replace all matches:** +- Replace with another string or a function result -- `str.replace(reg, str|func)` + +**To split the string by a separator:** +- `str.split(str|reg)` + +Now you get the details about every method in this chapter... But if you're reading for the first time, and want to know more about regexps - go ahead! + +You may want to skip methods for now, move on to the next chapter, and then return here if something about a method is unclear. + +## str.search(reg) + +We've seen this method already. It returns the position of the first match or `-1` if none found: + +```js run +let str = "A drop of ink may make a million think"; + +alert( str.search( *!*/a/i*/!* ) ); // 0 (the first position) +``` + +**The important limitation: `search` only finds the first match.** + +We can't find next positions using `search`, there's just no syntax for that. But there are other methods that can. + +## str.match(reg), no "g" flag + +The behavior of `str.match` varies depending on whether `reg` has `g` flag or not. + +First, if there's no `g` flag, then `str.match(reg)` looks for the first match only. + +The result is an array with that match and additional properties: + +- `index` -- the position of the match inside the string, +- `input` -- the subject string. + +For instance: + +```js run +let str = "Fame is the thirst of youth"; + +let result = str.match( *!*/fame/i*/!* ); + +alert( result[0] ); // Fame (the match) +alert( result.index ); // 0 (at the zero position) +alert( result.input ); // "Fame is the thirst of youth" (the string) +``` + +A match result may have more than one element. + +**If a part of the pattern is delimited by parentheses `(...)`, then it becomes a separate element in the array.** + +If parentheses have a name, designated by `(?...)` at their start, then `result.groups[name]` has the content. We'll see that later in the chapter [about groups](info:regexp-groups). + +For instance: + +```js run +let str = "JavaScript is a programming language"; + +let result = str.match( *!*/JAVA(SCRIPT)/i*/!* ); + +alert( result[0] ); // JavaScript (the whole match) +alert( result[1] ); // script (the part of the match that corresponds to the parentheses) +alert( result.index ); // 0 +alert( result.input ); // JavaScript is a programming language +``` + +Due to the `i` flag the search is case-insensitive, so it finds `match:JavaScript`. The part of the match that corresponds to `pattern:SCRIPT` becomes a separate array item. + +So, this method is used to find one full match with all details. + + +## str.match(reg) with "g" flag + +When there's a `"g"` flag, then `str.match` returns an array of all matches. There are no additional properties in that array, and parentheses do not create any elements. + +For instance: + +```js run +let str = "HO-Ho-ho!"; + +let result = str.match( *!*/ho/ig*/!* ); + +alert( result ); // HO, Ho, ho (array of 3 matches, case-insensitive) +``` + +Parentheses do not change anything, here we go: + +```js run +let str = "HO-Ho-ho!"; + +let result = str.match( *!*/h(o)/ig*/!* ); + +alert( result ); // HO, Ho, ho +``` + +**So, with `g` flag `str.match` returns a simple array of all matches, without details.** + +If we want to get information about match positions and contents of parentheses then we should use `matchAll` method that we'll cover below. + +````warn header="If there are no matches, `str.match` returns `null`" +Please note, that's important. If there are no matches, the result is not an empty array, but `null`. + +Keep that in mind to evade pitfalls like this: + +```js run +let str = "Hey-hey-hey!"; + +alert( str.match(/Z/g).length ); // Error: Cannot read property 'length' of null +``` + +Here `str.match(/Z/g)` is `null`, it has no `length` property. +```` + +## str.matchAll(regexp) + +The method `str.matchAll(regexp)` is used to find all matches with all details. + +For instance: + +```js run +let str = "Javascript or JavaScript? Should we uppercase 'S'?"; + +let result = str.matchAll( *!*/java(script)/ig*/!* ); + +let [match1, match2] = result; + +alert( match1[0] ); // Javascript (the whole match) +alert( match1[1] ); // script (the part of the match that corresponds to the parentheses) +alert( match1.index ); // 0 +alert( match1.input ); // = str (the whole original string) + +alert( match2[0] ); // JavaScript (the whole match) +alert( match2[1] ); // Script (the part of the match that corresponds to the parentheses) +alert( match2.index ); // 14 +alert( match2.input ); // = str (the whole original string) +``` + +````warn header="`matchAll` returns an iterable, not array" +For instance, if we try to get the first match by index, it won't work: + +```js run +let str = "Javascript or JavaScript??"; + +let result = str.matchAll( /javascript/ig ); + +*!* +alert(result[0]); // undefined (?! there must be a match) +*/!* +``` + +The reason is that the iterator is not an array. We need to run `Array.from(result)` on it, or use `for..of` loop to get matches. + +In practice, if we need all matches, then `for..of` works, so it's not a problem. + +And, to get only few matches, we can use destructuring: + +```js run +let str = "Javascript or JavaScript??"; + +*!* +let [firstMatch] = str.matchAll( /javascript/ig ); +*/!* + +alert(firstMatch); // Javascript +``` +```` + +```warn header="`matchAll` is supernew, may need a polyfill" +The method may not work in old browsers. A polyfill might be needed (this site uses core-js). + +Or you could make a loop with `regexp.exec`, explained below. +``` + +## str.split(regexp|substr, limit) + +Splits the string using the regexp (or a substring) as a delimiter. + +We already used `split` with strings, like this: + +```js run +alert('12-34-56'.split('-')) // array of [12, 34, 56] +``` + +But we can split by a regular expression, the same way: + +```js run +alert('12-34-56'.split(/-/)) // array of [12, 34, 56] +``` + +## str.replace(str|reg, str|func) + +That's actually a great method, one of most useful ones. The swiss army knife for searching and replacing. + +The simplest use -- searching and replacing a substring, like this: + +```js run +// replace a dash by a colon +alert('12-34-56'.replace("-", ":")) // 12:34-56 +``` + +There's a pitfall though. + +**When the first argument of `replace` is a string, it only looks for the first match.** + +You can see that in the example above: only the first `"-"` is replaced by `":"`. + +To find all dashes, we need to use not the string `"-"`, but a regexp `pattern:/-/g`, with an obligatory `g` flag: + +```js run +// replace all dashes by a colon +alert( '12-34-56'.replace( *!*/-/g*/!*, ":" ) ) // 12:34:56 +``` + +The second argument is a replacement string. We can use special characters in it: + +| Symbol | Inserts | +|--------|--------| +|`$$`|`"$"` | +|`$&`|the whole match| +|$`|a part of the string before the match| +|`$'`|a part of the string after the match| +|`$n`|if `n` is a 1-2 digit number, then it means the contents of n-th parentheses counting from left to right, otherwise it means a parentheses with the given name | + + +For instance if we use `$&` in the replacement string, that means "put the whole match here". + +Let's use it to prepend all entries of `"John"` with `"Mr."`: + +```js run +let str = "John Doe, John Smith and John Bull"; + +// for each John - replace it with Mr. and then John +alert(str.replace(/John/g, 'Mr.$&')); // Mr.John Doe, Mr.John Smith and Mr.John Bull +``` + +Quite often we'd like to reuse parts of the source string, recombine them in the replacement or wrap into something. + +To do so, we should: +1. First, mark the parts by parentheses in regexp. +2. Use `$1`, `$2` (and so on) in the replacement string to get the content matched by parentheses. + +For instance: + +```js run +let str = "John Smith"; + +// swap first and last name +alert(str.replace(/(john) (smith)/i, '$2, $1')) // Smith, John +``` + +**For situations that require "smart" replacements, the second argument can be a function.** + +It will be called for each match, and its result will be inserted as a replacement. + +For instance: + +```js run +let i = 0; + +// replace each "ho" by the result of the function +alert("HO-Ho-ho".replace(/ho/gi, function() { + return ++i; +})); // 1-2-3 +``` + +In the example above the function just returns the next number every time, but usually the result is based on the match. + +The function is called with arguments `func(str, p1, p2, ..., pn, offset, input, groups)`: + +1. `str` -- the match, +2. `p1, p2, ..., pn` -- contents of parentheses (if there are any), +3. `offset` -- position of the match, +4. `input` -- the source string, +5. `groups` -- an object with named groups (see chapter [](info:regexp-groups)). + +If there are no parentheses in the regexp, then there are only 3 arguments: `func(str, offset, input)`. + +Let's use it to show full information about matches: + +```js run +// show and replace all matches +function replacer(str, offset, input) { + alert(`Found ${str} at position ${offset} in string ${input}`); + return str.toLowerCase(); +} + +let result = "HO-Ho-ho".replace(/ho/gi, replacer); +alert( 'Result: ' + result ); // Result: ho-ho-ho + +// shows each match: +// Found HO at position 0 in string HO-Ho-ho +// Found Ho at position 3 in string HO-Ho-ho +// Found ho at position 6 in string HO-Ho-ho +``` + +In the example below there are two parentheses, so `replacer` is called with 5 arguments: `str` is the full match, then parentheses, and then `offset` and `input`: + +```js run +function replacer(str, name, surname, offset, input) { + // name is the first parentheses, surname is the second one + return surname + ", " + name; +} + +let str = "John Smith"; + +alert(str.replace(/(John) (Smith)/, replacer)) // Smith, John +``` + +Using a function gives us the ultimate replacement power, because it gets all the information about the match, has access to outer variables and can do everything. + +## regexp.exec(str) + +We've already seen these searching methods: + +- `search` -- looks for the position of the match, +- `match` -- if there's no `g` flag, returns the first match with parentheses and all details, +- `match` -- if there's a `g` flag -- returns all matches, without details parentheses, +- `matchAll` -- returns all matches with details. + +The `regexp.exec` method is the most flexible searching method of all. Unlike previous methods, `exec` should be called on a regexp, rather than on a string. + +It behaves differently depending on whether the regexp has the `g` flag. + +If there's no `g`, then `regexp.exec(str)` returns the first match, exactly as `str.match(reg)`. Such behavior does not give us anything new. + +But if there's `g`, then: +- `regexp.exec(str)` returns the first match and *remembers* the position after it in `regexp.lastIndex` property. +- The next call starts to search from `regexp.lastIndex` and returns the next match. +- If there are no more matches then `regexp.exec` returns `null` and `regexp.lastIndex` is set to `0`. + +We could use it to get all matches with their positions and parentheses groups in a loop, instead of `matchAll`: + +```js run +let str = 'A lot about JavaScript at https://javascript.info'; + +let regexp = /javascript/ig; + +let result; + +while (result = regexp.exec(str)) { + alert( `Found ${result[0]} at ${result.index}` ); + // shows: Found JavaScript at 12, then: + // shows: Found javascript at 34 +} +``` + +Surely, `matchAll` does the same, at least for modern browsers. But what `matchAll` can't do -- is to search from a given position. + +Let's search from position `13`. What we need is to assign `regexp.lastIndex=13` and call `regexp.exec`: + +```js run +let str = "A lot about JavaScript at https://javascript.info"; + +let regexp = /javascript/ig; +*!* +regexp.lastIndex = 13; +*/!* + +let result; + +while (result = regexp.exec(str)) { + alert( `Found ${result[0]} at ${result.index}` ); + // shows: Found javascript at 34 +} +``` + +Now, starting from the given position `13`, there's only one match. + + +## regexp.test(str) + +The method `regexp.test(str)` looks for a match and returns `true/false` whether it finds it. + +For instance: + +```js run +let str = "I love JavaScript"; + +// these two tests do the same +alert( *!*/love/i*/!*.test(str) ); // true +alert( str.search(*!*/love/i*/!*) != -1 ); // true +``` + +An example with the negative answer: + +```js run +let str = "Bla-bla-bla"; + +alert( *!*/love/i*/!*.test(str) ); // false +alert( str.search(*!*/love/i*/!*) != -1 ); // false +``` + +If the regexp has `'g'` flag, then `regexp.test` advances `regexp.lastIndex` property, just like `regexp.exec`. + +So we can use it to search from a given position: + +```js run +let regexp = /love/gi; + +let str = "I love JavaScript"; + +// start the search from position 10: +regexp.lastIndex = 10 +alert( regexp.test(str) ); // false (no match) +``` + + + +````warn header="Same global regexp tested repeatedly may fail to match" +If we apply the same global regexp to different inputs, it may lead to wrong result, because `regexp.test` call advances `regexp.lastIndex` property, so next matches start from non-zero position. + +For instance, here we call `regexp.test` twice on the same text, and the second time fails: + +```js run +let regexp = /javascript/g; // (regexp just created: regexp.lastIndex=0) + +alert( regexp.test("javascript") ); // true (regexp.lastIndex=10 now) +alert( regexp.test("javascript") ); // false +``` + +That's exactly because `regexp.lastIndex` is non-zero on the second test. + +To work around that, one could use non-global regexps or re-adjust `regexp.lastIndex=0` before a new search. +```` + +## Summary + +There's a variety of many methods on both regexps and strings. + +Their abilities and methods overlap quite a bit, we can do the same by different calls. Sometimes that may cause confusion when starting to learn the language. + +Then please refer to the recipes at the beginning of this chapter, as they provide solutions for the majority of regexp-related tasks. diff --git a/03-regexp-character-classes/1-find-time-hh-mm/solution.md b/03-regexp-character-classes/1-find-time-hh-mm/solution.md new file mode 100644 index 000000000..829eda13e --- /dev/null +++ b/03-regexp-character-classes/1-find-time-hh-mm/solution.md @@ -0,0 +1,6 @@ + +The answer: `pattern:\b\d\d:\d\d\b`. + +```js run +alert( "Breakfast at 09:00 in the room 123:456.".match( /\b\d\d:\d\d\b/ ) ); // 09:00 +``` diff --git a/03-regexp-character-classes/1-find-time-hh-mm/task.md b/03-regexp-character-classes/1-find-time-hh-mm/task.md new file mode 100644 index 000000000..5e32b9c48 --- /dev/null +++ b/03-regexp-character-classes/1-find-time-hh-mm/task.md @@ -0,0 +1,8 @@ +# Find the time + +The time has a format: `hours:minutes`. Both hours and minutes has two digits, like `09:00`. + +Make a regexp to find time in the string: `subject:Breakfast at 09:00 in the room 123:456.` + +P.S. In this task there's no need to check time correctness yet, so `25:99` can also be a valid result. +P.P.S. The regexp shouldn't match `123:456`. diff --git a/03-regexp-character-classes/article.md b/03-regexp-character-classes/article.md new file mode 100644 index 000000000..6bc4d710e --- /dev/null +++ b/03-regexp-character-classes/article.md @@ -0,0 +1,265 @@ +# Character classes + +Consider a practical task -- we have a phone number `"+7(903)-123-45-67"`, and we need to turn it into pure numbers: `79035419441`. + +To do so, we can find and remove anything that's not a number. Character classes can help with that. + +A character class is a special notation that matches any symbol from a certain set. + +For the start, let's explore a "digit" class. It's written as `\d`. We put it in the pattern, that means "any single digit". + +For instance, the let's find the first digit in the phone number: + +```js run +let str = "+7(903)-123-45-67"; + +let reg = /\d/; + +alert( str.match(reg) ); // 7 +``` + +Without the flag `g`, the regular expression only looks for the first match, that is the first digit `\d`. + +Let's add the `g` flag to find all digits: + +```js run +let str = "+7(903)-123-45-67"; + +let reg = /\d/g; + +alert( str.match(reg) ); // array of matches: 7,9,0,3,1,2,3,4,5,6,7 + +alert( str.match(reg).join('') ); // 79035419441 +``` + +That was a character class for digits. There are other character classes as well. + +Most used are: + +`\d` ("d" is from "digit") +: A digit: a character from `0` to `9`. + +`\s` ("s" is from "space") +: A space symbol: that includes spaces, tabs, newlines. + +`\w` ("w" is from "word") +: A "wordly" character: either a letter of English alphabet or a digit or an underscore. Non-english letters (like cyrillic or hindi) do not belong to `\w`. + +For instance, `pattern:\d\s\w` means a "digit" followed by a "space character" followed by a "wordly character", like `"1 a"`. + +**A regexp may contain both regular symbols and character classes.** + +For instance, `pattern:CSS\d` matches a string `match:CSS` with a digit after it: + +```js run +let str = "CSS4 is cool"; +let reg = /CSS\d/ + +alert( str.match(reg) ); // CSS4 +``` + +Also we can use many character classes: + +```js run +alert( "I love HTML5!".match(/\s\w\w\w\w\d/) ); // 'HTML5' +``` + +The match (each character class corresponds to one result character): + +![](love-html5-classes.png) + +## Word boundary: \b + +A word boundary `pattern:\b` -- is a special character class. + +It does not denote a character, but rather a boundary between characters. + +For instance, `pattern:\bJava\b` matches `match:Java` in the string `subject:Hello, Java!`, but not in the script `subject:Hello, JavaScript!`. + +```js run +alert( "Hello, Java!".match(/\bJava\b/) ); // Java +alert( "Hello, JavaScript!".match(/\bJava\b/) ); // null +``` + +The boundary has "zero width" in a sense that usually a character class means a character in the result (like a wordly character or a digit), but not in this case. + +The boundary is a test. + +When regular expression engine is doing the search, it's moving along the string in an attempt to find the match. At each string position it tries to find the pattern. + +When the pattern contains `pattern:\b`, it tests that the position in string is a word boundary, that is one of three variants: + +- Immediately before is `\w`, and immediately after -- not `\w`, or vise versa. +- At string start, and the first string character is `\w`. +- At string end, and the last string character is `\w`. + +For instance, in the string `subject:Hello, Java!` the following positions match `\b`: + +![](hello-java-boundaries.png) + +So it matches `pattern:\bHello\b`, because: + +1. At the beginning of the string the first `\b` test matches. +2. Then the word `Hello` matches. +3. Then `\b` matches, as we're between `o` and a space. + +Pattern `pattern:\bJava\b` also matches. But not `pattern:\bHell\b` (because there's no word boundary after `l`) and not `Java!\b` (because the exclamation sign is not a wordly character, so there's no word boundary after it). + + +```js run +alert( "Hello, Java!".match(/\bHello\b/) ); // Hello +alert( "Hello, Java!".match(/\bJava\b/) ); // Java +alert( "Hello, Java!".match(/\bHell\b/) ); // null (no match) +alert( "Hello, Java!".match(/\bJava!\b/) ); // null (no match) +``` + +Once again let's note that `pattern:\b` makes the searching engine to test for the boundary, so that `pattern:Java\b` finds `match:Java` only when followed by a word boundary, but it does not add a letter to the result. § + +Usually we use `\b` to find standalone English words. So that if we want `"Java"` language then `pattern:\bJava\b` finds exactly a standalone word and ignores it when it's a part of `"JavaScript"`. + +Another example: a regexp `pattern:\b\d\d\b` looks for standalone two-digit numbers. In other words, it requires that before and after `pattern:\d\d` must be a symbol different from `\w` (or beginning/end of the string). + +```js run +alert( "1 23 456 78".match(/\b\d\d\b/g) ); // 23,78 +``` + +```warn header="Word boundary doesn't work for non-English alphabets" +The word boundary check `\b` tests for a boundary between `\w` and something else. But `\w` means an English letter (or a digit or an underscore), so the test won't work for other characters (like cyrillic or hieroglyphs). +``` + + +## Inverse classes + +For every character class there exists an "inverse class", denoted with the same letter, but uppercased. + +The "reverse" means that it matches all other characters, for instance: + +`\D` +: Non-digit: any character except `\d`, for instance a letter. + +`\S` +: Non-space: any character except `\s`, for instance a letter. + +`\W` +: Non-wordly character: anything but `\w`. + +`\B` +: Non-boundary: a test reverse to `\b`. + +In the beginning of the chapter we saw how to get all digits from the phone `subject:+7(903)-123-45-67`. + +One way was to match all digits and join them: + +```js run +let str = "+7(903)-123-45-67"; + +alert( str.match(/\d/g).join('') ); // 79031234567 +``` + +An alternative, shorter way is to find non-digits `\D` and remove them from the string: + + +```js run +let str = "+7(903)-123-45-67"; + +alert( str.replace(/\D/g, "") ); // 79031234567 +``` + +## Spaces are regular characters + +Usually we pay little attention to spaces. For us strings `subject:1-5` and `subject:1 - 5` are nearly identical. + +But if a regexp doesn't take spaces into account, it may fail to work. + +Let's try to find digits separated by a dash: + +```js run +alert( "1 - 5".match(/\d-\d/) ); // null, no match! +``` + +Here we fix it by adding spaces into the regexp `pattern:\d - \d`: + +```js run +alert( "1 - 5".match(/\d - \d/) ); // 1 - 5, now it works +``` + +**A space is a character. Equal in importance with any other character.** + +Of course, spaces in a regexp are needed only if we look for them. Extra spaces (just like any other extra characters) may prevent a match: + +```js run +alert( "1-5".match(/\d - \d/) ); // null, because the string 1-5 has no spaces +``` + +In other words, in a regular expression all characters matter, spaces too. + +## A dot is any character + +The dot `"."` is a special character class that matches "any character except a newline". + +For instance: + +```js run +alert( "Z".match(/./) ); // Z +``` + +Or in the middle of a regexp: + +```js run +let reg = /CS.4/; + +alert( "CSS4".match(reg) ); // CSS4 +alert( "CS-4".match(reg) ); // CS-4 +alert( "CS 4".match(reg) ); // CS 4 (space is also a character) +``` + +Please note that the dot means "any character", but not the "absense of a character". There must be a character to match it: + +```js run +alert( "CS4".match(/CS.4/) ); // null, no match because there's no character for the dot +``` + +### The dotall "s" flag + +Usually a dot doesn't match a newline character. + +For instance, this doesn't match: + +```js run +alert( "A\nB".match(/A.B/) ); // null (no match) + +// a space character would match +// or a letter, but not \n +``` + +Sometimes it's inconvenient, we really want "any character", newline included. + +That's what `s` flag does. If a regexp has it, then the dot `"."` match literally any character: + +```js run +alert( "A\nB".match(/A.B/s) ); // A\nB (match!) +``` + + +## Summary + +There exist following character classes: + +- `pattern:\d` -- digits. +- `pattern:\D` -- non-digits. +- `pattern:\s` -- space symbols, tabs, newlines. +- `pattern:\S` -- all but `pattern:\s`. +- `pattern:\w` -- English letters, digits, underscore `'_'`. +- `pattern:\W` -- all but `pattern:\w`. +- `pattern:.` -- any character if with the regexp `'s'` flag, otherwise any except a newline. + +...But that's not all! + +Modern Javascript also allows to look for characters by their Unicode properties, for instance: + +- A cyrillic letter is: `pattern:\p{Script=Cyrillic}` or `pattern:\p{sc=Cyrillic}`. +- A dash (be it a small hyphen `-` or a long dash `—`): `pattern:\p{Dash_Punctuation}` or `pattern:\p{pd}`. +- A currency symbol: `pattern:\p{Currency_Symbol}` or `pattern:\p{sc}`. +- ...And much more. Unicode has a lot of character categories that we can select from. + +These patterns require `'u'` regexp flag to work. More about that in the chapter [](info:regexp-unicode). diff --git a/03-regexp-character-classes/hello-java-boundaries.png b/03-regexp-character-classes/hello-java-boundaries.png new file mode 100644 index 0000000000000000000000000000000000000000..be4e99a02747f4baeac03be4b81f57ab5f00e44b GIT binary patch literal 3676 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 WBF57c%X0HB66t9hY1L}jNBtkW`5{dJ literal 0 HcmV?d00001 diff --git a/03-regexp-character-classes/hello-java-boundaries@2x.png b/03-regexp-character-classes/hello-java-boundaries@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..872addd51a181f49cdbd3eda8c89d07e48b3e4bc GIT binary patch literal 7648 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 bcuV?+ECEI@W&7r?WeZSN(on3BHxK?ljX#)_ literal 0 HcmV?d00001 diff --git a/03-regexp-character-classes/love-html5-classes.png b/03-regexp-character-classes/love-html5-classes.png new file mode 100644 index 0000000000000000000000000000000000000000..35de13655258f63e60f3258c4caf5fbd3af93385 GIT binary patch literal 4130 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 literal 0 HcmV?d00001 diff --git a/03-regexp-character-classes/love-html5-classes@2x.png b/03-regexp-character-classes/love-html5-classes@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..af01fbb054837ac26222ebefbbf32be866192645 GIT binary patch literal 8839 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 literal 0 HcmV?d00001 diff --git a/04-regexp-escaping/article.md b/04-regexp-escaping/article.md new file mode 100644 index 000000000..63fce1f52 --- /dev/null +++ b/04-regexp-escaping/article.md @@ -0,0 +1,99 @@ + +# Escaping, special characters + +As we've seen, a backslash `"\"` is used to denote character classes. So it's a special character in regexps (just like in a regular string). + +There are other special characters as well, that have special meaning in a regexp. They are used to do more powerful searches. Here's a full list of them: `pattern:[ \ ^ $ . | ? * + ( )`. + +Don't try to remember the list -- soon we'll deal with each of them separately and you'll know them by heart automatically. + +## Escaping + +Let's say we want to find a dot literally. Not "any character", but just a dot. + +To use a special character as a regular one, prepend it with a backslash: `pattern:\.`. + +That's also called "escaping a character". + +For example: +```js run +alert( "Chapter 5.1".match(/\d\.\d/) ); // 5.1 (match!) +alert( "Chapter 511".match(/\d\.\d/) ); // null (looking for a real dot \.) +``` + +Parentheses are also special characters, so if we want them, we should use `pattern:\(`. The example below looks for a string `"g()"`: + +```js run +alert( "function g()".match(/g\(\)/) ); // "g()" +``` + +If we're looking for a backslash `\`, it's a special character in both regular strings and regexps, so we should double it. + +```js run +alert( "1\\2".match(/\\/) ); // '\' +``` + +## A slash + +A slash symbol `'/'` is not a special character, but in JavaScript it is used to open and close the regexp: `pattern:/...pattern.../`, so we should escape it too. + +Here's what a search for a slash `'/'` looks like: + +```js run +alert( "/".match(/\//) ); // '/' +``` + +From the other hand, if we're not using `/.../`, but create a regexp using `new RegExp`, then we no need to escape it: + +```js run +alert( "/".match(new RegExp("/")) ); // '/' +``` + +## new RegExp + +If we are creating a regular expression with `new RegExp`, then we don't have to escape `/`, but need to do some other escaping. + +For instance, consider this: + +```js run +let reg = new RegExp("\d\.\d"); + +alert( "Chapter 5.1".match(reg) ); // null +``` + +It worked with `pattern:/\d\.\d/`, but with `new RegExp("\d\.\d")` it doesn't, why? + +The reason is that backslashes are "consumed" by a string. Remember, regular strings have their own special characters like `\n`, and a backslash is used for escaping. + +Please, take a look, what "\d\.\d" really is: + +```js run +alert("\d\.\d"); // d.d +``` + +The quotes "consume" backslashes and interpret them, for instance: + +- `\n` -- becomes a newline character, +- `\u1234` -- becomes the Unicode character with such code, +- ...And when there's no special meaning: like `\d` or `\z`, then the backslash is simply removed. + +So the call to `new RegExp` gets a string without backslashes. That's why it doesn't work! + +To fix it, we need to double backslashes, because quotes turn `\\` into `\`: + +```js run +*!* +let regStr = "\\d\\.\\d"; +*/!* +alert(regStr); // \d\.\d (correct now) + +let reg = new RegExp(regStr); + +alert( "Chapter 5.1".match(reg) ); // 5.1 +``` + +## Summary + +- To search special characters `pattern:[ \ ^ $ . | ? * + ( )` literally, we need to prepend them with `\` ("escape them"). +- We also need to escape `/` if we're inside `pattern:/.../` (but not inside `new RegExp`). +- When passing a string `new RegExp`, we need to double backslashes `\\`, cause strings consume one of them. diff --git a/05-regexp-character-sets-and-ranges/1-find-range-1/solution.md b/05-regexp-character-sets-and-ranges/1-find-range-1/solution.md new file mode 100644 index 000000000..a6d71f661 --- /dev/null +++ b/05-regexp-character-sets-and-ranges/1-find-range-1/solution.md @@ -0,0 +1,12 @@ +Answers: **no, yes**. + +- In the script `subject:Java` it doesn't match anything, because `pattern:[^script]` means "any character except given ones". So the regexp looks for `"Java"` followed by one such symbol, but there's a string end, no symbols after it. + + ```js run + alert( "Java".match(/Java[^script]/) ); // null + ``` +- Yes, because the regexp is case-insensitive, the `pattern:[^script]` part matches the character `"S"`. + + ```js run + alert( "JavaScript".match(/Java[^script]/) ); // "JavaS" + ``` diff --git a/05-regexp-character-sets-and-ranges/1-find-range-1/task.md b/05-regexp-character-sets-and-ranges/1-find-range-1/task.md new file mode 100644 index 000000000..5a48e01e7 --- /dev/null +++ b/05-regexp-character-sets-and-ranges/1-find-range-1/task.md @@ -0,0 +1,5 @@ +# Java[^script] + +We have a regexp `pattern:/Java[^script]/`. + +Does it match anything in the string `subject:Java`? In the string `subject:JavaScript`? diff --git a/05-regexp-character-sets-and-ranges/2-find-time-2-formats/solution.md b/05-regexp-character-sets-and-ranges/2-find-time-2-formats/solution.md new file mode 100644 index 000000000..91568d033 --- /dev/null +++ b/05-regexp-character-sets-and-ranges/2-find-time-2-formats/solution.md @@ -0,0 +1,8 @@ +Answer: `pattern:\d\d[-:]\d\d`. + +```js run +let reg = /\d\d[-:]\d\d/g; +alert( "Breakfast at 09:00. Dinner at 21-30".match(reg) ); // 09:00, 21-30 +``` + +Please note that the dash `pattern:'-'` has a special meaning in square brackets, but only between other characters, not when it's in the beginning or at the end, so we don't need to escape it. diff --git a/05-regexp-character-sets-and-ranges/2-find-time-2-formats/task.md b/05-regexp-character-sets-and-ranges/2-find-time-2-formats/task.md new file mode 100644 index 000000000..868115bdf --- /dev/null +++ b/05-regexp-character-sets-and-ranges/2-find-time-2-formats/task.md @@ -0,0 +1,12 @@ +# Find the time as hh:mm or hh-mm + +The time can be in the format `hours:minutes` or `hours-minutes`. Both hours and minutes have 2 digits: `09:00` or `21-30`. + +Write a regexp to find time: + +```js +let reg = /your regexp/g; +alert( "Breakfast at 09:00. Dinner at 21-30".match(reg) ); // 09:00, 21-30 +``` + +P.S. In this task we assume that the time is always correct, there's no need to filter out bad strings like "45:67". Later we'll deal with that too. diff --git a/05-regexp-character-sets-and-ranges/article.md b/05-regexp-character-sets-and-ranges/article.md new file mode 100644 index 000000000..5c8a8babb --- /dev/null +++ b/05-regexp-character-sets-and-ranges/article.md @@ -0,0 +1,114 @@ +# Sets and ranges [...] + +Several characters or character classes inside square brackets `[…]` mean to "search for any character among given". + +## Sets + +For instance, `pattern:[eao]` means any of the 3 characters: `'a'`, `'e'`, or `'o'`. + +That's called a *set*. Sets can be used in a regexp along with regular characters: + +```js run +// find [t or m], and then "op" +alert( "Mop top".match(/[tm]op/gi) ); // "Mop", "top" +``` + +Please note that although there are multiple characters in the set, they correspond to exactly one character in the match. + +So the example above gives no matches: + +```js run +// find "V", then [o or i], then "la" +alert( "Voila".match(/V[oi]la/) ); // null, no matches +``` + +The pattern assumes: + +- `pattern:V`, +- then *one* of the letters `pattern:[oi]`, +- then `pattern:la`. + +So there would be a match for `match:Vola` or `match:Vila`. + +## Ranges + +Square brackets may also contain *character ranges*. + +For instance, `pattern:[a-z]` is a character in range from `a` to `z`, and `pattern:[0-5]` is a digit from `0` to `5`. + +In the example below we're searching for `"x"` followed by two digits or letters from `A` to `F`: + +```js run +alert( "Exception 0xAF".match(/x[0-9A-F][0-9A-F]/g) ); // xAF +``` + +Please note that in the word `subject:Exception` there's a substring `subject:xce`. It didn't match the pattern, because the letters are lowercase, while in the set `pattern:[0-9A-F]` they are uppercase. + +If we want to find it too, then we can add a range `a-f`: `pattern:[0-9A-Fa-f]`. The `i` flag would allow lowercase too. + +**Character classes are shorthands for certain character sets.** + +For instance: + +- **\d** -- is the same as `pattern:[0-9]`, +- **\w** -- is the same as `pattern:[a-zA-Z0-9_]`, +- **\s** -- is the same as `pattern:[\t\n\v\f\r ]` plus few other unicode space characters. + +We can use character classes inside `[…]` as well. + +For instance, we want to match all wordly characters or a dash, for words like "twenty-third". We can't do it with `pattern:\w+`, because `pattern:\w` class does not include a dash. But we can use `pattern:[\w-]`. + +We also can use a combination of classes to cover every possible character, like `pattern:[\s\S]`. That matches spaces or non-spaces -- any character. That's wider than a dot `"."`, because the dot matches any character except a newline. + +## Excluding ranges + +Besides normal ranges, there are "excluding" ranges that look like `pattern:[^…]`. + +They are denoted by a caret character `^` at the start and match any character *except the given ones*. + +For instance: + +- `pattern:[^aeyo]` -- any character except `'a'`, `'e'`, `'y'` or `'o'`. +- `pattern:[^0-9]` -- any character except a digit, the same as `\D`. +- `pattern:[^\s]` -- any non-space character, same as `\S`. + +The example below looks for any characters except letters, digits and spaces: + +```js run +alert( "alice15@gmail.com".match(/[^\d\sA-Z]/gi) ); // @ and . +``` + +## No escaping in […] + +Usually when we want to find exactly the dot character, we need to escape it like `pattern:\.`. And if we need a backslash, then we use `pattern:\\`. + +In square brackets the vast majority of special characters can be used without escaping: + +- A dot `pattern:'.'`. +- A plus `pattern:'+'`. +- Parentheses `pattern:'( )'`. +- Dash `pattern:'-'` in the beginning or the end (where it does not define a range). +- A caret `pattern:'^'` if not in the beginning (where it means exclusion). +- And the opening square bracket `pattern:'['`. + +In other words, all special characters are allowed except where they mean something for square brackets. + +A dot `"."` inside square brackets means just a dot. The pattern `pattern:[.,]` would look for one of characters: either a dot or a comma. + +In the example below the regexp `pattern:[-().^+]` looks for one of the characters `-().^+`: + +```js run +// No need to escape +let reg = /[-().^+]/g; + +alert( "1 + 2 - 3".match(reg) ); // Matches +, - +``` + +...But if you decide to escape them "just in case", then there would be no harm: + +```js run +// Escaped everything +let reg = /[\-\(\)\.\^\+]/g; + +alert( "1 + 2 - 3".match(reg) ); // also works: +, - +``` diff --git a/07-regexp-quantifiers/1-find-text-manydots/solution.md b/07-regexp-quantifiers/1-find-text-manydots/solution.md new file mode 100644 index 000000000..d4ddb1369 --- /dev/null +++ b/07-regexp-quantifiers/1-find-text-manydots/solution.md @@ -0,0 +1,9 @@ + +Solution: + +```js run +let reg = /\.{3,}/g; +alert( "Hello!... How goes?.....".match(reg) ); // ..., ..... +``` + +Please note that the dot is a special character, so we have to escape it and insert as `\.`. diff --git a/07-regexp-quantifiers/1-find-text-manydots/task.md b/07-regexp-quantifiers/1-find-text-manydots/task.md new file mode 100644 index 000000000..6fd91bdcf --- /dev/null +++ b/07-regexp-quantifiers/1-find-text-manydots/task.md @@ -0,0 +1,14 @@ +importance: 5 + +--- + +# How to find an ellipsis "..." ? + +Create a regexp to find ellipsis: 3 (or more?) dots in a row. + +Check it: + +```js +let reg = /your regexp/g; +alert( "Hello!... How goes?.....".match(reg) ); // ..., ..... +``` diff --git a/07-regexp-quantifiers/2-find-html-colors-6hex/solution.md b/07-regexp-quantifiers/2-find-html-colors-6hex/solution.md new file mode 100644 index 000000000..ec871d05c --- /dev/null +++ b/07-regexp-quantifiers/2-find-html-colors-6hex/solution.md @@ -0,0 +1,31 @@ +We need to look for `#` followed by 6 hexadimal characters. + +A hexadimal character can be described as `pattern:[0-9a-fA-F]`. Or if we use the `i` flag, then just `pattern:[0-9a-f]`. + +Then we can look for 6 of them using the quantifier `pattern:{6}`. + +As a result, we have the regexp: `pattern:/#[a-f0-9]{6}/gi`. + +```js run +let reg = /#[a-f0-9]{6}/gi; + +let str = "color:#121212; background-color:#AA00ef bad-colors:f#fddee #fd2" + +alert( str.match(reg) ); // #121212,#AA00ef +``` + +The problem is that it finds the color in longer sequences: + +```js run +alert( "#12345678".match( /#[a-f0-9]{6}/gi ) ) // #12345678 +``` + +To fix that, we can add `pattern:\b` to the end: + +```js run +// color +alert( "#123456".match( /#[a-f0-9]{6}\b/gi ) ); // #123456 + +// not a color +alert( "#12345678".match( /#[a-f0-9]{6}\b/gi ) ); // null +``` diff --git a/07-regexp-quantifiers/2-find-html-colors-6hex/task.md b/07-regexp-quantifiers/2-find-html-colors-6hex/task.md new file mode 100644 index 000000000..1960a09c6 --- /dev/null +++ b/07-regexp-quantifiers/2-find-html-colors-6hex/task.md @@ -0,0 +1,15 @@ +# Regexp for HTML colors + +Create a regexp to search HTML-colors written as `#ABCDEF`: first `#` and then 6 hexadimal characters. + +An example of use: + +```js +let reg = /...your regexp.../ + +let str = "color:#121212; background-color:#AA00ef bad-colors:f#fddee #fd2 #12345678"; + +alert( str.match(reg) ) // #121212,#AA00ef +``` + +P.S. In this task we do not need other color formats like `#123` or `rgb(1,2,3)` etc. diff --git a/07-regexp-quantifiers/article.md b/07-regexp-quantifiers/article.md new file mode 100644 index 000000000..5ab592561 --- /dev/null +++ b/07-regexp-quantifiers/article.md @@ -0,0 +1,140 @@ +# Quantifiers +, *, ? and {n} + +Let's say we have a string like `+7(903)-123-45-67` and want to find all numbers in it. But unlike before, we are interested not in single digits, but full numbers: `7, 903, 123, 45, 67`. + +A number is a sequence of 1 or more digits `\d`. To mark how many we need, we need to append a *quantifier*. + +## Quantity {n} + +The simplest quantifier is a number in curly braces: `pattern:{n}`. + +A quantifier is appended to a character (or a character class, or a `[...]` set etc) and specifies how many we need. + +It has a few advanced forms, let's see examples: + +The exact count: `{5}` +: `pattern:\d{5}` denotes exactly 5 digits, the same as `pattern:\d\d\d\d\d`. + + The example below looks for a 5-digit number: + + ```js run + alert( "I'm 12345 years old".match(/\d{5}/) ); // "12345" + ``` + + We can add `\b` to exclude longer numbers: `pattern:\b\d{5}\b`. + +The range: `{3,5}`, match 3-5 times +: To find numbers from 3 to 5 digits we can put the limits into curly braces: `pattern:\d{3,5}` + + ```js run + alert( "I'm not 12, but 1234 years old".match(/\d{3,5}/) ); // "1234" + ``` + + We can omit the upper limit. + + Then a regexp `pattern:\d{3,}` looks for sequences of digits of length `3` or more: + + ```js run + alert( "I'm not 12, but 345678 years old".match(/\d{3,}/) ); // "345678" + ``` + +Let's return to the string `+7(903)-123-45-67`. + +A number is a sequence of one or more digits in a row. So the regexp is `pattern:\d{1,}`: + +```js run +let str = "+7(903)-123-45-67"; + +let numbers = str.match(/\d{1,}/g); + +alert(numbers); // 7,903,123,45,67 +``` + +## Shorthands + +There are shorthands for most used quantifiers: + +`+` +: Means "one or more", the same as `{1,}`. + + For instance, `pattern:\d+` looks for numbers: + + ```js run + let str = "+7(903)-123-45-67"; + + alert( str.match(/\d+/g) ); // 7,903,123,45,67 + ``` + +`?` +: Means "zero or one", the same as `{0,1}`. In other words, it makes the symbol optional. + + For instance, the pattern `pattern:ou?r` looks for `match:o` followed by zero or one `match:u`, and then `match:r`. + + So, `pattern:colou?r` finds both `match:color` and `match:colour`: + + ```js run + let str = "Should I write color or colour?"; + + alert( str.match(/colou?r/g) ); // color, colour + ``` + +`*` +: Means "zero or more", the same as `{0,}`. That is, the character may repeat any times or be absent. + + For example, `pattern:\d0*` looks for a digit followed by any number of zeroes: + + ```js run + alert( "100 10 1".match(/\d0*/g) ); // 100, 10, 1 + ``` + + Compare it with `'+'` (one or more): + + ```js run + alert( "100 10 1".match(/\d0+/g) ); // 100, 10 + // 1 not matched, as 0+ requires at least one zero + ``` + +## More examples + +Quantifiers are used very often. They serve as the main "building block" of complex regular expressions, so let's see more examples. + +Regexp "decimal fraction" (a number with a floating point): `pattern:\d+\.\d+` +: In action: + ```js run + alert( "0 1 12.345 7890".match(/\d+\.\d+/g) ); // 12.345 + ``` + +Regexp "open HTML-tag without attributes", like `` or `

`: `pattern:/<[a-z]+>/i` +: In action: + + ```js run + alert( " ... ".match(/<[a-z]+>/gi) ); // + ``` + + We look for character `pattern:'<'` followed by one or more English letters, and then `pattern:'>'`. + +Regexp "open HTML-tag without attributes" (improved): `pattern:/<[a-z][a-z0-9]*>/i` +: Better regexp: according to the standard, HTML tag name may have a digit at any position except the first one, like `

`. + + ```js run + alert( "

Hi!

".match(/<[a-z][a-z0-9]*>/gi) ); //

+ ``` + +Regexp "opening or closing HTML-tag without attributes": `pattern:/<\/?[a-z][a-z0-9]*>/i` +: We added an optional slash `pattern:/?` before the tag. Had to escape it with a backslash, otherwise JavaScript would think it is the pattern end. + + ```js run + alert( "

Hi!

".match(/<\/?[a-z][a-z0-9]*>/gi) ); //

,

+ ``` + +```smart header="To make a regexp more precise, we often need make it more complex" +We can see one common rule in these examples: the more precise is the regular expression -- the longer and more complex it is. + +For instance, for HTML tags we could use a simpler regexp: `pattern:<\w+>`. + +...But because `pattern:\w` means any English letter or a digit or `'_'`, the regexp also matches non-tags, for instance `match:<_>`. So it's much simpler than `pattern:<[a-z][a-z0-9]*>`, but less reliable. + +Are we ok with `pattern:<\w+>` or we need `pattern:<[a-z][a-z0-9]*>`? + +In real life both variants are acceptable. Depends on how tolerant we can be to "extra" matches and whether it's difficult or not to filter them out by other means. +``` diff --git a/08-regexp-greedy-and-lazy/1-lazy-greedy/solution.md b/08-regexp-greedy-and-lazy/1-lazy-greedy/solution.md new file mode 100644 index 000000000..b8e022223 --- /dev/null +++ b/08-regexp-greedy-and-lazy/1-lazy-greedy/solution.md @@ -0,0 +1,6 @@ + +The result is: `match:123 4`. + +First the lazy `pattern:\d+?` tries to take as little digits as it can, but it has to reach the space, so it takes `match:123`. + +Then the second `\d+?` takes only one digit, because that's enough. diff --git a/08-regexp-greedy-and-lazy/1-lazy-greedy/task.md b/08-regexp-greedy-and-lazy/1-lazy-greedy/task.md new file mode 100644 index 000000000..b46f55917 --- /dev/null +++ b/08-regexp-greedy-and-lazy/1-lazy-greedy/task.md @@ -0,0 +1,7 @@ +# A match for /d+? d+?/ + +What's the match here? + +```js +"123 456".match(/\d+? \d+?/g) ); // ? +``` diff --git a/08-regexp-greedy-and-lazy/3-find-html-comments/solution.md b/08-regexp-greedy-and-lazy/3-find-html-comments/solution.md new file mode 100644 index 000000000..b36eec4a0 --- /dev/null +++ b/08-regexp-greedy-and-lazy/3-find-html-comments/solution.md @@ -0,0 +1,17 @@ +We need to find the beginning of the comment `match:`. + +The first idea could be `pattern:` -- the lazy quantifier makes the dot stop right before `match:-->`. + +But a dot in Javascript means "any symbol except the newline". So multiline comments won't be found. + +We can use `pattern:[\s\S]` instead of the dot to match "anything": + +```js run +let reg = //g; + +let str = `... .. .. +`; + +alert( str.match(reg) ); // '', '' +``` diff --git a/08-regexp-greedy-and-lazy/3-find-html-comments/task.md b/08-regexp-greedy-and-lazy/3-find-html-comments/task.md new file mode 100644 index 000000000..81fd5c634 --- /dev/null +++ b/08-regexp-greedy-and-lazy/3-find-html-comments/task.md @@ -0,0 +1,13 @@ +# Find HTML comments + +Find all HTML comments in the text: + +```js +let reg = /your regexp/g; + +let str = `... .. .. +`; + +alert( str.match(reg) ); // '', '' +``` diff --git a/08-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/solution.md b/08-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/solution.md new file mode 100644 index 000000000..c453926fa --- /dev/null +++ b/08-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/solution.md @@ -0,0 +1,10 @@ + +The solution is `pattern:<[^<>]+>`. + +```js run +let reg = /<[^<>]+>/g; + +let str = '<> '; + +alert( str.match(reg) ); // '', '', '' +``` diff --git a/08-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/task.md b/08-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/task.md new file mode 100644 index 000000000..2cf48a4ae --- /dev/null +++ b/08-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/task.md @@ -0,0 +1,15 @@ +# Find HTML tags + +Create a regular expression to find all (opening and closing) HTML tags with their attributes. + +An example of use: + +```js run +let reg = /your regexp/g; + +let str = '<> '; + +alert( str.match(reg) ); // '', '', '' +``` + +Let's assume that may not contain `<` and `>` inside (in quotes too), that simplifies things a bit. diff --git a/08-regexp-greedy-and-lazy/article.md b/08-regexp-greedy-and-lazy/article.md new file mode 100644 index 000000000..f7c6de43d --- /dev/null +++ b/08-regexp-greedy-and-lazy/article.md @@ -0,0 +1,304 @@ +# Greedy and lazy quantifiers + +Quantifiers are very simple from the first sight, but in fact they can be tricky. + +We should understand how the search works very well if we plan to look for something more complex than `pattern:/\d+/`. + +Let's take the following task as an example. + +We have a text and need to replace all quotes `"..."` with guillemet marks: `«...»`. They are preferred for typography in many countries. + +For instance: `"Hello, world"` should become `«Hello, world»`. Some countries prefer other quotes, like `„Witam, świat!”` (Polish) or `「你好,世界」` (Chinese), but for our task let's choose `«...»`. + +The first thing to do is to locate quoted strings, and then we can replace them. + +A regular expression like `pattern:/".+"/g` (a quote, then something, then the other quote) may seem like a good fit, but it isn't! + +Let's try it: + +```js run +let reg = /".+"/g; + +let str = 'a "witch" and her "broom" is one'; + +alert( str.match(reg) ); // "witch" and her "broom" +``` + +...We can see that it works not as intended! + +Instead of finding two matches `match:"witch"` and `match:"broom"`, it finds one: `match:"witch" and her "broom"`. + +That can be described as "greediness is the cause of all evil". + +## Greedy search + +To find a match, the regular expression engine uses the following algorithm: + +- For every position in the string + - Match the pattern at that position. + - If there's no match, go to the next position. + +These common words do not make it obvious why the regexp fails, so let's elaborate how the search works for the pattern `pattern:".+"`. + +1. The first pattern character is a quote `pattern:"`. + + The regular expression engine tries to find it at the zero position of the source string `subject:a "witch" and her "broom" is one`, but there's `subject:a` there, so there's immediately no match. + + Then it advances: goes to the next positions in the source string and tries to find the first character of the pattern there, and finally finds the quote at the 3rd position: + + ![](witch_greedy1.png) + +2. The quote is detected, and then the engine tries to find a match for the rest of the pattern. It tries to see if the rest of the subject string conforms to `pattern:.+"`. + + In our case the next pattern character is `pattern:.` (a dot). It denotes "any character except a newline", so the next string letter `match:'w'` fits: + + ![](witch_greedy2.png) + +3. Then the dot repeats because of the quantifier `pattern:.+`. The regular expression engine builds the match by taking characters one by one while it is possible. + + ...When it becomes impossible? All characters match the dot, so it only stops when it reaches the end of the string: + + ![](witch_greedy3.png) + +4. Now the engine finished repeating for `pattern:.+` and tries to find the next character of the pattern. It's the quote `pattern:"`. But there's a problem: the string has finished, there are no more characters! + + The regular expression engine understands that it took too many `pattern:.+` and starts to *backtrack*. + + In other words, it shortens the match for the quantifier by one character: + + ![](witch_greedy4.png) + + Now it assumes that `pattern:.+` ends one character before the end and tries to match the rest of the pattern from that position. + + If there were a quote there, then that would be the end, but the last character is `subject:'e'`, so there's no match. + +5. ...So the engine decreases the number of repetitions of `pattern:.+` by one more character: + + ![](witch_greedy5.png) + + The quote `pattern:'"'` does not match `subject:'n'`. + +6. The engine keep backtracking: it decreases the count of repetition for `pattern:'.'` until the rest of the pattern (in our case `pattern:'"'`) matches: + + ![](witch_greedy6.png) + +7. The match is complete. + +8. So the first match is `match:"witch" and her "broom"`. The further search starts where the first match ends, but there are no more quotes in the rest of the string `subject:is one`, so no more results. + +That's probably not what we expected, but that's how it works. + +**In the greedy mode (by default) the quantifier is repeated as many times as possible.** + +The regexp engine tries to fetch as many characters as it can by `pattern:.+`, and then shortens that one by one. + +For our task we want another thing. That's what the lazy quantifier mode is for. + +## Lazy mode + +The lazy mode of quantifier is an opposite to the greedy mode. It means: "repeat minimal number of times". + +We can enable it by putting a question mark `pattern:'?'` after the quantifier, so that it becomes `pattern:*?` or `pattern:+?` or even `pattern:??` for `pattern:'?'`. + +To make things clear: usually a question mark `pattern:?` is a quantifier by itself (zero or one), but if added *after another quantifier (or even itself)* it gets another meaning -- it switches the matching mode from greedy to lazy. + +The regexp `pattern:/".+?"/g` works as intended: it finds `match:"witch"` and `match:"broom"`: + +```js run +let reg = /".+?"/g; + +let str = 'a "witch" and her "broom" is one'; + +alert( str.match(reg) ); // witch, broom +``` + +To clearly understand the change, let's trace the search step by step. + +1. The first step is the same: it finds the pattern start `pattern:'"'` at the 3rd position: + + ![](witch_greedy1.png) + +2. The next step is also similar: the engine finds a match for the dot `pattern:'.'`: + + ![](witch_greedy2.png) + +3. And now the search goes differently. Because we have a lazy mode for `pattern:+?`, the engine doesn't try to match a dot one more time, but stops and tries to match the rest of the pattern `pattern:'"'` right now: + + ![](witch_lazy3.png) + + If there were a quote there, then the search would end, but there's `'i'`, so there's no match. +4. Then the regular expression engine increases the number of repetitions for the dot and tries one more time: + + ![](witch_lazy4.png) + + Failure again. Then the number of repetitions is increased again and again... +5. ...Till the match for the rest of the pattern is found: + + ![](witch_lazy5.png) + +6. The next search starts from the end of the current match and yield one more result: + + ![](witch_lazy6.png) + +In this example we saw how the lazy mode works for `pattern:+?`. Quantifiers `pattern:+?` and `pattern:??` work the similar way -- the regexp engine increases the number of repetitions only if the rest of the pattern can't match on the given position. + +**Laziness is only enabled for the quantifier with `?`.** + +Other quantifiers remain greedy. + +For instance: + +```js run +alert( "123 456".match(/\d+ \d+?/g) ); // 123 4 +``` + +1. The pattern `pattern:\d+` tries to match as many numbers as it can (greedy mode), so it finds `match:123` and stops, because the next character is a space `pattern:' '`. +2. Then there's a space in pattern, it matches. +3. Then there's `pattern:\d+?`. The quantifier is in lazy mode, so it finds one digit `match:4` and tries to check if the rest of the pattern matches from there. + + ...But there's nothing in the pattern after `pattern:\d+?`. + + The lazy mode doesn't repeat anything without a need. The pattern finished, so we're done. We have a match `match:123 4`. +4. The next search starts from the character `5`. + +```smart header="Optimizations" +Modern regular expression engines can optimize internal algorithms to work faster. So they may work a bit different from the described algorithm. + +But to understand how regular expressions work and to build regular expressions, we don't need to know about that. They are only used internally to optimize things. + +Complex regular expressions are hard to optimize, so the search may work exactly as described as well. +``` + +## Alternative approach + +With regexps, there's often more than one way to do the same thing. + +In our case we can find quoted strings without lazy mode using the regexp `pattern:"[^"]+"`: + +```js run +let reg = /"[^"]+"/g; + +let str = 'a "witch" and her "broom" is one'; + +alert( str.match(reg) ); // witch, broom +``` + +The regexp `pattern:"[^"]+"` gives correct results, because it looks for a quote `pattern:'"'` followed by one or more non-quotes `pattern:[^"]`, and then the closing quote. + +When the regexp engine looks for `pattern:[^"]+` it stops the repetitions when it meets the closing quote, and we're done. + +Please note, that this logic does not replace lazy quantifiers! + +It is just different. There are times when we need one or another. + +**Let's see an example where lazy quantifiers fail and this variant works right.** + +For instance, we want to find links of the form ``, with any `href`. + +Which regular expression to use? + +The first idea might be: `pattern://g`. + +Let's check it: +```js run +let str = '......'; +let reg = //g; + +// Works! +alert( str.match(reg) ); // +``` + +It worked. But let's see what happens if there are many links in the text? + +```js run +let str = '...... ...'; +let reg = //g; + +// Whoops! Two links in one match! +alert( str.match(reg) ); // ... +``` + +Now the result is wrong for the same reason as our "witches" example. The quantifier `pattern:.*` took too many characters. + +The match looks like this: + +```html + +... +``` + +Let's modify the pattern by making the quantifier `pattern:.*?` lazy: + +```js run +let str = '...... ...'; +let reg = //g; + +// Works! +alert( str.match(reg) ); // , +``` + +Now it seems to work, there are two matches: + +```html + +... +``` + +...But let's test it on one more text input: + +```js run +let str = '......

...'; +let reg = //g; + +// Wrong match! +alert( str.match(reg) ); // ...

+``` + +Now it fails. The match includes not just a link, but also a lot of text after it, including ``. + +Why? + +That's what's going on: + +1. First the regexp finds a link start `match:` (none). +3. Then takes another character into `pattern:.*?`, and so on... until it finally reaches `match:" class="doc">`. + +But the problem is: that's already beyound the link, in another tag `

`. Not what we want. + +Here's the picture of the match aligned with the text: + +```html + +...

+``` + +So the laziness did not work for us here. + +We need the pattern to look for ``, but both greedy and lazy variants have problems. + +The correct variant would be: `pattern:href="[^"]*"`. It will take all characters inside the `href` attribute till the nearest quote, just what we need. + +A working example: + +```js run +let str1 = '......

...'; +let str2 = '...... ...'; +let reg = //g; + +// Works! +alert( str1.match(reg) ); // null, no matches, that's correct +alert( str2.match(reg) ); // , +``` + +## Summary + +Quantifiers have two modes of work: + +Greedy +: By default the regular expression engine tries to repeat the quantifier as many times as possible. For instance, `pattern:\d+` consumes all possible digits. When it becomes impossible to consume more (no more digits or string end), then it continues to match the rest of the pattern. If there's no match then it decreases the number of repetitions (backtracks) and tries again. + +Lazy +: Enabled by the question mark `pattern:?` after the quantifier. The regexp engine tries to match the rest of the pattern before each repetition of the quantifier. + +As we've seen, the lazy mode is not a "panacea" from the greedy search. An alternative is a "fine-tuned" greedy search, with exclusions. Soon we'll see more examples of it. diff --git a/08-regexp-greedy-and-lazy/witch_greedy1.png b/08-regexp-greedy-and-lazy/witch_greedy1.png new file mode 100644 index 0000000000000000000000000000000000000000..5a0897738aadd6539f3908e69963a05c20fccca1 GIT binary patch literal 8282 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 U%vvY0FP22}iJmI>k!|e%0Q^@2Pyhe` literal 0 HcmV?d00001 diff --git a/08-regexp-greedy-and-lazy/witch_greedy3.png b/08-regexp-greedy-and-lazy/witch_greedy3.png new file mode 100644 index 0000000000000000000000000000000000000000..e3a50ff3982bfaacd82452f85a41edd162795572 GIT binary patch literal 10386 zcmc(_RahKt6DM*Wm6BGq977{O9)U zyM5E$@7wj1b#*_hdR3T`f+Q-^J0vJ5C{$@FaTO>i=(0c8<_K_qj>MmfDgK3tkg7U90gM<*V2YpApNML{33nYu{V0>H!&uy z(4!xp5&PDC;;MTzEzK=?o$>6;-e&~gR48mC7`bnVKnnju4}9;vNpM=gGK0*b0j86{ zpKVrX>h8CpBC{+-NxC}4$aF&A;R|P+dHW7W z{TD`q=%HpoY`K6+l}zi?1A4k=h@YthZZ_)Ef9g_2P~P^?mKA-JzDVm>>Is*dDNN*7|RMPF9qd5r==U9aF?PS86ITJ zR~_#QIC;J*nsh~Uxeo_nnS5|GLo(Kv*D6R{n%*pK%s5CBvH$2m^| zqT(G$pHI`)Hc@HL2fL|4pF}Oh3Fz@8YdM-b;Muh;5{4ONV^0vBul63N;+IItt@l;Y?Yx~1OvB(5B(_1iUe`!2`Z%S0!9+dSWImtD|k zUFi7ZCkDLNksKo^W;QVke7P^@5&`RaI$O8s=8`0+;v|sjxD%;l3` z!_HV;3l|q-BHTPiq0{wTnpLh(A@%tk1~KD4$>i6j*o~c6-p!8occ45|cDWIDN$ZQ8 zYrCYoHb~Fni0XLydzTXd@<{n%CmQB=55rfk8$EodXW48I1tR*Wk3qLyQK!hixH>Ni zxA4ck9gb&C{SWoIbwyDxJ%6bH^MR)3|zn;(r(cc zke$M)1YU%kkCxHJk&f<3)WbnE=qpSOU~=C{joe1+d<@g9?}6+LT$&TT@4bpoSSlBl z0Cc8J9gW<2?q6wwOm+ObL@^Oij)nEuT{d^6Ge*{d?`n}MZDf(>(KV1ZR9>e9N zKetcw!S?m%y?KhqJjKuJXI4=` z;}bSi2e*8|U7CE4BVD4u+}4{)Mqja9IalU|fl?Hgq0KPRoho`OH9^{Ot7#>sWN#Jl zW7mZC{O1(h&+tT`ETgR0RBk`g*7RMl0&zlP3piiuKn;bAz2}mF7BCWU>*&!xiP-jw zTT_ToI+$90mzj(w^m!6#{)7LeeANX!Tp+sM znJL#ml(&rPTo#u6fZ>s%1n*{+qfYBRfArXBgEc;&7wV1gPsypWYN{BQk8}Qbpwv@G zpP6h$h0x^0>5ykv`U}kNHZKEP_)+p_83t^oSdEXO`*h_3dry3hvrR3y^Qp- zR$UepUkH)j>dne;f}TzK1gfZ#10@vD*9IaxRIRQn4EgvELh>U<9)~Crp!b*gp-JXE z{^<#JzDg%J%T z`CtgbddG62G))_d#kXGx2r}gb7;W-O|2o7Xj!fYiNk%!#F6!*?$p9}YeM+mdqJKV zhw3sKojSY2uoy4mlMPiFJk@+R!C}Y`dnd{pf?Gboqd`+WeLw!R>QhSamylYy6*c83 zrn;wqwK6!nty9)d89b)Tg$O8CKTd{c!9iG-G zm`lOJR+7Y4x8Wr7$xayA_g=K9{7lvYj)#v;`OlFVngri-~6expb=x_~eOkxi$ng)%XXnMWjCl`-;72 zfA{t8w7gqhj{%#xI#wW%tN?D_Wp};zGhAX5?I?QGaS%ta_i{aoYeyQLTVhkO>p#A` zuwoD@KQEOrEp;C+=OB1K_eD=s=&(h*CvNd(FVL~xBR2!-hK1I)kN&Phe(4=QhV&clS4p+g&&Qp49ik_1m5_Gq9q>}eB#&%>F}xJ z`$egZ^e5%DeZAHeuL3s{C0-lv-ZBmqt-+Y-eGD`!{=Vp?_ymBgoK5+Rg)Y_{;?-Ix z9gS*+|a2wMVCqSDsDyD9bOg(yZj22bCI$Bz(H7sp!&X7@u)w_IO~jvUOOC@Pk(W>QK4h< zKk>Lf%0ALCJ{5brc)T3?kD*pM8vUl45 z>&RN)|Aq54Dm=3P#Q!RzKyLgO1wQ5EW|zj2fd@-^PVEp-JX38msy)80Fo?IFMi<(d zxxZRq*lin6@XL$!)~$@gaJ=QLuJ*s5Qa$J2VJRdusz*j{G_^HE3FZ8wul z7^H>Dya`Ahz4<`62gexqi4C&Z$E1m$c)~K8mgE3iVJ21r%IqJ>e@;7_E0|9+Ms{W% z2f}=o`Q9|>!;L4@n8)Nn2g-ITwFa@D@3p*SH#HyMH1^FVndvT*MysRM&PdxX_Q!vA zPWy2j>5!%a#wicjo4g~lKAkw*_}gO0*g6&B@TtoB6lF2}dM9&sJ;7?L3$|SE#V;+5 zaj3temyO6Mm=i87=XKXrUGH6&sx>~(QAy?9YVY+~KK(jmxSRl@2R2tV)TW6>0<>lU z5%ZMQ@>gtM+0}pRRz_7bgv=MUCW3aZ&KzcQ($KZ3f2G>6YnptKddZ)uojutg*ujv! zX-L;+4C6?sm4g%juL}v@ywp*i#V2HVT-m<;+n>ay(1T*w%p2@Lzxfxp5`s=X-`;ux zC%rP86Jt>UgOx4XP5lX_Z+1sS!_&qfd{6q)cJ&FO>GOLmf+7!bI!pGVa2@~vDK8|N zqWgp#FqhTG%=2ueUyh=W?R6R>Jp89gQX>B8uTB`P!uRFeZo+1WN&lfBZw5V4ZGI5_ z22v!rb$Z?`*J}|pyDvmz7gC&mPvXWE^9E*x)TDY_!KfJJj6;qI-@U+NoaI!jk^gp- z%rBnA_cK*c*>q3Oc#694KAfhSijAaGgBO!oN`?E6Rf>~9ga-KXVkj!TuJ8}#-!JRr z&MR`DVu^$YVWI8OYc-uMYewjNJsH00VR%)=7)t9Z<8F_?V}|Uicb~g{A&;8Yp^>HQ z>}j0#EZxRJxt?gi|IpVWW}1QI)o>fx!y46rmxyy~C+u8&v7O)?S?Ms_5!wFsQbzN$ z0E=ACY0`PB)arE&_M>9PP3`#Kbhh00P}(9=c=$`w&E4 z@qR+ay^Su(7^}AY&qq z*-w4hZnMY)wOLQq-HA%*#dDfQ8V(UDKi4ZszA>s2Tfdi_-uDlh*LUmcYLT0Ys&|+I zWzwT;74r&C3(;1THD@=*k%!z;34e~WpU~I4{o>rBwPtFE4vK$10)#HQ%}a=Y@v68M8AUiRGI9!_w3HXCUix*q zh^%14HOfFv!iyZVJAIvZXAIV>0lwE|*yDDMpNm*+cj>nwvpfzfy`8N~l2bZx(uGK0 zWi`NYOYf;2kQnMdwoiBFS42=8p+Jk&ZIyI~h|x#8>c!6;+J% z-Mjfuex(eKSwrX|GX+^#hw6SDuMDSLf7kWBLR+-~Sf!>D6QU<yVama4@#^8z;OYinbUP` zLu*Txd$v@b-Q2s&2S5$Hq;}LG!iq?Lnr)nQGQfL4kF;X-Ge;=%^G+cI!U_Mp9Gi_N zO{>|?Hsh`4XdU3yQAo?a1)J#l6~eHp5;|yf5FlD&hou3EA_%47aO#!4025jo)B3$V z5=W^bh%Q(NhbHCee~%)UE!{EQ5xq$V>ZI&vgXm&d)yHr0j!e(@t_1vsFRi$_yfYrT z^sI9mc+>9#^>9WynsSfhu^)2>4KRpxaHvafcJSHF{K=K`L5o@(bR*|yqpP!+Af*&Vm2zpYvx?C0+6ntgs*gn4nt{Kf& z;&t^G>|0+){PBouD5}6Q){6wPNfeEe)~wFG2IP=82qyoghy)>@CNQbbZ`Ig6yA02P z$m^OpZj|aUm?q8OEu{((eo27hR&b9j{b2MA@7jO|9;AS{dK8))Iv39>-%4Dug*%bn z2q%()9F!j7CVW~VG^s;IdgH-lw-x1UU2q=4urB4Xen9ndv>R8GnlVsCdp`qfi~VxI zy}9x3waIma+&1cLD4+~>shK!PLneF=n-&TfQm2@in8_7*=Yimd{AfcK2tjz@>z~6f zIT9C(>CCg*v5*WN6=qINXz!{zMU0HY7qzG1^P)0?6T5Xu_1(an-@{9S3^Q#lNQW4G zrcAS##Y$DkFo0+tKXsbv@y0JWT<8-uE{x$-;l? zw{hK=jb!_q_CYo3teu*|0jQI9q&YLb?$dPsxTx-${{ZXE3d3Fy2nlp4ucBh*?pAOs z!^3-Xd`~`2QC(Q0Wn|E@y~|6}ymLEGq7~Ta`Vnjp`}tC&B2)2`XE8M;jdC;ks_}Af zRYL<5$&{tk70S$nwaoCw(9t_!PT*2&E2G;xV0=x%4J%lVeJ%C7`lNPjS~_tGE6&EV zfBz!S<7hY2R%1RyAT*jk>yQi#cXtEAx&GK}e2-(1&HqJm`Attd_98(E%7mItxu7D+ z0=n*{pM<^QU8`Mu#^nvA5ixj^(9ijLGf54*uf9*+euZakvv68 z8S)3X>lC~~r*X-B*i3RxbE6|QrLjR=4^%e}1x{>*ar?Ld)|dVQks^Z3h=ehT5mW1T zrl616W(6CiXR>@%bA4#a^_E>-_T+SQC0$)NvZwqmpAJ9~A3eIq6HauVhoWR>bpqI|H)??Zw35m1FOj;337FQm%>p(~UonhWc=5q${$M z@yy1&e`zQ=u4vn%Rm^V#v}p7s)1JH=x+H?o`p&Na3M!8kL`Wb2kF}|*8wSHv*AN)o zi;D}B_YsF#OE1|~{SOs_V==VoguX;VR_|Tfl^eW7hhE87FPnY8AWi%*(;~s3VVZvo z{k3;)&bVBLpV7jYcIJ$=+&!l;8yB9d+Uq55z)PBMPN>Wd!E)n^Hw$D?@d&kRHaS8 zUE`E%M6$+~5|~EGk(zJf=9)J^x9Vq=xuG1mX!!=+_j)WNel_Xks>xxqzsUGs9jLlj4$YtG@`eLRX#sUy4R_|&reeBlS_l{QTW~xh2TUSkLUoK+i zJwMVeo?!CJ3;%dN=@T>6`mQ+&+pbd*YfYtHKkf)7#@6d%EfAo&tEy|*{=Da)VAx}J zu0}(gR>(aj)35Q|P~$s0K~ZHpO`7W#J7Do|qLdv@=2)fjGZ}a$fI7#TycJqK6F} ztOssCD_y2yvwoN+e$?XXNmjPs4!&1?;a!ClsikD6m;>`DBkj{rmIVZ_$VVZUQLyJk5+DHTzL7{$s&2c3OyFGLS}qN0nfHA3p-+GAPVtBJK_84|i6AbTgAe zaE-0)yRBLb&goE|Ipx%R|uL0C4w;l`s%u0oEij|AZ*ReDW}`OqomCF-)gR|YMQ8&AxY zN}?bGNy?ya^vTwg7I`Mho%m&M9m>btDDf-rOEZyNMt=B)2$eyG&05{i7} z-^+_2pHjQY99uS5lmk*ilWpUWXK{>IC=p7?7n5pg7L3ab|hZI0Vbjc&>cJoYQR zm(KfJG@24&bdFDKr7gTFxeqjiK;t40WIHkW=>zW1xa?u6$6 z#vFyv>W`9ZMQ#ZZx+s;YfAXq$g8M8&099_Z^Onj+;w#+3=_D_VrW7zxMZq?rz1L%}p-nC;cHeYlNd?u-jKE6L`-qsI7@!+RyA4nJKyIpbTTBz7F5X~lAya*Ev8 zP1M7(XRifj4}Zmk>@KNr>uN*{uyoEqlP zxs89I4Wtz+o&?4V2R64MD)XaIqn=CNnQBbIrmM9Qm}nuc=X5n+tYC5b78!qMrCJFu z1C^g1&hN2AoZzSWvbw_2uRTxDe%9p(9^E*^8=+c-R zf5)!d)R}!x**1qdzWY1P4#~#dl04pYk<41m3lWFVzcs=?j-^@xznPGB6Rpcw)RXl1 zsK1Esj9-24B=%owX2Dziv~O0yfBBjF^M7iqup+L-1&f$-iL9mVb#V-3L)xWXXX4UqoxqLu?!#j z$7!YOavvw582wwVk;shb+wS1;I86HJ0HK$=qtz4kk9C6|u<1`tr=rG}znb;&TOU#7l0;@)V;eE^BDd8w7x;}t&Qt3_jY&z6c4~J5-@(t#;PuR+&1r~nM_It?U zrFpr~N zxOb^2Wi=Da0;p^_<|&q$uE^ zwHaq!l~Y;6B>)9Nj(=loY|F47#)3yEd8@_VyGmJEidc{r-CkgXQVD1(TyYt})9fe9 z=ev8j>9{^g#wPQje?NNYrR>*HTMtA*PIV*h(i#mL-g2UQvqj(8w%qi5^+QX@^|FtK zvRzMCw+%lIWwFV3FXNt_n8ldE4n;^t5MR^zC>F{m#Aw3abl6PSe6o=1y)b_4W;bJd zcNlsU#A#t-pi5QRshq@(?c(R5-Y8C$mSH=aPO0Wqr>mruK(TPcQQo;t56FJMjU zCjIuAo|-vTqq|{EqCw23XN*P$#cme9Nb$Di;0Zoff4)}I>kydh0P7+;p@AkPADG#> zpl(47rrZ8()5rDMxoI<8#wUy}Bc;HwG5Li}eKY_I`IzgpO8zVKl%*NvEh0Yq$^CKy+VC*#@D#5mC6f+!?n$v6y&*6(T}7>%#FPPsy%@UKC=AQP znibSpEyjAIfnc`C-m6BnS%pCY2og0z&TJDbUngNi;qskxL^6b1jn8I{MAm67-{OPn zQQU?LijoI{JLx#Bfy}w*;AX+@tkQRqU5nqlhLC(0qf%4+wZK9ShWd4LN9)DkG_z7J z@dx9=iMFf{Q_odf7Kn@D8$5L`S?|`nMh6V~@*S_@dV0EYri`gA-gjg=+Os!Zr=4v` z9U*Z@eHTCyu5=`XV*_V)3E@Uvw$O3xl2R%5D7~B|s<=_Kkxq7`3&{+iS;F{4MoNzv zGYmB190eNqu=F?fZ0&mr9wn(@EiGp$VQGq4Pc0f23j$wqGBSOIAmMMQ8TXYQ`I`$< zqqweZSN5Oi4*AATt+JAP=V&5lU&NGJV~mt|L?BS?r4IzD%wiMw@xDObVzUQ{6Q}&h zuffv4$M(hU@!(T(STRgoIx&|^_e^aGbrrsPmUQ*wMaUh|xm3Izh>pnm2p@3SbTZ?_ z+v8M!^a(zgb2tf4cXDsQX!A*I(?WX=HC0OPC%r4pz@ecTK1)}v^gV&$zNd$`D0sdE z{fFRPkNXXUcd`@ev6gfgyw?C#0q09l7(oKoee+!qPc?+MM_B2F<_D^Qgb$+i?q(4k z^<&SqdynDfr|frRk2_E~6NeeuE{`+?utYv4O$|FVi6)8-K9tbP$>TZvwB&2y)v~O2 z&7ETi8Bji3jy=}CjK|P_Vy1K@}b+992X+~sn7Yd9It*e4*klrh`;X+r-vcF{l}|})BK@yu8zJr|C^!0{4-D$4i(bz zkF)vzH7)Ne2$3=i@k_jwQrh8pWtq_ZU*N|C>CZ7;ueARR8A3@*D2P{y8h!siseT1!$K3i-pc;mR?5-iLtNW8-qDWB2Q zZM=bfG`7NPw0zXmsgeGIU}uv8bI_&vAbJ1=0w-bCaakW>vdguTFg0A%Fj;+Vyv~<2 zw!Qj81J6mZsVUC(N6qyzL;6lf#~`8Jq(cFYa|kcT`M^L@2NCE!0^(my*$??@`6B5V z5Q?R`;KOK2dfo~=_PJU7B1_AX^XL7izl7n}e#N;tf%YGn%j%aM=G6T%=;VGwn+{me z5o@X0 zLlQye(@K&}0sx${s##97bN^T~PpBd#Ja}?!>*Y3OhLI~n8&EN8{Si?8{->EHh-k(Y z{85V@J118?V3AEMY8L20>{Fw(ni2iqaUdY0efQJs19NcXAZVj#OVetQ$d{6PEdBCz zy?h^*r3@8nI;~1$+2wtMZ2lJE#s4(_G$j#Kkd6grr62p`c|R%dUuR#uJ^6_Md7F4D zimwb6_>Rf;k3*g)Mfv~_@87fyOd^4vCTO=s@!Jh&CG7ulGThML{D`T&;5W)QLtyvR z53m1c2H+<%f^k;D9i}M|w?yv@)UA4#^_Gk70(Hy2vrl%sA?Zc=UvuFG1^K;0g9v4S zHc66Xor^S*mIA;1W7Yd#W{9(V#1OW6iPtU3=fC&-sg!iaC$mQ@*yA?Q5KjLQzoikO zyJ#*Go?}9ZzY9b@VzY`D+ z5Ita#_v3znr1mE{edNvU8a=kr-2a0q|H<>b@D3)`fG+5LMf&G*|8P{1q&`#}#DB2P z&o2c5%)o;+^H0W!yr)7`hO)Q+5c$`<6*0hyf=!Hkhg9`zs4MnJTOYhDoef9Md9+0iD&knHGLuqvaxAiokHD(0sT zV2fZt)9OT+V#VQAP1C~G_9nkpE*yXEC93{i#J-h+`KfUC{m@3MXwt z8|J^dxL+7Dscxq_J7EV((>OXaX@!SvjW2ru%D%3m{3|Q-`bH;XTErV%0v{n_0 ztyx4Ed)QuQ`hNk`3uK=X`Q5S2zUDn~BKtgg$5K7rXF|CKFZGMKm+&poUr{oLdSj3T zcwHpVz6vS8$32>4EcN*AH2Dgh_MgCiJA&*}ChthA7y52nvN4WfZ3QjX_7ZLKv`9UVm_Zbm~_-J{JqvB7J{t zMKW$sXS{wM*WTh8j#762(@Fh%c%l)2-=X}SnSRG`NI27vrWE^sz5LrA3F=Pb?s%2- zBOIZVQLUkg-(TtLj4o_!u1&)_x5m37!X+nWMadW3o@&x(+lz|KxYy>sN#3$Ck`fL} zd^FDo?ROpcnEPt1F|c42V%-wb4yqzb?gX;Ur~3+$oLVNO1St^Gs;ee_&1?n+baSRY zb;YjQ{;6@@=mcVs^L=Kf&U%zp>ureUpFSRx6p&?jS}QOaj`%a9-Og%2<;L=DNFA=w zX)mz5Gb^~;nKt<@@XNlE@A@s$>`#mR9-_>xj%42k3&bky8;ZSKlU{M;nIVNMx4do z=RUc{ROsdpjkee4&qI;|I$K*Dv}w2z;@!jx57Okov!Du$qDZ*qJaj`lmrF}_CI!1B zYQ{f<&S53K9RGP2L#%%&UN#(8{)oPDWnvHxo*X2O%|25%qEg8w>(Vf^zNLM*Th3Lu z*Hj1$NoT4X9jl2rf&$U6)^7Q^o020uss}KrXfal|qMgi6u2Fi=xzU$BeVw`2oFbdU zv7LdlMJY@FS!Q0^Wk90TxT8gsdnF1M{~T39(%78IM&daqV!{Gr(Sd>;D^bN^V2AlT zN$#~#_F8l@VS%o8lS;uWug!s|Oa6rg&d3n0^tbypM@?kIt^2NNb<>E>3=YT1d^Ucc zJpg8HnipEMXezr4XQa-t=OKc2hLXF-AKN+}B5on)RM>GclJAMg?kztsM^ zb=x>~Siaa)2cUDxRx1t~T^Ww)?tGy$*Ociq-G$Qp%c0-WDmBaZ{e3-*Wcwg<#c#b- z_&KnZ6t>vlT|*p!HDSz{O)_U1a+pIuMA~>BxIMqn;4sJPaZ3><4duYg9FGLP|`f-M99kDOgH*>k} zW~VcS?|!Y260AsyW40Qc6k{{C7EMCy}Xct@VS%9{s1tJ$J3jmFuo!9m9S9o4TK$^7MV2K02*@=-rTjWw*^d zzlw+{^H|KZB?LSHJ16WWgtTH^z;q>~AmBmipl{{+)!H1Z?F4e}A2GG#AREt#n{h&& zhZu?urY1F)iw&vq%*ShG0nM6R-6qTOl{f>Y_ZVV*EgyOBUrwGreR2$;-J>ha2i}))tm2MmJ!vv9tPXlFLLa@hA4>jgBX*Nl_1qMNI|rEOZf7@ z8ry4VtD928{gG=&*CR^8=yL3S?_BU+09bD_s`2XV)^R5Bp*Ck+WbrM#{yl5dDD_L)zT05oEOl(-a&>loRP1VCWqlpVw`m_M*sY;ZBuhW8MoDcIzq1A=(v|7s=nbu~o?}A&c_s6&Gc# zJ*mbKVIAGV%**}g00nn`Jcdu-*C1~-Dx_SIrd~$NI9L&CTxsu#gUB^=ihr8=0!qoX zgnbTfa(GEQIda@YcTxqK$TnA-_b19ObIN!mr&$dK^>|U+9(PKhq>!Vw&7(#KAA+tt}&UPxV zvbex>@Dpp1Q1zK$mw*Mb*&*-Ca6q_3JlxDf7VzC&@ScPco{C(WYV_ED>_x z7Ep53mMIfJht~1qDx*$zz1KUbF!@fZXe2B?_+^UYJLzMW;wjB{yI#eKw_=4h@szon zfV9Z*OM_|OvbE7+ni8~+bh90HNmDEGy(E|CwvC>Sc6=Vn(+INNc%R}umD?_5# z@>|lSv*)^9o-Kp-k0y0q$3c%+K1#^;k2{WucP<3oYpAqFx{ zR?%*y`ZpSZ>@9C;a-Ob0;l{idWI0{pB|^ex(w`a%#uRZJwhR|P zc6oUOH5)%KQ&$1mLt8y4#DOcyc&h!E$X|InyB($*kLqsI(5uK-)u+~^cQ zpg;!7D?E)>M+SfnG=@NScK;nqw}BUB{M;133@le#dz7BnuXDq}3LCl*P^?AC4OTiL z3n}O>7_?E%Del?Oyz%xvI$FBYawe)rN^Z1o!1*T1RM`2gQrN znxyNtc2or=n0swi%eW`!w3*q$#&h|%n!VkWJK6x~Ed%?PXF&H2{$q7L+TmE^ZVKyA z8uYAGcef!@t`w`QTvslZQw=jO4GNbI(M#se6*-X4c)=1^lUi#GG-c17o>-NK*1W3d zs$;c7Q(5XW_3R_(J}`DED`udq%3dcjKH)%!tW{Bpy|uK?N+?;i(p%M00Ut(H8-Es0 zp+p?H7ou($BYQzE5FtW5E|}9bYGbo8%mTxF>C;i9jc*F zvR~O-37d_DluaDkcdf0zaP`f&53CEAKM9?S6tT_SUuYQ^ta9@Fy`QN~kH%{MC^`3RcUNRSX+@ z5~;CQL_&ZLAS?EYV1fnHI_8Y2cWeZYr-1S~>hq<7R~b7X=Y!Qwb!7Y)bfenTKVL|1 zWTYHu{NF$sXaPC80QKnQqtF4<>z_aeg%6&2wCCCC=MH-ilHLcteJGu)6^huHm;8a^ z&z~KJd&L9b)!bW!JnTL6gvtn~S9?E*`qb#Ynh}G$XJ!jJ`rdg-(%1NI3B9XgP!I@z znIyZP?kSHO6UJE?&Iy8q2F8C?%}XMd$t1=~8|4k@7279L@Jf6|dtCz^7U+-tDgY_2 z0_0{^9qHwoTl9wZy6o<#xlE8l!4g#m43B(xuU|r6G1h!cE|_m>WN7&^sy#b&5>MJa zkUp-IG7hC;`-XDm;xp!WC}Qh-NaW`gQ%$ESZtP8kHS2X7rPM%WKrnopSPTzNf zL!4+WM?Uo1cl2)I(2N)KuNA|ELF^{Rqe&2Nns

BkAJ+ag-mrNkfyT&+2?Xw*(Va3HIjRO9hXY^JybEymH&@|Us zx;qlQ^jq}QI8^4y1u%c#>@s%~E7K-ZHQv$4H>8yei<42>tA%ucA%SEF(?d8J?k@op z@xMFm%M2{B=+pSfWnsT4OMESyBspF)n2pMd$+iyt$?QO5R_)932k+s4=lPg8v~SLA z=vw8_*4j)#bD!*ppbZRhjk5X_mIybJh4Xy-h4XGY?Ub9I)Nqy2d zSYbcBFlHx-JPX>bdDXTUu#%+{BGuSc>&IIjIXz_ux#F7Cj>zKNr7mgW7EF(<_8pV& z(&aVyYO9B71c#@bA&MeFpa#h3p+e!x?p%S|b&iwLGzeqG9yX1!sXMmN_LaJ4@*osOPYFu+&acL44RPT zWOJM*3MdHZD3_Pf0ag3kh>Q&jtBI`cV&H0Al=x|}`J4DYioc|^>TlHcn2F3I!Wo73 zR_{vpGK8QcSl!W#LDiN;=VKS9tn!@ugPIjw1vW)FT}}cmJhQmUCo1dQO;|8q^r__) zed*>wrMeL*>@#Luoqf}3;wDrZOAe(|&DqI~0d+h@x0j6%eNU1ZFF_$?`v6+B@@KH$ z(I2~4nhGnBdDx{nU-rHP6oc5bE5W-@zcf(EWnz9X^9CYzH5a_vOC^!1ccaNtaVTDv zjdCeuH#Fov8o1PtPB|dQL(P${&0rilmT^_@@>17iO{#O@VG+**9$;-RcS$zz+G%yBO0&z=$-376Q^h4bzu z7KPmmjL-Og{Cr@Z%pufVswuhKP;Uza@f1Bq6-`I-?}oR+u6GK<9@Go@ci)DBK?O%L zyFxpET1Vub=VNa7#pwH%Znp1x9~#!WJkL&%;uc@<0Z-dXd*t1edRDHZ2R7)Co#L{F zWoa3jQ-r?#xY~Sjs8pWVvU`mQc$VFo#^d=A6Gc=o_-lYwfW*51!BAq?n@O7(4@+QW za@9~LGdJ$BF}73e@-#p><6_hlr0=*tDEF*O@j3dy;AmA#uRoYQF5HP{@q%q>;io|X zQ_v?__jt~gI-7@=9R#!X26}Q5y)|muPg~$zimhMoZoX?GDST@5nU3#9_sX0pz}<lNlqqk#T~j`J9H5PRFX$7%}}jg za%^z#wk4A&31`7+e1wsPO1(nXm8L`YoZ=k|4Mq#RGgL_U6XXN@8QiilS)4l5QD?Ug z>VNqhofcbjZi-?3-M=hqzQRaH}${J4UeYXV<@tLySYAH{^0)QaWQ_WbtV!~ zIt`N!-KI*Gd9F;5s@UHK2&doi)~t_eRtjgNTjc4w4{CU4&&cMHh6c~&j<}R*2vplH zg7(}dcwgkrYxg3)acSHSZ{;^%q}T!Zr{xl)I=MV&-oN|r!iIDl*@sk*&i%wAEZ5eZ zO$+9W0;}&;EZAk&saE*%3;IG?%SsUYMXn-jkYL=XA@+tX#w)RLCGEy)hPLK@4d5LO zMg|kT(rVpXz}wI3F3HPXla96a#234^YyrElOT0Y#(CcBEN7&1V35NWxj&h_Wg>Ce8ByqjZg6C5=#Dj4& zxE^d7daa;%IohxEBYffZz??5)tLRT@#i!moQ@g9GvqciYl#9tq8S~-CSt)G%wb7mA zLLpi8#IrBstgisNb@m4&EcJ{9nj4pIfGJ9kGc$yTHCufPLy^YA-nXp$<5obP9A3U? z^B!ZDrn4#o4Ic}EJG1WQgWT6I#L(HO=K(O2Mf|+h1oOwmw|i&(1=$NW=mORynjEYl zRukPbJY?3%(%zHzjv8mF2^?)hbr+tc5>N9=&H>R*U1mTYak3;4 z{(}@X);hNX8;sM}$QE@+$(J%>4*`NW`Ji-DX6toutV?+Pv3>a{L% z>x*eGUS%g*rPc}NtkE}kZ@@u6DyI5H13AcnbzNIz!QCl(X+v5Uac4HrvTN&?-9%B` z``NV$pLd>QKXp`z2W3aOhA&ZBinBns5s-3WcO1>1d&3%Cd|uX$Ob*4?3U>DH2YhYC5W^N5lx=d-pzl5RfFBPzESlgICZLZ7?`U=bKzlwC>q zWdj&_kpc>Z2J1h-6e_$WgHyGR7d-Qrf3k22m`F_t{Vxp|Z9_g@ytZa1BxEqPm9jck zO}};;8zV{pJx5i|h{aiyBcCi(;4(ZvPxnLZVB@EeZBn^Ow#`{55v*}5LX7#d zRE4EBtlMCUXFl==9lO!RYSS*3MR|LHY|{z3Y;nf_Vu z(nw)`C`r=GzsGd~*UL~Q{RQ)HGeuP3Zctb2j2QTba`u4h1#41&K>6FujC{Bo7z@`V z&Ho~i5s;hUdi$7Sej)vBrm`B`4S4E~|DXH+@8bR!!rN}W54&_s_I0>jI9?UahQ$WN z73a<0mcrX;z6aViw!~lY?FDF0UXwaD;8l9|#`Mc(@!!h>*uB6jnKf~ja@V*{Ud~C} zgOZ24-c4i{T9rKy$+_Nk-mub6z;$J3+1t&hv-l(_3H9o92n^6gY_V8TJqp|2PcGw2 zD|@*6uw)Cv%Etobf0*qR8T#`)f}#+vy~SixA2(hRP#kQP<~VVZMyJMWg#+rstR zzA}onLGydRyOm(#7>Iyr)sh)qCnO=1O=UZ2HS!4OTgjY@p`O3Yimk74s29UHS8Q>j zH9-(l2Oh18d3C6kdy^Z^f=BeNam$9!g7w;bUsFjgj%fw!x!t2_< zQW55YH}qDL^${hMkDUZ*`|SNiy_7zz?8h;b-hcq{dS*t*clBU8#XhjH{o{LV>#`s9 zKX79L&y`}>Hx=J8sL?t+5%LP{UF|d}44?f%ynH>DDJ1$)SjCX{5)jv_;bvaqso{PNpg;rTVUun~3fms2srhc&>s`iV?@vz*p3dA7V`o9|(t zQrPC^$jFv2wPUbqDJrh$Vn3ejA0)&UD!;2XcZoSFb~WTnOp`2f6&zrKG(09kI~QbC zV({pUoHPmNPV%rd%h2j4f)(kt=|8tdusz{#AFw8+zm+_xoUKYnCva+bOoPE^8^j{E zfrVTtfp91W>-l2OhuEon%t|Y(iL{dN39)5qt2@acDaO(5!X9jsE9*_o?9G&BddbL~ zd4gWTWcMt=Fz$PUhw2aS=;e3fUIc$~DR@UwOzei#b%c9lUE(eIg@VjDE%0KXI&H$4 zxZ5-%nE8_tgI=VQ>S4!cA0uRVX+7h}?8j?=kqr9R89@3z_@)P{a(rfhEbXWCUttt5 zRwB%G_37 z>jyPM-c8cp8wu#wZRdiajqr5wBcvTjDuU{R0`mm(9BFNs!P))&!W>?dbD9Uf!Rm(c zNX52Y%7Y8$!m-pYcx=>#tki(l`66tO2pGjF*<#VSd%rzhR<{Uq-_+s1K2u^~5T12& zSb5%>WVvtU<~MO4Om{MN1`oS6`M}c@=}ajb9YgoZ5YT z`jfW|YFrqmU3rh2P&QDe>*~wi8oA&iSe(M|$~oEbYzl~H8;YW<&E7@6574Uw%AWwK z*I&-H)q5>Ln0Bk%27Id{#*@fk8&gExLD^EJ=4@-rUy-wQ<-Of4)j< znr`2v=}kKF8*-^9q!-D1;$0HNS{6KuuW|a-HOoF2lWt_13sc#!1rj*~5`TFoEKqjk9t^q8Vc)I_ zkN;R-Ox+~t!Ekx1N`IK0ukbldvG!1IYua+{f~K=G>;vIOZT;O*AJHAjb(-viMM zy1+NTx=d{Df@NEH_9Jr--4-pALRwR&8vpbN!YrKSCsY6g>WXP!oji$R*GxDKK1a{h zXIstyWbGgzdrSaHbrjQS1NIr07mRzb%gL`H!nUOQ`kPaMJqpx;28PrE9-(cMU|V5gzE|BNBd9;1+c0Ff{nmn;(u z8BX7IYUOs{($r-0G*#HFzAsQEwUHU+QO&Iaxl{-d+0OIoYN@hqLNbwLgMbxYRyj1v zQqStKWLa)U;QbDTRf*7$mQQEvH9*!*Pqt1soZe@XGQfHYgbZw!eKfl}bjd|vC+4g* zc0Dk+_a0;DT3Kk?h9+_@mn56f3k74so4<%$Jz$+-z?VLDQQ7%?qby%}cY1sJd;n;SsjYvJ-YP(FI^$Qs*4+fH5MM%hoilHZwhOlZ65QF z+^VXUp4_?~Z`lEolhpevH2v+osv2j%{TxQ#KQ_yUbW$24Gt{2C_5PH7tVfYcs-zW7 z?r2r1n3?bhH_gD#=F5~!im9Sfs7SFXgn}Fm4nwy&A~-Zxo#uq-ZTu|EO5BF3%MQXu zf|rtbN1QScD+_r{x<{%+nkI8UdaZON7nb?Z##Z)e5>2YhlTgeuQ)FGluuBUUWoh$L zX7k09o7@d$nX*~4x(V8pmZkgNPBY>QJvqt!9C)^%B9|S*0K;Nk83!y^%3Nj(@IC@! zK!qr{B`?_%U-Py=ohmVTf}dvE`4) zB(8er{hH~e>qBnKIqTB!z-9PwtC|0fkC{z`kDsf38xY}{12f)B!aeHVZQtr}uwSHb z732B{=QS{F=U9jCJzZ<`K3i03^bJiN{mIzaeglm`>|2_;Dt$m6tmY3k3$n^l-)X>> z%3MBoadpketS|zFpHlp)oEpv)CaK0FPSk@4nh?R!ilhGau7CTs-J?Wv#x_G2%P# z+RF{*Lo0jScSzz6$F1>}d7@)~y{y{4krlbG-HmjAEu?rzQG#lErmC7)|NJHSccOZY z<)%r}!YFh2hDM_ZyAZj(J=Cl1RqdN~H^ML3yS`(=-Fbs0x|wwDXj=*WSNWm zD)V)xFA4@&+)@4eM+a;lwjpoKefGBaa+``+NGj{S|7vz}gCCYS4Ybz!8-C5lfuE#% zi=<%>Sb`3R_mYfd9nk^Pswez0z26!(E0=iJF9YhKXJ@m~JDECtawAx`z3OC9RehrH z$?TI`7C(jBgfHby#xHV?c&MVD(iUK}t66^*T*&VAF(y?GTScjUXfn57?90Ph09tRB z;ULK+jS<<#cPTeCElv(Fiq6O9$5s2bWyqW@3ePp*KxsBK7fL^I6RTa&j{h-M&f2|UVxIRy>PA1!&kSpp+wuhcZ>6n~vw5httjTLagjt#1Qhlk~c*G?8N` z$!fM?ZJrc+gi4uzQomefAHIh)hMuI@r1;8R*D?T7rKGon^Me;fb!NZGb|Z)8nbm~; zkV}3HKR_F`r7Swu(`&(iT*CbOOji6gmvUcJxiA8xEMvEpb5|Tx9{B=xB6Ay*ie#>o zARZec8|Iadf81nT^|jaz$1BY12egOyKGt|DR5Q26aokdBBdJ|0w7eL) z7OF&JZxY!rW1~z{%TnhPgWjE%^R;2H3N%4G$_UkxV%zz|yzc`^oFn9UyIQzLn8y5k^$lf=zTDJPe98*en>*G|ad}6vI-pQr z2s#;~s@peVy((T>*$uJzPTI;uZHrp7c;Eh+jdZ}NX5iQ3vAP}92=z#4x8>Us-n1`9 zmc_-IJ8zB?#`%_#A8oI9`H~+l6)??0$qk6bes1TB0-LC3u1`~J$gO!YrbB?$UXqsA zZ9NfV;D#6Fe4P4imb2%eN~~Pr@s`F!cYgEsl64ReR%{t5+3 z$>G`ojZ?q+18B0^mugEFt+y2^`b=G33clN(AG5^UZ@egN^EIsfX4Y6eWAxxPs5GBp zuG-XW3x#Kz$V*xKr0tom=}J2Ov&5=Yu$ubiNB^@#)wWeOW5Isg6>DiDJJVbs)oi z!HrkzK{3U3+noK}Xbq})tP5fUCB*7D|sc#(rPqhWXAq zFc*|YDxFNIDHLh2ouk0HZ0~Ro3wUm15~dri&ys8Lhxq5d&v?~@$m7F<=Vj58xpO|m z`;l#I!JDP#MAepO&26rl#m^&W$R&&X~Tv@J50G!e6_Ms&`LmPyJho zJ-uCpa`z-4C40N|S3_y|l;7;Wq--}0va1?8V_$N0%z19ie#mnbNSi*m+~o`Pxl-VJ zx{(L7Dt=DD1SEd;J?#rDz-0gifOrBK9R&xo)vOxVdBj_(kIL_-=yy85SZw9sib1Z1 z5);%67t@JHJ*1?rTSk-ay}f1v@l;u|>`bTY&qOiQtyn)7TA!XR=K>6NBrZO^!7nQ& z{C4%4PX9dMzPV@pJ2hlj zxSp?&ic?Tj%W)xWj#HYBKzL&y?dq>lqT?I-Ql3cLR2QcVj!=;aeOtzM8UuC1LOEZj zioLTYU`?jMnbQatg|SoL4Rb;QC}xkUBX_K$lGQxQ4}*)`k@rYQhG?y*Y(u=-8;AYI z7z*YD_)}XnrdSrmOsq9^H!g;Qd8b_$hD^BcUC5*i?rB2}XNV>v3hah93P~8f8r=Ch zlEbfb2s)Hx6WBX*N#j4xb2&qON}+4#zSU2Rc_1F`M6Gpy)rBLkH%5*R7xIYB6cS9x zcJrt&*XJY2Sa*aiSbPpjPhy-;-IOwV=-BS;sz)$u+Y2o&6?4p07)d{mta! zK3}PlK#s+lw;Sk_#5Ak@(as40@Yf3rk)!hCaZR1g`**IT)LG}kfYRqdBqTa$%0+h5 zbCg`=7@Y|IbyQleH~vIPs;AxM<6y7!Kw--^D#pHp}=lpk}*<+ zR*mNKQ}%Nbs_Lx|$>I`xY<75I6!X$?hunGmm-x>GN+Yffvs-$dCf#{uUf&Gnn0D3_ z=WmD)S>2wc`L(t_pWFOFL^)u+BjzYU@=R4^^%fCf%pUj@)EcwvU4L|s))61YcQjBx z4Qzx$rT0`A?uyM)LTux}~8~nT8XzmCn4^Gbixuapv zL?T##G#S6H#5q2RT1=Lao@tPVoE)e@nl*XzUaTUiUl!RPN|Oi9@1b>Q`$#TrV#;A7 zE;^7{jyTsEFep(jY*N>a=zd)nHajzNl>7dHl%qvy{C6&lBkmN<=C>LD%|>zrA8145 zJR%2yj6mv7{r=5eoi}!X==Q}({%F0#)l4J4o+BV8gN(Pi-*ZKnpU*0CsmNKdk@LM^ z3soEFDo>_xavde!wXTozqJ6A&{6y%frHH(TY01TrZq`FbatdFZ<`Td4CTeN8D34FE zcmIaxD*p_He!|T{n#N=b#+#>Gy5`tbFj#%@R5UBBd)G1!SsJ%pbUFn8n?;%pdhXg`0*#IwKwmCeuM?TvEI zyk772o#1_9n9s8x{kT$Efz-!7cP1uf+PKeieD{IkR31Mhv(YX2VhBM$;H@5y(ad;B`2L#DZgNSFQxPc+XkD+$)fsUp?Tu}dB)&R@GGYFEno zJ(fdT0{v8of^VIYR-#B=W$Bt6&zK{%h**(Zv1(~cmai`zVt*l6I1sl~3?5x_wnU`< znip~x!qbgO?>hiClqLO{4?RMpEF(T!@yd&?RGf4y9dcpM#A|bf1FCuMLM+Y~ zYN8Qn{FlQcH$cu*9_Hwl>RjG2xmbSvRr<=A_gyTi-G|VgoEemYB|!yQ|8t0eJoj%HLmjByH3?jP3p$zWReB z@v6izID0Ekn&@-V#A&S6Gc$ynPBDYU83nRxIpXwPF2%{~Zm}4=6vMMn4XO-_rSO8?-gVZOrgiHf6dXd}|fa8Js8-m3qCQT$sT=e%3zDC6$i z$Dz`5V5?qdtX1;6(r>89cSME}T(XbvZaqk(FUx<-Rn1nvbK{11Jups=O7UU|fsb-} ze71X{T$_~dXcO4g;Y>-VpL)5Au3G0r1CTE0o8}O{55K7Amvq&UDpb@p;^z1#QOnt>TgHH6Cx(6_=^X zJ4b4DQqhvgx}v5#DrPD|Zn4LLi;zQ-4l><@t1-S*h`zR)N)fF7?38u@+0X94)|}hb zWi&ii3v;M-E-Bdla?x{*>4Ygf{f<$lPi;O*=we%E(C)l9x>wnv^Ikd>Fm}ztPja3C zdUOMo`=%d7m#~E%g)IwVlPfwIMD+Km3!I`F?Jyu;42NKxqwI;_3UwFV- zX%{--ZRzp(A`-_=0!)-W?X!mzu)kbrOnjD&=+)roM0RY$jNuy%IY}-o`nBUDn02STLQn$xKz%DoC6}M( zlO*Y8C+lHM4kndDNnK#rT;hT6|LfDbzQ-e`6Mm#{6P`N#vYS#ISCAaapb7BTUP49@lK0kSw5Ju$%@seq~UYNh~RP-mA&w>3ln*j(CF}J_n$e%3)PQafsYp>=$*33 zy%}W#MFsdIG;}*r5qvEXj-i9$<_LyWgP_(RM4HC-QCN$@Fky>JzG&6V>UGm~&ckhi zT?X(tg428Ffre32Q8btET$B);-dkCWjFJ&|mcm3n2rLW8Jj;XboG$!4`JXos-G@>< zy{O%GeDwllm%p(l-k||IXfi7t1C2RH8U}U&A5oNd1W3$JE)&I)uwQV=H|_`Ewa-s( zOY@e4WcxN>u>dDc2Twn}l4@6J=DRc$ZoNE%|EIxt3N0RUpk4+|$MHe(lGmjBuIrV} zeKush9Bt>ZM;U7S-m~Sd#4f|^Mlf<0Yj5A9u>|TVX;3~oTwn;Rg*jQ{U=YvEF}=H+ z{4UXNz%V0DWu#B_mIJ<{O{?^sD6378u6cBFRxfq@*{gduH}r=rM0^pA(1JhpSgxiA z9`z6z-gbOO|EZ2|7v5^lds~Y2zt#3+0b^xQ|1=xC^vL?>fl`Z(gbkCFVJT{e%szn;vVS?$;BUYyF+EWd_;YRD7sNJzw7r=ecHme z&yondDwetM--Tiw+j?07#$BDL1@8}OUpV0@S&T=X1S*KsM*K(p^RzyN@D31u4&VCF zQiR1LD}8C_a-)@wT$`%kYb z27PJi&tMJPq~>b5xSQ2Feqg@aYLit2@#7T&0;bY`?*hp7{LU^><^M8IUIqA#j9-C* z$i6l8ut$q7qV!=5eTabHQ~0|@G<<}wfO;^T1Q3tQ=>tRQdJE)9v8v{J(KHrf-|4av zImE-|QL1iY)_0yN@b%~x`NIA+%?-7M;=ud+#_=>84oK7X9^DRW=;_^hHblPLb?5}D ze7NMUYx} zUJMJ8e{$m%eT75Nb0s6HP!5R znjp)S*;b=xsMUqSSvD6tarI%z9{(K$w-8XA)=ufB$Sx0aHH>p||Lfp2-WrdC<>O$k z{HPJNVXhM$q~g9+DEATn9o;oslRWcSRrX>6=Ice8m429L#XUsthm~GQVbOYCGT&o$ zJ!t5H-Kr}sXSSa_YKB=iha?|FI^_+!;P~P!26gu{`_Y+L;G`YwFdp)rE+v^=i$%fap zrVM!w$-t;P@QslmXCpb$MA>s#Z~NBVAbcP6_73W2bk+(m!AXzE^6wBa;})tdck25- zCi3;rvsvm;-`C?@nLIXOav;f#Y~9JF=|opplBAX(sZC8M$vUMvJ2;(Y3q}qkHp8){J@j2JkwQAqnNlE3$X3bog(97#yt9 z1wFf*fKMi0$h68W)q{1}3isldmGXY*OC1PpNq1Ga%VHf_uA~Bs7~zlR-(AXI2N4RsU+IP!*)N9j5DX<1*M60+dZz+ufWe&R zekX_a`qFdF+?8Wnj5q@$I0u;ySzU5F0G}rSKi`#N5eq+X3zp^0Bx%Zn z;-pgYM$f(IfM~|qzqaqvk%~%}khXQw->0bK| z=-dfye@;i@u`?^>SgWB?A!r_4akTIPh_ zZgrgkS=Q?~OcF5|y~^_|H7-BxTMicZt4TulW;=%j9K{}rFLf;xx10XMU--S*%R>CJ z8{~0j)NOP79j3_SNNgCPPe}{~B%as9fPn=?MG{xy){NmMFyA5|Wm4o37_jm5 zVCUq^T5sq*O;VIVo;BO*kOX+@-7FH8@1FJTRx9r0wH^~a{e<(>AhpGImBZwN_ND^h zLfAd+p(Z3v zq1Mmg=^3jIS$xyy!Z#lLy@Ell=)QXD%HHv?muLUQi{^kc8vL-ww6k{<`C}Zj>5&Z0 z3MwQSyQ2feS#7~wpQErj*DPu<;{(!e-w8~_@?KO|8ykfr5EVB01RA#DpR1)4LxHnwnPm^<17pM;%-B;S9HLFY^3@eAmGxucT!Gbob zt&%FzBT1C6#NtO-y3J~c;rjyklHndH!rU#caX)pow3r*-!nHIeMvu?y)FWGcL?E81 zXssCY(o21f0YonVl?YKqe+L5!CB2faeAlEA!&lo-V%=_4L7`-CGCMq3$ zL2jWXobq3_S8m=wCyiE0vVh~xr12BT(f2mEZq#b!H61?hG~R5cJx0$)(zymLIe#C?`~1eunQBwpfKLR?**eZ>!D9zxk?PG94f2Tc;M1 zpvXR_sRhtOkP4X`#!~uI_+m*)e1^(&Z<+AvdF8rA86U=}Wy-Q>o*i4_G;a0^hCmt) zbh6~ArSQde8nzlVpJ0boxk8;x?a_XVsZPSu!tpzz(C_~@j@Tb>Z-rWK*=tQ3v|#YY?d&`j`yAzU@7R_6 zi}|*0<>oY3cK=mAv-7WWy)=DtyQyT@ANSGR1`#cy2) z_I_UeCA;U?xsW>&I<5)x6KB2W=h##ZoF@9TAiU7Q?-@&#*X;?FeQSh&`z<(|xp=CP ze~MF$$L*`zc>hM64ErrzICo;`n#8Y)2JP{cuWPcn`kwp9oaMRMz3t_@=SmLkXIAig zivCT=d%NkbNx}A8Z<3x)IbZYIh%5j1gC7xFDmOf8tlJ`5GoNefp5^5cPmE*l_D0uF zN(oM8{wnAs-x%i=z4jh2TW@UC$~WtBp8o}opG&HrX?1n@UVqW!Ge_3Z7fb9m&kxzN zK46u(=3Dj`(Tje2@qB%>U3{I7EoV%`&CKaiz<|s7?z>Iy(&DKC*1`eHZrKVKpdH9` zLWnV%KeIAj%X^{&+8SX7jvrHj%R={0)A_`Tx@;DhivpHy>JmSMw#xR87jRW>ytmj- zv@>2#XaQFPM;_%yKLD#v6}T#Qy=r$2+Sx5BKzmoNdDL2fx_X##zYuUy^z})u`_NV` zZvxsI686!_0(JFpLp<<+mQUFymEzG>Ek^?F)mmMlD1*9s_`rJLYT%z+Qzq)8+S|AW zXz$d}JpyP)i984g2E(7IO((=r?R5wP+IvbXjsxwek`LOzV5rlLe8P=tufl4ey{V%5 zEzPLsY7F1tKd@QL_Vl6e>sAIw_Axy|OGOR}nv83FRF5*bqecUxfQSEs|I7jtIu4by TJ}LyBImh7X>gTe~DWM4fDP(T5 literal 0 HcmV?d00001 diff --git a/08-regexp-greedy-and-lazy/witch_greedy4.png b/08-regexp-greedy-and-lazy/witch_greedy4.png new file mode 100644 index 0000000000000000000000000000000000000000..c455ba64bc9df5f9b9cdfad8cc22e5696848eeb5 GIT binary patch literal 10320 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 lOv~l$H`rZda6D1|vLe(h~CG<)VfG{|~v+DTM$4 literal 0 HcmV?d00001 diff --git a/08-regexp-greedy-and-lazy/witch_greedy4@2x.png b/08-regexp-greedy-and-lazy/witch_greedy4@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..36179761648e82b18ff50b3ae1cee9ec9db45b83 GIT binary patch literal 20025 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 b1_dTV<~EGQ2<^>;c>l;sDN5Fe8wUL!)LIwX literal 0 HcmV?d00001 diff --git a/08-regexp-greedy-and-lazy/witch_greedy6@2x.png b/08-regexp-greedy-and-lazy/witch_greedy6@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0457ccabe97a217d4fa6a37472fd6bd2a878e6c8 GIT binary patch literal 18949 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 B)I&&{$cqyx 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 O@{yHPlqeT73jRN0E^fa7 literal 0 HcmV?d00001 diff --git a/08-regexp-greedy-and-lazy/witch_lazy3@2x.png b/08-regexp-greedy-and-lazy/witch_lazy3@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..8c5a44ac1cb42c9c539e0fdb3dcb29026e42b4f3 GIT binary patch literal 15379 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 UiWZXNILz}QFRdh1Ct(!)KgsWmWdHyG literal 0 HcmV?d00001 diff --git a/08-regexp-greedy-and-lazy/witch_lazy4@2x.png b/08-regexp-greedy-and-lazy/witch_lazy4@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..8ef62d18300403fabfc520686e2c1c17af1fe1b5 GIT binary patch literal 15691 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 XIiLz)yh^?ueMUn?R~hu!?#2HB8L%!2 literal 0 HcmV?d00001 diff --git a/08-regexp-greedy-and-lazy/witch_lazy5.png b/08-regexp-greedy-and-lazy/witch_lazy5.png new file mode 100644 index 0000000000000000000000000000000000000000..630d1c496350cd8d8ced2e2d26055c3d05570feb GIT binary patch literal 8501 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 a=t*|fMfm`(IaIOz9eHULsY*%H_x}$S6rbb( literal 0 HcmV?d00001 diff --git a/08-regexp-greedy-and-lazy/witch_lazy6.png b/08-regexp-greedy-and-lazy/witch_lazy6.png new file mode 100644 index 0000000000000000000000000000000000000000..1bc710fba6666c21304821125c2e9c7059ed4144 GIT binary patch literal 9288 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 literal 0 HcmV?d00001 diff --git a/08-regexp-greedy-and-lazy/witch_lazy6@2x.png b/08-regexp-greedy-and-lazy/witch_lazy6@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..86efed4644481dc49a889518e3e7010d1b76ca24 GIT binary patch literal 17667 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 Ol#GO;c!j7@;Qs<$uUIqy literal 0 HcmV?d00001 diff --git a/09-regexp-groups/1-find-webcolor-3-or-6/solution.md b/09-regexp-groups/1-find-webcolor-3-or-6/solution.md new file mode 100644 index 000000000..d653ff970 --- /dev/null +++ b/09-regexp-groups/1-find-webcolor-3-or-6/solution.md @@ -0,0 +1,29 @@ +A regexp to search 3-digit color `#abc`: `pattern:/#[a-f0-9]{3}/i`. + +We can add exactly 3 more optional hex digits. We don't need more or less. Either we have them or we don't. + +The simplest way to add them -- is to append to the regexp: `pattern:/#[a-f0-9]{3}([a-f0-9]{3})?/i` + +We can do it in a smarter way though: `pattern:/#([a-f0-9]{3}){1,2}/i`. + +Here the regexp `pattern:[a-f0-9]{3}` is in parentheses to apply the quantifier `pattern:{1,2}` to it as a whole. + +In action: + +```js run +let reg = /#([a-f0-9]{3}){1,2}/gi; + +let str = "color: #3f3; background-color: #AA00ef; and: #abcd"; + +alert( str.match(reg) ); // #3f3 #AA00ef #abc +``` + +There's a minor problem here: the pattern found `match:#abc` in `subject:#abcd`. To prevent that we can add `pattern:\b` to the end: + +```js run +let reg = /#([a-f0-9]{3}){1,2}\b/gi; + +let str = "color: #3f3; background-color: #AA00ef; and: #abcd"; + +alert( str.match(reg) ); // #3f3 #AA00ef +``` diff --git a/09-regexp-groups/1-find-webcolor-3-or-6/task.md b/09-regexp-groups/1-find-webcolor-3-or-6/task.md new file mode 100644 index 000000000..4efd6f61f --- /dev/null +++ b/09-regexp-groups/1-find-webcolor-3-or-6/task.md @@ -0,0 +1,14 @@ +# Find color in the format #abc or #abcdef + +Write a RegExp that matches colors in the format `#abc` or `#abcdef`. That is: `#` followed by 3 or 6 hexadecimal digits. + +Usage example: +```js +let reg = /your regexp/g; + +let str = "color: #3f3; background-color: #AA00ef; and: #abcd"; + +alert( str.match(reg) ); // #3f3 #AA00ef +``` + +P.S. This should be exactly 3 or 6 hex digits: values like `#abcd` should not match. diff --git a/09-regexp-groups/3-find-decimal-positive-numbers/solution.md b/09-regexp-groups/3-find-decimal-positive-numbers/solution.md new file mode 100644 index 000000000..23065413e --- /dev/null +++ b/09-regexp-groups/3-find-decimal-positive-numbers/solution.md @@ -0,0 +1,18 @@ + +An non-negative integer number is `pattern:\d+`. We should exclude `0` as the first digit, as we don't need zero, but we can allow it in further digits. + +So that gives us `pattern:[1-9]\d*`. + +A decimal part is: `pattern:\.\d+`. + +Because the decimal part is optional, let's put it in parentheses with the quantifier `pattern:'?'`. + +Finally we have the regexp: `pattern:[1-9]\d*(\.\d+)?`: + +```js run +let reg = /[1-9]\d*(\.\d+)?/g; + +let str = "1.5 0 -5 12. 123.4."; + +alert( str.match(reg) ); // 1.5, 0, 12, 123.4 +``` diff --git a/09-regexp-groups/3-find-decimal-positive-numbers/task.md b/09-regexp-groups/3-find-decimal-positive-numbers/task.md new file mode 100644 index 000000000..ad8c81eae --- /dev/null +++ b/09-regexp-groups/3-find-decimal-positive-numbers/task.md @@ -0,0 +1,12 @@ +# Find positive numbers + +Create a regexp that looks for positive numbers, including those without a decimal point. + +An example of use: +```js +let reg = /your regexp/g; + +let str = "1.5 0 -5 12. 123.4."; + +alert( str.match(reg) ); // 1.5, 12, 123.4 (ignores 0 and -5) +``` diff --git a/09-regexp-groups/4-find-decimal-numbers/solution.md b/09-regexp-groups/4-find-decimal-numbers/solution.md new file mode 100644 index 000000000..dd2410847 --- /dev/null +++ b/09-regexp-groups/4-find-decimal-numbers/solution.md @@ -0,0 +1,11 @@ +A positive number with an optional decimal part is (per previous task): `pattern:\d+(\.\d+)?`. + +Let's add an optional `-` in the beginning: + +```js run +let reg = /-?\d+(\.\d+)?/g; + +let str = "-1.5 0 2 -123.4."; + +alert( str.match(reg) ); // -1.5, 0, 2, -123.4 +``` diff --git a/09-regexp-groups/4-find-decimal-numbers/task.md b/09-regexp-groups/4-find-decimal-numbers/task.md new file mode 100644 index 000000000..121a18a41 --- /dev/null +++ b/09-regexp-groups/4-find-decimal-numbers/task.md @@ -0,0 +1,13 @@ +# Find all numbers + +Write a regexp that looks for all decimal numbers including integer ones, with the floating point and negative ones. + +An example of use: + +```js +let reg = /your regexp/g; + +let str = "-1.5 0 2 -123.4."; + +alert( str.match(re) ); // -1.5, 0, 2, -123.4 +``` diff --git a/09-regexp-groups/5-parse-expression/solution.md b/09-regexp-groups/5-parse-expression/solution.md new file mode 100644 index 000000000..3db5f667c --- /dev/null +++ b/09-regexp-groups/5-parse-expression/solution.md @@ -0,0 +1,51 @@ +A regexp for a number is: `pattern:-?\d+(\.\d+)?`. We created it in previous tasks. + +An operator is `pattern:[-+*/]`. We put the dash `pattern:-` first, because in the middle it would mean a character range, we don't need that. + +Note that a slash should be escaped inside a JavaScript regexp `pattern:/.../`. + +We need a number, an operator, and then another number. And optional spaces between them. + +The full regular expression: `pattern:-?\d+(\.\d+)?\s*[-+*/]\s*-?\d+(\.\d+)?`. + +To get a result as an array let's put parentheses around the data that we need: numbers and the operator: `pattern:(-?\d+(\.\d+)?)\s*([-+*/])\s*(-?\d+(\.\d+)?)`. + +In action: + +```js run +let reg = /(-?\d+(\.\d+)?)\s*([-+*\/])\s*(-?\d+(\.\d+)?)/; + +alert( "1.2 + 12".match(reg) ); +``` + +The result includes: + +- `result[0] == "1.2 + 12"` (full match) +- `result[1] == "1.2"` (first group `(-?\d+(\.\d+)?)` -- the first number, including the decimal part) +- `result[2] == ".2"` (second group`(\.\d+)?` -- the first decimal part) +- `result[3] == "+"` (third group `([-+*\/])` -- the operator) +- `result[4] == "12"` (forth group `(-?\d+(\.\d+)?)` -- the second number) +- `result[5] == undefined` (fifth group `(\.\d+)?` -- the last decimal part is absent, so it's undefined) + +We only want the numbers and the operator, without the full match or the decimal parts. + +The full match (the arrays first item) can be removed by shifting the array `pattern:result.shift()`. + +The decimal groups can be removed by making them into non-capturing groups, by adding `pattern:?:` to the beginning: `pattern:(?:\.\d+)?`. + +The final solution: + +```js run +function parse(expr) { + let reg = /(-?\d+(?:\.\d+)?)\s*([-+*\/])\s*(-?\d+(?:\.\d+)?)/; + + let result = expr.match(reg); + + if (!result) return []; + result.shift(); + + return result; +} + +alert( parse("-1.23 * 3.45") ); // -1.23, *, 3.45 +``` diff --git a/09-regexp-groups/5-parse-expression/task.md b/09-regexp-groups/5-parse-expression/task.md new file mode 100644 index 000000000..8b54d4683 --- /dev/null +++ b/09-regexp-groups/5-parse-expression/task.md @@ -0,0 +1,28 @@ +# Parse an expression + +An arithmetical expression consists of 2 numbers and an operator between them, for instance: + +- `1 + 2` +- `1.2 * 3.4` +- `-3 / -6` +- `-2 - 2` + +The operator is one of: `"+"`, `"-"`, `"*"` or `"/"`. + +There may be extra spaces at the beginning, at the end or between the parts. + +Create a function `parse(expr)` that takes an expression and returns an array of 3 items: + +1. The first number. +2. The operator. +3. The second number. + +For example: + +```js +let [a, op, b] = parse("1.2 * 3.4"); + +alert(a); // 1.2 +alert(op); // * +alert(b); // 3.4 +``` diff --git a/09-regexp-groups/article.md b/09-regexp-groups/article.md new file mode 100644 index 000000000..7efe68858 --- /dev/null +++ b/09-regexp-groups/article.md @@ -0,0 +1,237 @@ +# Capturing groups + +A part of a pattern can be enclosed in parentheses `pattern:(...)`. This is called a "capturing group". + +That has two effects: + +1. It allows to place a part of the match into a separate array. +2. If we put a quantifier after the parentheses, it applies to the parentheses as a whole, not the last character. + +## Example + +In the example below the pattern `pattern:(go)+` finds one or more `match:'go'`: + +```js run +alert( 'Gogogo now!'.match(/(go)+/i) ); // "Gogogo" +``` + +Without parentheses, the pattern `pattern:/go+/` means `subject:g`, followed by `subject:o` repeated one or more times. For instance, `match:goooo` or `match:gooooooooo`. + +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}`. + +1. The first part `pattern:[-.\w]+` (before `@`) may include any alphanumeric word characters, a dot and a dash, to match `match:john.smith`. +2. Then `pattern:@`, and the domain. It may be a subdomain like `host.site.com.uk`, so we match it as "a word followed by a dot `pattern:([\w-]+\.)` (repeated), and then the last part must be a word: `match:com` or `match:uk` (but not very long: 2-20 characters). + +That regexp is not perfect, but good enough to fix errors or occasional mistypes. + +For instance, 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 +``` + +In this example parentheses were used to make a group for repeating `pattern:(...)+`. But there are other uses too, let's see them. + +## Contents of parentheses + +Parentheses are numbered from left to right. The search engine remembers the content of each and allows to reference it in the pattern or in the replacement string. + +For instance, we'd like to find HTML tags `pattern:<.*?>`, and process them. + +Let's wrap the inner content into parentheses, like this: `pattern:<(.*?)>`. + +We'll get them into an array: + +```js run +let str = '

Hello, world!

'; +let reg = /<(.*?)>/; + +alert( str.match(reg) ); // Array: ["

", "h1"] +``` + +The call to [String#match](mdn:js/String/match) returns groups only if the regexp has no `pattern:/.../g` flag. + +If we need all matches with their groups then we can use `.matchAll` or `regexp.exec` as described in : + +```js run +let str = '

Hello, world!

'; + +// two matches: opening

and closing

tags +let reg = /<(.*?)>/g; + +let matches = Array.from( str.matchAll(reg) ); + +alert(matches[0]); // Array: ["

", "h1"] +alert(matches[1]); // Array: ["

", "/h1"] +``` + +Here we have two matches for `pattern:<(.*?)>`, each of them is an array with the full match and groups. + +## Nested groups + +Parentheses can be nested. In this case the numbering also goes from left to right. + +For instance, when searching a tag in `subject:` we may be interested in: + +1. The tag content as a whole: `match:span class="my"`. +2. The tag name: `match:span`. +3. The tag attributes: `match:class="my"`. + +Let's add parentheses for them: + +```js run +let str = ''; + +let reg = /<(([a-z]+)\s*([^>]*))>/; + +let result = str.match(reg); +alert(result); // , span class="my", span, class="my" +``` + +Here's how groups look: + +![](regexp-nested-groups.png) + +At the zero index of the `result` is always the full match. + +Then groups, numbered from left to right. Whichever opens first gives the first group `result[1]`. Here it encloses the whole tag content. + +Then in `result[2]` goes the group from the second opening `pattern:(` till the corresponding `pattern:)` -- tag name, then we don't group spaces, but group attributes for `result[3]`. + +**If a group is optional and doesn't exist in the match, the corresponding `result` index is present (and equals `undefined`).** + +For instance, let's consider the regexp `pattern:a(z)?(c)?`. It looks for `"a"` optionally followed by `"z"` optionally followed by `"c"`. + +If we run it on the string with a single letter `subject:a`, then the result is: + +```js run +let match = 'a'.match(/a(z)?(c)?/); + +alert( match.length ); // 3 +alert( match[0] ); // a (whole match) +alert( match[1] ); // undefined +alert( match[2] ); // undefined +``` + +The array has the length of `3`, but all groups are empty. + +And here's a more complex match for the string `subject:ack`: + +```js run +let match = 'ack'.match(/a(z)?(c)?/) + +alert( match.length ); // 3 +alert( match[0] ); // ac (whole match) +alert( match[1] ); // undefined, because there's nothing for (z)? +alert( match[2] ); // c +``` + +The array length is permanent: `3`. But there's nothing for the group `pattern:(z)?`, so the result is `["ac", undefined, "c"]`. + +## Named groups + +Remembering groups by their numbers is hard. For simple patterns it's doable, but for more complex ones we can give names to parentheses. + +That's done by putting `pattern:?` immediately after the opening paren, like this: + +```js run +*!* +let dateRegexp = /(?[0-9]{4})-(?[0-9]{2})-(?[0-9]{2})/; +*/!* +let str = "2019-04-30"; + +let groups = str.match(dateRegexp).groups; + +alert(groups.year); // 2019 +alert(groups.month); // 04 +alert(groups.day); // 30 +``` + +As you can see, the groups reside in the `.groups` property of the match. + +Wee can also use them in replacements, as `pattern:$` (like `$1..9`, but name instead of a digit). + +For instance, let's rearrange the date into `day.month.year`: + +```js run +let dateRegexp = /(?[0-9]{4})-(?[0-9]{2})-(?[0-9]{2})/; + +let str = "2019-04-30"; + +let rearranged = str.replace(dateRegexp, '$.$.$'); + +alert(rearranged); // 30.04.2019 +``` + +If we use a function, then named `groups` object is always the last argument: + +```js run +let dateRegexp = /(?[0-9]{4})-(?[0-9]{2})-(?[0-9]{2})/; + +let str = "2019-04-30"; + +let rearranged = str.replace(dateRegexp, + (str, year, month, day, offset, input, groups) => + `${groups.day}.${groups.month}.${groups.year}` +); + +alert(rearranged); // 30.04.2019 +``` + +Usually, when we intend to use named groups, we don't need positional arguments of the function. For the majority of real-life cases we only need `str` and `groups`. + +So we can write it a little bit shorter: + +```js +let rearranged = str.replace(dateRegexp, (str, ...args) => { + let {year, month, day} = args.pop(); + alert(str); // 2019-04-30 + alert(year); // 2019 + alert(month); // 04 + alert(day); // 30 +}); +``` + + +## Non-capturing groups with ?: + +Sometimes we need parentheses to correctly apply a quantifier, but we don't want the contents in results. + +A group may be excluded by adding `pattern:?:` in the beginning. + +For instance, if we want to find `pattern:(go)+`, but don't want to remember the contents (`go`) in a separate array item, we can write: `pattern:(?:go)+`. + +In the example below we only get the name "John" as a separate member of the `results` array: + +```js run +let str = "Gogo John!"; +*!* +// exclude Gogo from capturing +let reg = /(?:go)+ (\w+)/i; +*/!* + +let result = str.match(reg); + +alert( result.length ); // 2 +alert( result[1] ); // John +``` + +## Summary + +- Parentheses can be: + - capturing `(...)`, ordered left-to-right, accessible by number. + - named capturing `(?...)`, accessible by name. + - non-capturing `(?:...)`, used only to apply quantifier to the whole groups. diff --git a/09-regexp-groups/regexp-nested-groups.png b/09-regexp-groups/regexp-nested-groups.png new file mode 100644 index 0000000000000000000000000000000000000000..da42e6a9c22e488d962b25de917cff8aac4fe201 GIT binary patch literal 12523 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 + +For named groups, we can backreference by `\k`. + +The same example with the named group: + +```js run +let str = `He said: "She's the one!".`; + +*!* +let reg = /(?['"])(.*?)\k/g; +*/!* + +alert( str.match(reg) ); // "She's the one!" +``` diff --git a/11-regexp-alternation/01-find-programming-language/solution.md b/11-regexp-alternation/01-find-programming-language/solution.md new file mode 100644 index 000000000..3419aa498 --- /dev/null +++ b/11-regexp-alternation/01-find-programming-language/solution.md @@ -0,0 +1,33 @@ + +The first idea can be to list the languages with `|` in-between. + +But that doesn't work right: + +```js run +let reg = /Java|JavaScript|PHP|C|C\+\+/g; + +let str = "Java, JavaScript, PHP, C, C++"; + +alert( str.match(reg) ); // Java,Java,PHP,C,C +``` + +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. + +As a result, `match:JavaScript` can never be found, just because `match:Java` is checked first. + +The same with `match:C` and `match:C++`. + +There are two solutions for that problem: + +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 +let reg = /Java(Script)?|C(\+\+)?|PHP/g; + +let str = "Java, JavaScript, PHP, C, C++"; + +alert( str.match(reg) ); // Java,JavaScript,PHP,C,C++ +``` diff --git a/11-regexp-alternation/01-find-programming-language/task.md b/11-regexp-alternation/01-find-programming-language/task.md new file mode 100644 index 000000000..61b9526f7 --- /dev/null +++ b/11-regexp-alternation/01-find-programming-language/task.md @@ -0,0 +1,11 @@ +# Find programming languages + +There are many programming languages, for instance 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 +``` diff --git a/11-regexp-alternation/02-find-matching-bbtags/solution.md b/11-regexp-alternation/02-find-matching-bbtags/solution.md new file mode 100644 index 000000000..03080f86c --- /dev/null +++ b/11-regexp-alternation/02-find-matching-bbtags/solution.md @@ -0,0 +1,23 @@ + +Opening tag is `pattern:\[(b|url|quote)\]`. + +Then to find everything till the closing tag -- let's the pattern `pattern:[\s\S]*?` to match any character including the newline and then a backreference to the closing tag. + +The full pattern: `pattern:\[(b|url|quote)\][\s\S]*?\[/\1\]`. + +In action: + +```js run +let reg = /\[(b|url|quote)\][\s\S]*?\[\/\1\]/g; + +let str = ` + [b]hello![/b] + [quote] + [url]http://google.com[/url] + [/quote] +`; + +alert( str.match(reg) ); // [b]hello![/b],[quote][url]http://google.com[/url][/quote] +``` + +Please note that we had to escape a slash for the closing tag `pattern:[/\1]`, because normally the slash closes the pattern. diff --git a/11-regexp-alternation/02-find-matching-bbtags/task.md b/11-regexp-alternation/02-find-matching-bbtags/task.md new file mode 100644 index 000000000..e0919e034 --- /dev/null +++ b/11-regexp-alternation/02-find-matching-bbtags/task.md @@ -0,0 +1,48 @@ +# Find bbtag pairs + +A "bb-tag" looks like `[tag]...[/tag]`, where `tag` is one of: `b`, `url` or `quote`. + +For instance: +``` +[b]text[/b] +[url]http://google.com[/url] +``` + +BB-tags can be nested. But a tag can't be nested into itself, for instance: + +``` +Normal: +[url] [b]http://google.com[/b] [/url] +[quote] [b]text[/b] [/quote] + +Impossible: +[b][b]text[/b][/b] +``` + +Tags can contain line breaks, that's normal: + +``` +[quote] + [b]text[/b] +[/quote] +``` + +Create a regexp to find all BB-tags with their contents. + +For instance: + +```js +let reg = /your regexp/g; + +let str = "..[url]http://google.com[/url].."; +alert( str.match(reg) ); // [url]http://google.com[/url] +``` + +If tags are nested, then we need the outer tag (if we want we can continue the search in its content): + +```js +let reg = /your regexp/g; + +let str = "..[url][b]http://google.com[/b][/url].."; +alert( str.match(reg) ); // [url][b]http://google.com[/b][/url] +``` diff --git a/11-regexp-alternation/03-match-quoted-string/solution.md b/11-regexp-alternation/03-match-quoted-string/solution.md new file mode 100644 index 000000000..143be870c --- /dev/null +++ b/11-regexp-alternation/03-match-quoted-string/solution.md @@ -0,0 +1,17 @@ +The solution: `pattern:/"(\\.|[^"\\])*"/g`. + +Step by step: + +- 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 +let reg = /"(\\.|[^"\\])*"/g; +let str = ' .. "test me" .. "Say \\"Hello\\"!" .. "\\\\ \\"" .. '; + +alert( str.match(reg) ); // "test me","Say \"Hello\"!","\\ \"" +``` diff --git a/11-regexp-alternation/03-match-quoted-string/task.md b/11-regexp-alternation/03-match-quoted-string/task.md new file mode 100644 index 000000000..2ccac4bdf --- /dev/null +++ b/11-regexp-alternation/03-match-quoted-string/task.md @@ -0,0 +1,32 @@ +# Find quoted strings + +Create a regexp to find strings in double quotes `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 +let str = "Just like \"here\"."; +``` + +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: + +```js run +let str = ' .. "test me" .. "Say \\"Hello\\"!" .. "\\\\ \\"" .. '; + +// the in-memory string +alert(str); // .. "test me" .. "Say \"Hello\"!" .. "\\ \"" .. +``` diff --git a/11-regexp-alternation/04-match-exact-tag/solution.md b/11-regexp-alternation/04-match-exact-tag/solution.md new file mode 100644 index 000000000..70c4de91a --- /dev/null +++ b/11-regexp-alternation/04-match-exact-tag/solution.md @@ -0,0 +1,16 @@ + +The pattern start is obvious: `pattern:`, because `match:` would match it. + +We need either a space after `match:`. + +In the regexp language: `pattern:|\s.*?>)`. + +In action: + +```js run +let reg = /|\s.*?>)/g; + +alert( 'Hello, Java ! \ No newline at end of file diff --git a/03-regexp-character-classes/love-html5-classes.svg b/03-regexp-character-classes/love-html5-classes.svg index 4b9f4d295..409d5034d 100644 --- a/03-regexp-character-classes/love-html5-classes.svg +++ b/03-regexp-character-classes/love-html5-classes.svg @@ -1,18 +1 @@ - - - - love-html5-classes.svg - Created with sketchtool. - - - - I love HTML - 5 - - - \s \w \w \w \w \ - d - - - - \ No newline at end of file +I love HTML 5\s \w \w \w \w \ d \ No newline at end of file diff --git a/08-regexp-greedy-and-lazy/witch_greedy1.svg b/08-regexp-greedy-and-lazy/witch_greedy1.svg index 65e490e97..a357cbaf8 100644 --- a/08-regexp-greedy-and-lazy/witch_greedy1.svg +++ b/08-regexp-greedy-and-lazy/witch_greedy1.svg @@ -1,16 +1 @@ - - - - witch_greedy1.svg - Created with sketchtool. - - - - a "witch" and her "broom" is one - - - - - - - \ No newline at end of file +a "witch" and her "broom" is one \ No newline at end of file diff --git a/08-regexp-greedy-and-lazy/witch_greedy2.svg b/08-regexp-greedy-and-lazy/witch_greedy2.svg index 1ee351588..1eece4187 100644 --- a/08-regexp-greedy-and-lazy/witch_greedy2.svg +++ b/08-regexp-greedy-and-lazy/witch_greedy2.svg @@ -1,16 +1 @@ - - - - witch_greedy2.svg - Created with sketchtool. - - - - a "witch" and her "broom" is one - - - - - - - \ No newline at end of file +a "witch" and her "broom" is one \ No newline at end of file diff --git a/08-regexp-greedy-and-lazy/witch_greedy3.svg b/08-regexp-greedy-and-lazy/witch_greedy3.svg index e60ed1618..50e16761f 100644 --- a/08-regexp-greedy-and-lazy/witch_greedy3.svg +++ b/08-regexp-greedy-and-lazy/witch_greedy3.svg @@ -1,16 +1 @@ - - - - witch_greedy3.svg - Created with sketchtool. - - - - a "witch" and her "broom" is one - - - - - - - \ No newline at end of file +a "witch" and her "broom" is one \ No newline at end of file diff --git a/08-regexp-greedy-and-lazy/witch_greedy4.svg b/08-regexp-greedy-and-lazy/witch_greedy4.svg index 630a62230..2abb71a50 100644 --- a/08-regexp-greedy-and-lazy/witch_greedy4.svg +++ b/08-regexp-greedy-and-lazy/witch_greedy4.svg @@ -1,19 +1 @@ - - - - witch_greedy4.svg - Created with sketchtool. - - - - a "witch" and her "broom" is one - - - - - - - - - - \ No newline at end of file +a "witch" and her "broom" is one \ No newline at end of file diff --git a/08-regexp-greedy-and-lazy/witch_greedy5.svg b/08-regexp-greedy-and-lazy/witch_greedy5.svg index fa8db5846..9cbfe97f0 100644 --- a/08-regexp-greedy-and-lazy/witch_greedy5.svg +++ b/08-regexp-greedy-and-lazy/witch_greedy5.svg @@ -1,19 +1 @@ - - - - witch_greedy5.svg - Created with sketchtool. - - - - a "witch" and her "broom" is one - - - - - - - - - - \ No newline at end of file +a "witch" and her "broom" is one \ No newline at end of file diff --git a/08-regexp-greedy-and-lazy/witch_greedy6.svg b/08-regexp-greedy-and-lazy/witch_greedy6.svg index c0165e88d..6518201c5 100644 --- a/08-regexp-greedy-and-lazy/witch_greedy6.svg +++ b/08-regexp-greedy-and-lazy/witch_greedy6.svg @@ -1,17 +1 @@ - - - - witch_greedy6.svg - Created with sketchtool. - - - - a "witch" and her "broom" is one - - - - - - - - \ No newline at end of file +a "witch" and her "broom" is one \ No newline at end of file diff --git a/08-regexp-greedy-and-lazy/witch_lazy3.svg b/08-regexp-greedy-and-lazy/witch_lazy3.svg index 61506bb5a..23e5ee8cc 100644 --- a/08-regexp-greedy-and-lazy/witch_lazy3.svg +++ b/08-regexp-greedy-and-lazy/witch_lazy3.svg @@ -1,18 +1 @@ - - - - witch_lazy3.svg - Created with sketchtool. - - - - a "witch" and her "broom" is one - - - - - - - - - \ No newline at end of file +a "witch" and her "broom" is one \ No newline at end of file diff --git a/08-regexp-greedy-and-lazy/witch_lazy4.svg b/08-regexp-greedy-and-lazy/witch_lazy4.svg index fecc1a5ae..487a2ff6b 100644 --- a/08-regexp-greedy-and-lazy/witch_lazy4.svg +++ b/08-regexp-greedy-and-lazy/witch_lazy4.svg @@ -1,18 +1 @@ - - - - witch_lazy4.svg - Created with sketchtool. - - - - a "witch" and her "broom" is one - - - - - - - - - \ No newline at end of file +a "witch" and her "broom" is one \ No newline at end of file diff --git a/08-regexp-greedy-and-lazy/witch_lazy5.svg b/08-regexp-greedy-and-lazy/witch_lazy5.svg index b75a9d7bd..2dd240296 100644 --- a/08-regexp-greedy-and-lazy/witch_lazy5.svg +++ b/08-regexp-greedy-and-lazy/witch_lazy5.svg @@ -1,16 +1 @@ - - - - witch_lazy5.svg - Created with sketchtool. - - - - a "witch" and her "broom" is one - - - - - - - \ No newline at end of file +a "witch" and her "broom" is one \ No newline at end of file diff --git a/08-regexp-greedy-and-lazy/witch_lazy6.svg b/08-regexp-greedy-and-lazy/witch_lazy6.svg index 867e30c85..0d47a9a16 100644 --- a/08-regexp-greedy-and-lazy/witch_lazy6.svg +++ b/08-regexp-greedy-and-lazy/witch_lazy6.svg @@ -1,18 +1 @@ - - - - witch_lazy6.svg - Created with sketchtool. - - - - a "witch" and her "broom" is one - - - - - - - - - \ No newline at end of file +a "witch" and her "broom" is one \ No newline at end of file diff --git a/09-regexp-groups/regexp-nested-groups.svg b/09-regexp-groups/regexp-nested-groups.svg index 75ced6ff6..881b9d57b 100644 --- a/09-regexp-groups/regexp-nested-groups.svg +++ b/09-regexp-groups/regexp-nested-groups.svg @@ -1,48 +1 @@ - - - - regexp-nested-groups.svg - Created with sketchtool. - - - - < - (( - [a-z]+ - ) - \s* - ( - [^>]* - )) - > - - - - - - - - - 1 - - - span class="my" - - - 2 - - - span - - - - - - 3 - - - class="my" - - - - \ No newline at end of file +< (( [a-z]+ ) \s* ( [^>]* )) >1span class="my"2span3class="my" \ No newline at end of file From b267485681aa3c4a1114fe2c369cc5d01fcb6034 Mon Sep 17 00:00:00 2001 From: Ilya Kantor Date: Sun, 11 Aug 2019 14:24:31 +0300 Subject: [PATCH 050/101] minor --- 03-regexp-character-classes/hello-java-boundaries.svg | 2 +- 03-regexp-character-classes/love-html5-classes.svg | 2 +- 08-regexp-greedy-and-lazy/witch_greedy1.svg | 2 +- 08-regexp-greedy-and-lazy/witch_greedy2.svg | 2 +- 08-regexp-greedy-and-lazy/witch_greedy3.svg | 2 +- 08-regexp-greedy-and-lazy/witch_greedy4.svg | 2 +- 08-regexp-greedy-and-lazy/witch_greedy5.svg | 2 +- 08-regexp-greedy-and-lazy/witch_greedy6.svg | 2 +- 08-regexp-greedy-and-lazy/witch_lazy3.svg | 2 +- 08-regexp-greedy-and-lazy/witch_lazy4.svg | 2 +- 08-regexp-greedy-and-lazy/witch_lazy5.svg | 2 +- 08-regexp-greedy-and-lazy/witch_lazy6.svg | 2 +- 09-regexp-groups/regexp-nested-groups.svg | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/03-regexp-character-classes/hello-java-boundaries.svg b/03-regexp-character-classes/hello-java-boundaries.svg index 2af31d5dc..2cb2316a1 100644 --- a/03-regexp-character-classes/hello-java-boundaries.svg +++ b/03-regexp-character-classes/hello-java-boundaries.svg @@ -1 +1 @@ -Hello, Java ! \ No newline at end of file +Hello, Java ! \ No newline at end of file diff --git a/03-regexp-character-classes/love-html5-classes.svg b/03-regexp-character-classes/love-html5-classes.svg index 409d5034d..994c39118 100644 --- a/03-regexp-character-classes/love-html5-classes.svg +++ b/03-regexp-character-classes/love-html5-classes.svg @@ -1 +1 @@ -I love HTML 5\s \w \w \w \w \ d \ No newline at end of file +I love HTML 5\s \w \w \w \w \ d \ No newline at end of file diff --git a/08-regexp-greedy-and-lazy/witch_greedy1.svg b/08-regexp-greedy-and-lazy/witch_greedy1.svg index a357cbaf8..13db398d5 100644 --- a/08-regexp-greedy-and-lazy/witch_greedy1.svg +++ b/08-regexp-greedy-and-lazy/witch_greedy1.svg @@ -1 +1 @@ -a "witch" and her "broom" is one \ No newline at end of file +a "witch" and her "broom" is one \ No newline at end of file diff --git a/08-regexp-greedy-and-lazy/witch_greedy2.svg b/08-regexp-greedy-and-lazy/witch_greedy2.svg index 1eece4187..6ed66590c 100644 --- a/08-regexp-greedy-and-lazy/witch_greedy2.svg +++ b/08-regexp-greedy-and-lazy/witch_greedy2.svg @@ -1 +1 @@ -a "witch" and her "broom" is one \ No newline at end of file +a "witch" and her "broom" is one \ No newline at end of file diff --git a/08-regexp-greedy-and-lazy/witch_greedy3.svg b/08-regexp-greedy-and-lazy/witch_greedy3.svg index 50e16761f..e37f8e75f 100644 --- a/08-regexp-greedy-and-lazy/witch_greedy3.svg +++ b/08-regexp-greedy-and-lazy/witch_greedy3.svg @@ -1 +1 @@ -a "witch" and her "broom" is one \ No newline at end of file +a "witch" and her "broom" is one \ No newline at end of file diff --git a/08-regexp-greedy-and-lazy/witch_greedy4.svg b/08-regexp-greedy-and-lazy/witch_greedy4.svg index 2abb71a50..a1ec3f2be 100644 --- a/08-regexp-greedy-and-lazy/witch_greedy4.svg +++ b/08-regexp-greedy-and-lazy/witch_greedy4.svg @@ -1 +1 @@ -a "witch" and her "broom" is one \ No newline at end of file +a "witch" and her "broom" is one \ No newline at end of file diff --git a/08-regexp-greedy-and-lazy/witch_greedy5.svg b/08-regexp-greedy-and-lazy/witch_greedy5.svg index 9cbfe97f0..05ef0f1a1 100644 --- a/08-regexp-greedy-and-lazy/witch_greedy5.svg +++ b/08-regexp-greedy-and-lazy/witch_greedy5.svg @@ -1 +1 @@ -a "witch" and her "broom" is one \ No newline at end of file +a "witch" and her "broom" is one \ No newline at end of file diff --git a/08-regexp-greedy-and-lazy/witch_greedy6.svg b/08-regexp-greedy-and-lazy/witch_greedy6.svg index 6518201c5..bb2cead9e 100644 --- a/08-regexp-greedy-and-lazy/witch_greedy6.svg +++ b/08-regexp-greedy-and-lazy/witch_greedy6.svg @@ -1 +1 @@ -a "witch" and her "broom" is one \ No newline at end of file +a "witch" and her "broom" is one \ No newline at end of file diff --git a/08-regexp-greedy-and-lazy/witch_lazy3.svg b/08-regexp-greedy-and-lazy/witch_lazy3.svg index 23e5ee8cc..f4c040a75 100644 --- a/08-regexp-greedy-and-lazy/witch_lazy3.svg +++ b/08-regexp-greedy-and-lazy/witch_lazy3.svg @@ -1 +1 @@ -a "witch" and her "broom" is one \ No newline at end of file +a "witch" and her "broom" is one \ No newline at end of file diff --git a/08-regexp-greedy-and-lazy/witch_lazy4.svg b/08-regexp-greedy-and-lazy/witch_lazy4.svg index 487a2ff6b..5cd1fcd80 100644 --- a/08-regexp-greedy-and-lazy/witch_lazy4.svg +++ b/08-regexp-greedy-and-lazy/witch_lazy4.svg @@ -1 +1 @@ -a "witch" and her "broom" is one \ No newline at end of file +a "witch" and her "broom" is one \ No newline at end of file diff --git a/08-regexp-greedy-and-lazy/witch_lazy5.svg b/08-regexp-greedy-and-lazy/witch_lazy5.svg index 2dd240296..c8a945bef 100644 --- a/08-regexp-greedy-and-lazy/witch_lazy5.svg +++ b/08-regexp-greedy-and-lazy/witch_lazy5.svg @@ -1 +1 @@ -a "witch" and her "broom" is one \ No newline at end of file +a "witch" and her "broom" is one \ No newline at end of file diff --git a/08-regexp-greedy-and-lazy/witch_lazy6.svg b/08-regexp-greedy-and-lazy/witch_lazy6.svg index 0d47a9a16..ff271001e 100644 --- a/08-regexp-greedy-and-lazy/witch_lazy6.svg +++ b/08-regexp-greedy-and-lazy/witch_lazy6.svg @@ -1 +1 @@ -a "witch" and her "broom" is one \ No newline at end of file +a "witch" and her "broom" is one \ No newline at end of file diff --git a/09-regexp-groups/regexp-nested-groups.svg b/09-regexp-groups/regexp-nested-groups.svg index 881b9d57b..ce61ff3a7 100644 --- a/09-regexp-groups/regexp-nested-groups.svg +++ b/09-regexp-groups/regexp-nested-groups.svg @@ -1 +1 @@ -< (( [a-z]+ ) \s* ( [^>]* )) >1span class="my"2span3class="my" \ No newline at end of file +< (( [a-z]+ ) \s* ( [^>]* )) >1span class="my"2span3class="my" \ No newline at end of file From a1eab02230113e3f80d299dae4e10a96796741e2 Mon Sep 17 00:00:00 2001 From: Ilya Kantor Date: Wed, 14 Aug 2019 12:36:24 +0300 Subject: [PATCH 051/101] minor --- 03-regexp-character-classes/hello-java-boundaries.svg | 2 +- 03-regexp-character-classes/love-html5-classes.svg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/03-regexp-character-classes/hello-java-boundaries.svg b/03-regexp-character-classes/hello-java-boundaries.svg index 2cb2316a1..f46673a4b 100644 --- a/03-regexp-character-classes/hello-java-boundaries.svg +++ b/03-regexp-character-classes/hello-java-boundaries.svg @@ -1 +1 @@ -Hello, Java ! \ No newline at end of file +H e l l o, J a v a! \ No newline at end of file diff --git a/03-regexp-character-classes/love-html5-classes.svg b/03-regexp-character-classes/love-html5-classes.svg index 994c39118..706e49a3a 100644 --- a/03-regexp-character-classes/love-html5-classes.svg +++ b/03-regexp-character-classes/love-html5-classes.svg @@ -1 +1 @@ -I love HTML 5\s \w \w \w \w \ d \ No newline at end of file +I l o v e H T M L 5\w\w\w\w\s\d \ No newline at end of file From 8de599f84d70fe416e59d9c84d7ee0046336db42 Mon Sep 17 00:00:00 2001 From: Ilya Kantor Date: Wed, 14 Aug 2019 12:54:37 +0300 Subject: [PATCH 052/101] minor --- 03-regexp-character-classes/hello-java-boundaries.svg | 2 +- 03-regexp-character-classes/love-html5-classes.svg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/03-regexp-character-classes/hello-java-boundaries.svg b/03-regexp-character-classes/hello-java-boundaries.svg index f46673a4b..3a0228a4f 100644 --- a/03-regexp-character-classes/hello-java-boundaries.svg +++ b/03-regexp-character-classes/hello-java-boundaries.svg @@ -1 +1 @@ -H e l l o, J a v a! \ No newline at end of file + \ No newline at end of file diff --git a/03-regexp-character-classes/love-html5-classes.svg b/03-regexp-character-classes/love-html5-classes.svg index 706e49a3a..c91cd03d5 100644 --- a/03-regexp-character-classes/love-html5-classes.svg +++ b/03-regexp-character-classes/love-html5-classes.svg @@ -1 +1 @@ -I l o v e H T M L 5\w\w\w\w\s\d \ No newline at end of file + \ No newline at end of file From d61b9f3f50e84d3fb8149c9e6a11c8fcd937988d Mon Sep 17 00:00:00 2001 From: Ilya Kantor Date: Thu, 15 Aug 2019 08:19:00 +0300 Subject: [PATCH 053/101] minor --- 09-regexp-groups/article.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/09-regexp-groups/article.md b/09-regexp-groups/article.md index 141d03005..0d858e44f 100644 --- a/09-regexp-groups/article.md +++ b/09-regexp-groups/article.md @@ -35,7 +35,7 @@ The pattern: `pattern:[-.\w]+@([\w-]+\.)+[\w-]{2,20}`. That regexp is not perfect, but good enough to fix errors or occasional mistypes. -For instance, we can find all emails in the string: +For instance, we can find all emails in the string: ```js run let reg = /[-.\w]+@([\w-]+\.)+[\w-]{2,20}/g; @@ -43,7 +43,7 @@ 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 ``` -In this example parentheses were used to make a group for repeating `pattern:(...)+`. But there are other uses too, let's see them. +In this example parentheses were used to make a group for repetitions `pattern:([\w-]+\.)+`. But there are other uses too, let's see them. ## Contents of parentheses @@ -53,7 +53,7 @@ For instance, we'd like to find HTML tags `pattern:<.*?>`, and process them. Let's wrap the inner content into parentheses, like this: `pattern:<(.*?)>`. -We'll get both the tag as a whole and its content as an array: +Then we'll get both the tag as a whole and its content: ```js run let str = '

Hello, world!

'; @@ -111,7 +111,7 @@ Then groups, numbered from left to right. Whichever opens first gives the first Then in `result[2]` goes the group from the second opening `pattern:(` till the corresponding `pattern:)` -- tag name, then we don't group spaces, but group attributes for `result[3]`. -**If a group is optional and doesn't exist in the match, the corresponding `result` index is present (and equals `undefined`).** +**Even if a group is optional and doesn't exist in the match, the corresponding `result` array item is present (and equals `undefined`).** For instance, let's consider the regexp `pattern:a(z)?(c)?`. It looks for `"a"` optionally followed by `"z"` optionally followed by `"c"`. @@ -208,7 +208,7 @@ let rearranged = str.replace(dateRegexp, (str, ...args) => { ## Non-capturing groups with ?: -Sometimes we need parentheses to correctly apply a quantifier, but we don't want the contents in results. +Sometimes we need parentheses to correctly apply a quantifier, but we don't want their contents in results. A group may be excluded by adding `pattern:?:` in the beginning. From d1393432810670a63793d2241981777810559bb0 Mon Sep 17 00:00:00 2001 From: alevmo <54253179+alevmo@users.noreply.github.com> Date: Sun, 18 Aug 2019 20:54:52 -0400 Subject: [PATCH 054/101] Update article.md --- 02-regexp-methods/article.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/02-regexp-methods/article.md b/02-regexp-methods/article.md index f66a9c794..d1c363526 100644 --- a/02-regexp-methods/article.md +++ b/02-regexp-methods/article.md @@ -2,8 +2,8 @@ There are two sets of methods to deal with regular expressions. -1. First, regular expressions are objects of the built-in [RegExp](mdn:js/RegExp) class, it provides many methods. -2. Besides that, there are methods in regular strings can work with regexps. +1. First, regular expressions are objects of the built-in [RegExp](mdn:js/RegExp) class, which provides many methods. +2. Additionally, there are methods in regular strings that can work with regexps. ## Recipes From ac3791cd19483944e80ab95310d9658b683a93f4 Mon Sep 17 00:00:00 2001 From: paroche <46547072+paroche@users.noreply.github.com> Date: Tue, 20 Aug 2019 17:05:33 -0600 Subject: [PATCH 055/101] Change "occasionally" to "accidentally" --- 20-regexp-unicode/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/20-regexp-unicode/article.md b/20-regexp-unicode/article.md index 7eb3a1e07..dc8c60cb8 100644 --- a/20-regexp-unicode/article.md +++ b/20-regexp-unicode/article.md @@ -72,7 +72,7 @@ alert( '𝒳'.match(/[𝒳𝒴]/u) ); // 𝒳 Let's see one more example. -If we forget the `u` flag and occasionally use surrogate pairs, then we can get an error: +If we forget the `u` flag and accidentally use surrogate pairs, then we can get an error: ```js run '𝒳'.match(/[𝒳-𝒴]/); // SyntaxError: invalid range in character class From 986a5fc6fea20e07f80cea0916e1f933ddaecbc2 Mon Sep 17 00:00:00 2001 From: Ilya Kantor Date: Wed, 21 Aug 2019 12:32:58 +0300 Subject: [PATCH 056/101] minor --- 01-regexp-introduction/article.md | 8 +++++++- 03-regexp-character-classes/article.md | 11 ++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/01-regexp-introduction/article.md b/01-regexp-introduction/article.md index 57fe02f5d..1632a9301 100644 --- a/01-regexp-introduction/article.md +++ b/01-regexp-introduction/article.md @@ -1,10 +1,16 @@ # Patterns and flags +Regular expressions is a powerful way to search and replace in text. + +In JavaScript, they are available as `RegExp` object, and also integrated in methods of strings. + +## Regular Expressions + A regular expression (also "regexp", or just "reg") consists of a *pattern* and optional *flags*. There are two syntaxes to create a regular expression object. -The long syntax: +The "long" syntax: ```js regexp = new RegExp("pattern", "flags"); diff --git a/03-regexp-character-classes/article.md b/03-regexp-character-classes/article.md index 911622162..8e18df91f 100644 --- a/03-regexp-character-classes/article.md +++ b/03-regexp-character-classes/article.md @@ -89,9 +89,11 @@ When regular expression engine is doing the search, it's moving along the string When the pattern contains `pattern:\b`, it tests that the position in string is a word boundary, that is one of three variants: -- Immediately before is `\w`, and immediately after -- not `\w`, or vise versa. -- At string start, and the first string character is `\w`. -- At string end, and the last string character is `\w`. +There are three different positions that qualify as word boundaries: + +- At string start, if the first string character is a word character `\w`. +- Between two characters in the string, where one is a word character `\w` and the other is not. +- At string end, if the last string character is a word character `\w`. For instance, in the string `subject:Hello, Java!` the following positions match `\b`: @@ -101,11 +103,10 @@ So it matches `pattern:\bHello\b`, because: 1. At the beginning of the string the first `\b` test matches. 2. Then the word `Hello` matches. -3. Then `\b` matches, as we're between `o` and a space. +3. Then `\b` matches, as we're between `o` (a word character) and a space (not a word character). Pattern `pattern:\bJava\b` also matches. But not `pattern:\bHell\b` (because there's no word boundary after `l`) and not `Java!\b` (because the exclamation sign is not a wordly character, so there's no word boundary after it). - ```js run alert( "Hello, Java!".match(/\bHello\b/) ); // Hello alert( "Hello, Java!".match(/\bJava\b/) ); // Java From 103b6501d96a5c2d89089934d7574206c5981f18 Mon Sep 17 00:00:00 2001 From: Ilya Kantor Date: Mon, 2 Sep 2019 18:25:18 +0300 Subject: [PATCH 057/101] minor --- 03-regexp-character-classes/love-html5-classes.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/03-regexp-character-classes/love-html5-classes.svg b/03-regexp-character-classes/love-html5-classes.svg index c91cd03d5..9c88cc088 100644 --- a/03-regexp-character-classes/love-html5-classes.svg +++ b/03-regexp-character-classes/love-html5-classes.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file From a9d7fc8bfa563d86a6300287782618b7b0ba0b32 Mon Sep 17 00:00:00 2001 From: Ilya Kantor Date: Wed, 4 Sep 2019 15:44:48 +0300 Subject: [PATCH 058/101] WIP --- 01-regexp-introduction/article.md | 158 ++++++---- 02-regexp-character-classes/article.md | 189 ++++++++++++ .../love-html5-classes.svg | 0 .../1-find-time-hh-mm/solution.md | 6 - .../1-find-time-hh-mm/task.md | 8 - 03-regexp-character-classes/article.md | 270 ------------------ 03-regexp-unicode/article.md | 167 +++++++++++ .../1-start-end/solution.md | 3 +- .../1-start-end/task.md | 0 04-regexp-anchors/article.md | 52 ++++ 05-regexp-multiline-mode/article.md | 87 ++++++ .../1-find-time-hh-mm/solution.md | 6 + 06-regexp-boundary/1-find-time-hh-mm/task.md | 9 + 06-regexp-boundary/article.md | 53 ++++ .../hello-java-boundaries.svg | 0 .../article.md | 2 +- .../1-find-range-1/solution.md | 0 .../1-find-range-1/task.md | 0 .../2-find-time-2-formats/solution.md | 0 .../2-find-time-2-formats/task.md | 0 .../article.md | 6 +- .../1-find-text-manydots/solution.md | 0 .../1-find-text-manydots/task.md | 0 .../2-find-html-colors-6hex/solution.md | 2 +- .../2-find-html-colors-6hex/task.md | 0 .../article.md | 2 +- .../1-lazy-greedy/solution.md | 0 .../1-lazy-greedy/task.md | 0 .../3-find-html-comments/solution.md | 0 .../3-find-html-comments/task.md | 0 .../4-find-html-tags-greedy-lazy/solution.md | 0 .../4-find-html-tags-greedy-lazy/task.md | 0 .../article.md | 0 .../witch_greedy1.svg | 0 .../witch_greedy2.svg | 0 .../witch_greedy3.svg | 0 .../witch_greedy4.svg | 0 .../witch_greedy5.svg | 0 .../witch_greedy6.svg | 0 .../witch_lazy3.svg | 0 .../witch_lazy4.svg | 0 .../witch_lazy5.svg | 0 .../witch_lazy6.svg | 0 .../1-find-webcolor-3-or-6/solution.md | 0 .../1-find-webcolor-3-or-6/task.md | 0 .../2-find-decimal-numbers/solution.md | 0 .../2-find-decimal-numbers/task.md | 0 .../5-parse-expression/solution.md | 0 .../5-parse-expression/task.md | 0 .../article.md | 0 .../regexp-nested-groups.svg | 0 12-regexp-anchors/2-test-mac/solution.md | 21 -- 12-regexp-anchors/2-test-mac/task.md | 20 -- 12-regexp-anchors/article.md | 55 ---- .../article.md | 0 .../01-find-programming-language/solution.md | 0 .../01-find-programming-language/task.md | 0 .../02-find-matching-bbtags/solution.md | 2 +- .../02-find-matching-bbtags/task.md | 0 .../03-match-quoted-string/solution.md | 0 .../03-match-quoted-string/task.md | 0 .../04-match-exact-tag/solution.md | 0 .../04-match-exact-tag/task.md | 0 .../article.md | 0 13-regexp-multiline-mode/article.md | 75 ----- 14-regexp-lookahead-lookbehind/article.md | 8 +- .../article.md | 10 +- .../article.md | 26 +- 20-regexp-unicode/article.md | 89 ------ 21-regexp-unicode-properties/article.md | 86 ------ index.md | 4 - 71 files changed, 698 insertions(+), 718 deletions(-) create mode 100644 02-regexp-character-classes/article.md rename {03-regexp-character-classes => 02-regexp-character-classes}/love-html5-classes.svg (100%) delete mode 100644 03-regexp-character-classes/1-find-time-hh-mm/solution.md delete mode 100644 03-regexp-character-classes/1-find-time-hh-mm/task.md delete mode 100644 03-regexp-character-classes/article.md create mode 100644 03-regexp-unicode/article.md rename {12-regexp-anchors => 04-regexp-anchors}/1-start-end/solution.md (77%) rename {12-regexp-anchors => 04-regexp-anchors}/1-start-end/task.md (100%) create mode 100644 04-regexp-anchors/article.md create mode 100644 05-regexp-multiline-mode/article.md create mode 100644 06-regexp-boundary/1-find-time-hh-mm/solution.md create mode 100644 06-regexp-boundary/1-find-time-hh-mm/task.md create mode 100644 06-regexp-boundary/article.md rename {03-regexp-character-classes => 06-regexp-boundary}/hello-java-boundaries.svg (100%) rename {04-regexp-escaping => 07-regexp-escaping}/article.md (96%) rename {05-regexp-character-sets-and-ranges => 08-regexp-character-sets-and-ranges}/1-find-range-1/solution.md (100%) rename {05-regexp-character-sets-and-ranges => 08-regexp-character-sets-and-ranges}/1-find-range-1/task.md (100%) rename {05-regexp-character-sets-and-ranges => 08-regexp-character-sets-and-ranges}/2-find-time-2-formats/solution.md (100%) rename {05-regexp-character-sets-and-ranges => 08-regexp-character-sets-and-ranges}/2-find-time-2-formats/task.md (100%) rename {05-regexp-character-sets-and-ranges => 08-regexp-character-sets-and-ranges}/article.md (97%) rename {07-regexp-quantifiers => 09-regexp-quantifiers}/1-find-text-manydots/solution.md (100%) rename {07-regexp-quantifiers => 09-regexp-quantifiers}/1-find-text-manydots/task.md (100%) rename {07-regexp-quantifiers => 09-regexp-quantifiers}/2-find-html-colors-6hex/solution.md (91%) rename {07-regexp-quantifiers => 09-regexp-quantifiers}/2-find-html-colors-6hex/task.md (100%) rename {07-regexp-quantifiers => 09-regexp-quantifiers}/article.md (97%) rename {08-regexp-greedy-and-lazy => 10-regexp-greedy-and-lazy}/1-lazy-greedy/solution.md (100%) rename {08-regexp-greedy-and-lazy => 10-regexp-greedy-and-lazy}/1-lazy-greedy/task.md (100%) rename {08-regexp-greedy-and-lazy => 10-regexp-greedy-and-lazy}/3-find-html-comments/solution.md (100%) rename {08-regexp-greedy-and-lazy => 10-regexp-greedy-and-lazy}/3-find-html-comments/task.md (100%) rename {08-regexp-greedy-and-lazy => 10-regexp-greedy-and-lazy}/4-find-html-tags-greedy-lazy/solution.md (100%) rename {08-regexp-greedy-and-lazy => 10-regexp-greedy-and-lazy}/4-find-html-tags-greedy-lazy/task.md (100%) rename {08-regexp-greedy-and-lazy => 10-regexp-greedy-and-lazy}/article.md (100%) rename {08-regexp-greedy-and-lazy => 10-regexp-greedy-and-lazy}/witch_greedy1.svg (100%) rename {08-regexp-greedy-and-lazy => 10-regexp-greedy-and-lazy}/witch_greedy2.svg (100%) rename {08-regexp-greedy-and-lazy => 10-regexp-greedy-and-lazy}/witch_greedy3.svg (100%) rename {08-regexp-greedy-and-lazy => 10-regexp-greedy-and-lazy}/witch_greedy4.svg (100%) rename {08-regexp-greedy-and-lazy => 10-regexp-greedy-and-lazy}/witch_greedy5.svg (100%) rename {08-regexp-greedy-and-lazy => 10-regexp-greedy-and-lazy}/witch_greedy6.svg (100%) rename {08-regexp-greedy-and-lazy => 10-regexp-greedy-and-lazy}/witch_lazy3.svg (100%) rename {08-regexp-greedy-and-lazy => 10-regexp-greedy-and-lazy}/witch_lazy4.svg (100%) rename {08-regexp-greedy-and-lazy => 10-regexp-greedy-and-lazy}/witch_lazy5.svg (100%) rename {08-regexp-greedy-and-lazy => 10-regexp-greedy-and-lazy}/witch_lazy6.svg (100%) rename {09-regexp-groups => 11-regexp-groups}/1-find-webcolor-3-or-6/solution.md (100%) rename {09-regexp-groups => 11-regexp-groups}/1-find-webcolor-3-or-6/task.md (100%) rename {09-regexp-groups => 11-regexp-groups}/2-find-decimal-numbers/solution.md (100%) rename {09-regexp-groups => 11-regexp-groups}/2-find-decimal-numbers/task.md (100%) rename {09-regexp-groups => 11-regexp-groups}/5-parse-expression/solution.md (100%) rename {09-regexp-groups => 11-regexp-groups}/5-parse-expression/task.md (100%) rename {09-regexp-groups => 11-regexp-groups}/article.md (100%) rename {09-regexp-groups => 11-regexp-groups}/regexp-nested-groups.svg (100%) delete mode 100644 12-regexp-anchors/2-test-mac/solution.md delete mode 100644 12-regexp-anchors/2-test-mac/task.md delete mode 100644 12-regexp-anchors/article.md rename {10-regexp-backreferences => 12-regexp-backreferences}/article.md (100%) rename {11-regexp-alternation => 13-regexp-alternation}/01-find-programming-language/solution.md (100%) rename {11-regexp-alternation => 13-regexp-alternation}/01-find-programming-language/task.md (100%) rename {11-regexp-alternation => 13-regexp-alternation}/02-find-matching-bbtags/solution.md (79%) rename {11-regexp-alternation => 13-regexp-alternation}/02-find-matching-bbtags/task.md (100%) rename {11-regexp-alternation => 13-regexp-alternation}/03-match-quoted-string/solution.md (100%) rename {11-regexp-alternation => 13-regexp-alternation}/03-match-quoted-string/task.md (100%) rename {11-regexp-alternation => 13-regexp-alternation}/04-match-exact-tag/solution.md (100%) rename {11-regexp-alternation => 13-regexp-alternation}/04-match-exact-tag/task.md (100%) rename {11-regexp-alternation => 13-regexp-alternation}/article.md (100%) delete mode 100644 13-regexp-multiline-mode/article.md rename {22-regexp-sticky => 16-regexp-sticky}/article.md (83%) rename {02-regexp-methods => 17-regexp-methods}/article.md (93%) delete mode 100644 20-regexp-unicode/article.md delete mode 100644 21-regexp-unicode-properties/article.md diff --git a/01-regexp-introduction/article.md b/01-regexp-introduction/article.md index 1632a9301..5cbe011ac 100644 --- a/01-regexp-introduction/article.md +++ b/01-regexp-introduction/article.md @@ -2,7 +2,7 @@ Regular expressions is a powerful way to search and replace in text. -In JavaScript, they are available as `RegExp` object, and also integrated in methods of strings. +In JavaScript, they are available as [RegExp](mdn:js/RegExp) object, and also integrated in methods of strings. ## Regular Expressions @@ -23,35 +23,43 @@ regexp = /pattern/; // no flags regexp = /pattern/gmi; // with flags g,m and i (to be covered soon) ``` -Slashes `"/"` tell JavaScript that we are creating a regular expression. They play the same role as quotes for strings. +Slashes `pattern:/.../` tell JavaScript that we are creating a regular expression. They play the same role as quotes for strings. -## Usage +In both cases `regexp` becomes an object of the built-in `RegExp` class. -To search inside a string, we can use method [search](mdn:js/String/search). +The main difference between these two syntaxes is that slashes `pattern:/.../` do not allow to insert expressions (like strings with `${...}`). They are fully static. -Here's an example: +Slashes are used when we know the regular expression at the code writing time -- and that's the most common situation. While `new RegExp` is used when we need to create a regexp "on the fly", from a dynamically generated string, for instance: -```js run -let str = "I love JavaScript!"; // will search here +```js +let tag = prompt("What tag do you want to find?", "h2"); -let regexp = /love/; -alert( str.search(regexp) ); // 2 +let regexp = new RegExp(`<${tag}>`); // same as /

/ if answered "h2" in the prompt above ``` -The `str.search` method looks for the pattern `pattern:/love/` and returns the position inside the string. As we might guess, `pattern:/love/` is the simplest possible pattern. What it does is a simple substring search. +## Flags -The code above is the same as: +Regular expressions may have flags that affect the search. -```js run -let str = "I love JavaScript!"; // will search here +There are only 6 of them in JavaScript: -let substr = 'love'; -alert( str.search(substr) ); // 2 -``` +`pattern:i` +: With this flag the search is case-insensitive: no difference between `A` and `a` (see the example below). -So searching for `pattern:/love/` is the same as searching for `"love"`. +`pattern:g` +: With this flag the search looks for all matches, without it -- only the first one. -But that's only for now. Soon we'll create more complex regular expressions with much more searching power. +`pattern:m` +: Multiline mode (covered in the chapter ). + +`pattern:s` +: Enables "dotall" mode, that allows a dot `pattern:.` to match newline character `\n` (covered in the chapter ). + +`pattern:u` +: Enables full unicode support. The flag enables correct processing of surrogate pairs. More about that in the chapter . + +`pattern:y` +: "Sticky" mode: searching at the exact position in the text (covered in the chapter ) ```smart header="Colors" From here on the color scheme is: @@ -61,65 +69,109 @@ From here on the color scheme is: - result -- `match:green` ``` +## Searching: str.match -````smart header="When to use `new RegExp`?" -Normally we use the short syntax `/.../`. But it does not support variable insertions `${...}`. +As it was said previously, regular expressions are integrated with string methods. -On the other hand, `new RegExp` allows to construct a pattern dynamically from a string, so it's more flexible. +The method `str.match(regexp)` finds all matches of `regexp` in the string `str`. -Here's an example of a dynamically generated regexp: +It has 3 working modes: -```js run -let tag = prompt("Which tag you want to search?", "h2"); -let regexp = new RegExp(`<${tag}>`); +1. If the regular expression has flag `pattern:g`, it returns an array of all matches: + ```js run + let str = "We will, we will rock you"; -// finds

by default -alert( "

".search(regexp)); -``` -```` + alert( str.match(/we/gi) ); // We,we (an array of 2 matches) + ``` + Please note that both `match:We` and `match:we` are found, because flag `pattern:i` makes the regular expression case-insensitive. +2. If there's no such flag it returns only the first match in the form of an array, with the full match at index `0` and some additional details in properties: + ```js run + let str = "We will, we will rock you"; -## Flags + let result = str.match(/we/i); // without flag g -Regular expressions may have flags that affect the search. + alert( result[0] ); // We (1st match) + alert( result.length ); // 1 -There are only 6 of them in JavaScript: + // Details: + alert( result.index ); // 0 (position of the match) + alert( result.input ); // We will, we will rock you (source string) + ``` + The array may have other indexes, besides `0` if a part of the regular expression is enclosed in parentheses. We'll cover that in the chapter . -`i` -: With this flag the search is case-insensitive: no difference between `A` and `a` (see the example below). +3. And, finally, if there are no matches, `null` is returned (doesn't matter if there's flag `pattern:g` or not). -`g` -: With this flag the search looks for all matches, without it -- only the first one (we'll see uses in the next chapter). + That's a very important nuance. If there are no matches, we get not an empty array, but `null`. Forgetting about that may lead to errors, e.g.: -`m` -: Multiline mode (covered in the chapter ). + ```js run + let matches = "JavaScript".match(/HTML/); // = null -`s` -: "Dotall" mode, allows `.` to match newlines (covered in the chapter ). + if (!matches.length) { // Error: Cannot read property 'length' of null + alert("Error in the line above"); + } + ``` -`u` -: Enables full unicode support. The flag enables correct processing of surrogate pairs. More about that in the chapter . + If we'd like the result to be always an array, we can write it this way: + + ```js run + let matches = "JavaScript".match(/HTML/)*!* || []*/!*; -`y` -: Sticky mode (covered in the chapter ) + if (!matches.length) { + alert("No matches"); // now it works + } + ``` -We'll cover all these flags further in the tutorial. +## Replacing: str.replace -For now, the simplest flag is `i`, here's an example: +The method `str.replace(regexp, replacement)` replaces matches with `regexp` in string `str` with `replacement` (all matches, if there's flag `pattern:g`, otherwise only the first one). + +For instance: ```js run -let str = "I love JavaScript!"; +// no flag g +alert( "We will, we will".replace(/we/i, "I") ); // I will, we will + +// with flag g +alert( "We will, we will".replace(/we/ig, "I") ); // I will, I will +``` + +The second argument is the `replacement` string. We can use special character combinations in it to insert fragments of the match: -alert( str.search(/LOVE/i) ); // 2 (found lowercased) +| Symbols | Action in the replacement string | +|--------|--------| +|`$&`|inserts the whole match| +|$`|inserts a part of the string before the match| +|`$'`|inserts a part of the string after the match| +|`$n`|if `n` is a 1-2 digit number, then it inserts the contents of n-th parentheses, more about it in the chapter | +|`$`|inserts the contents of the parentheses with the given `name`, more about it in the chapter | +|`$$`|inserts character `$` | + +An example with `pattern:$&`: + +```js run +alert( "I love HTML".replace(/HTML/, "$& and JavaScript") ); // I love HTML and JavaScript +``` + +## Testing: regexp.test + +The method `regexp.test(str)` looks for at least one match, if found, returns `true`, otherwise `false`. + +```js run +let str = "I love JavaScript"; +let reg = /LOVE/i; -alert( str.search(/LOVE/) ); // -1 (nothing found without 'i' flag) +alert( reg.test(str) ); // true ``` -So the `i` flag already makes regular expressions more powerful than a simple substring search. But there's so much more. We'll cover other flags and features in the next chapters. +Further in this chapter we'll study more regular expressions, come across many other examples and also meet other methods. +Full information about the methods is given in the article . ## Summary -- A regular expression consists of a pattern and optional flags: `g`, `i`, `m`, `u`, `s`, `y`. -- Without flags and special symbols that we'll study later, the search by a regexp is the same as a substring search. -- The method `str.search(regexp)` returns the index where the match is found or `-1` if there's no match. In the next chapter we'll see other methods. +- A regular expression consists of a pattern and optional flags: `pattern:g`, `pattern:i`, `pattern:m`, `pattern:u`, `pattern:s`, `pattern:y`. +- Without flags and special symbols that we'll study later, the search by a regexp is the same as a substring search. +- The method `str.match(regexp)` looks for matches: all of them if there's `pattern:g` flag, otherwise only the first one. +- The method `str.replace(regexp, replacement)` replaces matches with `regexp` by `replacement`: all of them if there's `pattern:g` flag, otherwise only the first one. +- The method `regexp.test(str)` returns `true` if there's at least one match, otherwise `false`. diff --git a/02-regexp-character-classes/article.md b/02-regexp-character-classes/article.md new file mode 100644 index 000000000..881b6ba25 --- /dev/null +++ b/02-regexp-character-classes/article.md @@ -0,0 +1,189 @@ +# Character classes + +Consider a practical task -- we have a phone number like `"+7(903)-123-45-67"`, and we need to turn it into pure numbers: `79035419441`. + +To do so, we can find and remove anything that's not a number. Character classes can help with that. + +A *character class* is a special notation that matches any symbol from a certain set. + +For the start, let's explore the "digit" class. It's written as `pattern:\d` and corresponds to "any single digit". + +For instance, the let's find the first digit in the phone number: + +```js run +let str = "+7(903)-123-45-67"; + +let reg = /\d/; + +alert( str.match(reg) ); // 7 +``` + +Without the flag `pattern:g`, the regular expression only looks for the first match, that is the first digit `pattern:\d`. + +Let's add the `pattern:g` flag to find all digits: + +```js run +let str = "+7(903)-123-45-67"; + +let reg = /\d/g; + +alert( str.match(reg) ); // array of matches: 7,9,0,3,1,2,3,4,5,6,7 + +// let's make the digits-only phone number of them: +alert( str.match(reg).join('') ); // 79035419441 +``` + +That was a character class for digits. There are other character classes as well. + +Most used are: + +`pattern:\d` ("d" is from "digit") +: A digit: a character from `0` to `9`. + +`pattern:\s` ("s" is from "space") +: A space symbol: includes spaces, tabs `\t`, newlines `\n` and few other rare characters: `\v`, `\f` and `\r`. + +`pattern:\w` ("w" is from "word") +: A "wordly" character: either a letter of Latin alphabet or a digit or an underscore `_`. Non-Latin letters (like cyrillic or hindi) do not belong to `pattern:\w`. + +For instance, `pattern:\d\s\w` means a "digit" followed by a "space character" followed by a "wordly character", such as `match:1 a`. + +**A regexp may contain both regular symbols and character classes.** + +For instance, `pattern:CSS\d` matches a string `match:CSS` with a digit after it: + +```js run +let str = "Is there CSS4?"; +let reg = /CSS\d/ + +alert( str.match(reg) ); // CSS4 +``` + +Also we can use many character classes: + +```js run +alert( "I love HTML5!".match(/\s\w\w\w\w\d/) ); // ' HTML5' +``` + +The match (each regexp character class has the corresponding result character): + +![](love-html5-classes.svg) + +## Inverse classes + +For every character class there exists an "inverse class", denoted with the same letter, but uppercased. + +The "inverse" means that it matches all other characters, for instance: + +`pattern:\D` +: Non-digit: any character except `pattern:\d`, for instance a letter. + +`pattern:\S` +: Non-space: any character except `pattern:\s`, for instance a letter. + +`pattern:\W` +: Non-wordly character: anything but `pattern:\w`, e.g a non-latin letter or a space. + +In the beginning of the chapter we saw how to make a number-only phone number from a string like `subject:+7(903)-123-45-67`: find all digits and join them. + +```js run +let str = "+7(903)-123-45-67"; + +alert( str.match(/\d/g).join('') ); // 79031234567 +``` + +An alternative, shorter way is to find non-digits `pattern:\D` and remove them from the string: + +```js run +let str = "+7(903)-123-45-67"; + +alert( str.replace(/\D/g, "") ); // 79031234567 +``` + +## A dot is any character + +A dot `pattern:.` is a special character class that matches "any character except a newline". + +For instance: + +```js run +alert( "Z".match(/./) ); // Z +``` + +Or in the middle of a regexp: + +```js run +let reg = /CS.4/; + +alert( "CSS4".match(reg) ); // CSS4 +alert( "CS-4".match(reg) ); // CS-4 +alert( "CS 4".match(reg) ); // CS 4 (space is also a character) +``` + +Please note that a dot means "any character", but not the "absense of a character". There must be a character to match it: + +```js run +alert( "CS4".match(/CS.4/) ); // null, no match because there's no character for the dot +``` + +### Dot as literally any character with "s" flag + +Usually a dot doesn't match a newline character `\n`. + +For instance, the regexp `pattern:A.B` matches `match:A`, and then `match:B` with any character between them, except a newline `\n`: + +```js run +alert( "A\nB".match(/A.B/) ); // null (no match) +``` + +There are many situations when we'd like a dot to mean literally "any character", newline included. + +That's what flag `pattern:s` does. If a regexp has it, then a dot `pattern:.` matches literally any character: + +```js run +alert( "A\nB".match(/A.B/s) ); // A\nB (match!) +``` + +````warn header="Pay attention to spaces" +Usually we pay little attention to spaces. For us strings `subject:1-5` and `subject:1 - 5` are nearly identical. + +But if a regexp doesn't take spaces into account, it may fail to work. + +Let's try to find digits separated by a hyphen: + +```js run +alert( "1 - 5".match(/\d-\d/) ); // null, no match! +``` + +Let's fix it adding spaces into the regexp `pattern:\d - \d`: + +```js run +alert( "1 - 5".match(/\d - \d/) ); // 1 - 5, now it works +// or we can use \s class: +alert( "1 - 5".match(/\d\s-\s\d/) ); // 1 - 5, also works +``` + +**A space is a character. Equal in importance with any other character.** + +We can't add or remove spaces from a regular expression and expect to work the same. + +In other words, in a regular expression all characters matter, spaces too. +```` + +## Summary + +There exist following character classes: + +- `pattern:\d` -- digits. +- `pattern:\D` -- non-digits. +- `pattern:\s` -- space symbols, tabs, newlines. +- `pattern:\S` -- all but `pattern:\s`. +- `pattern:\w` -- Latin letters, digits, underscore `'_'`. +- `pattern:\W` -- all but `pattern:\w`. +- `pattern:.` -- any character if with the regexp `'s'` flag, otherwise any except a newline `\n`. + +...But that's not all! + +Unicode encoding, used by JavaScript for strings, provides many properties for characters, like: which language the letter belongs to (if it's a letter) it is it a punctuation sign, etc. + +We can search by these properties as well. That requires flag `pattern:u`, covered in the next article. diff --git a/03-regexp-character-classes/love-html5-classes.svg b/02-regexp-character-classes/love-html5-classes.svg similarity index 100% rename from 03-regexp-character-classes/love-html5-classes.svg rename to 02-regexp-character-classes/love-html5-classes.svg diff --git a/03-regexp-character-classes/1-find-time-hh-mm/solution.md b/03-regexp-character-classes/1-find-time-hh-mm/solution.md deleted file mode 100644 index 829eda13e..000000000 --- a/03-regexp-character-classes/1-find-time-hh-mm/solution.md +++ /dev/null @@ -1,6 +0,0 @@ - -The answer: `pattern:\b\d\d:\d\d\b`. - -```js run -alert( "Breakfast at 09:00 in the room 123:456.".match( /\b\d\d:\d\d\b/ ) ); // 09:00 -``` diff --git a/03-regexp-character-classes/1-find-time-hh-mm/task.md b/03-regexp-character-classes/1-find-time-hh-mm/task.md deleted file mode 100644 index 5e32b9c48..000000000 --- a/03-regexp-character-classes/1-find-time-hh-mm/task.md +++ /dev/null @@ -1,8 +0,0 @@ -# Find the time - -The time has a format: `hours:minutes`. Both hours and minutes has two digits, like `09:00`. - -Make a regexp to find time in the string: `subject:Breakfast at 09:00 in the room 123:456.` - -P.S. In this task there's no need to check time correctness yet, so `25:99` can also be a valid result. -P.P.S. The regexp shouldn't match `123:456`. diff --git a/03-regexp-character-classes/article.md b/03-regexp-character-classes/article.md deleted file mode 100644 index 8e18df91f..000000000 --- a/03-regexp-character-classes/article.md +++ /dev/null @@ -1,270 +0,0 @@ -# Character classes - -Consider a practical task -- we have a phone number `"+7(903)-123-45-67"`, and we need to turn it into pure numbers: `79035419441`. - -To do so, we can find and remove anything that's not a number. Character classes can help with that. - -A character class is a special notation that matches any symbol from a certain set. - -For the start, let's explore a "digit" class. It's written as `\d`. We put it in the pattern, that means "any single digit". - -For instance, the let's find the first digit in the phone number: - -```js run -let str = "+7(903)-123-45-67"; - -let reg = /\d/; - -alert( str.match(reg) ); // 7 -``` - -Without the flag `g`, the regular expression only looks for the first match, that is the first digit `\d`. - -Let's add the `g` flag to find all digits: - -```js run -let str = "+7(903)-123-45-67"; - -let reg = /\d/g; - -alert( str.match(reg) ); // array of matches: 7,9,0,3,1,2,3,4,5,6,7 - -alert( str.match(reg).join('') ); // 79035419441 -``` - -That was a character class for digits. There are other character classes as well. - -Most used are: - -`\d` ("d" is from "digit") -: A digit: a character from `0` to `9`. - -`\s` ("s" is from "space") -: A space symbol: that includes spaces, tabs, newlines. - -`\w` ("w" is from "word") -: A "wordly" character: either a letter of English alphabet or a digit or an underscore. Non-Latin letters (like cyrillic or hindi) do not belong to `\w`. - -For instance, `pattern:\d\s\w` means a "digit" followed by a "space character" followed by a "wordly character", like `"1 a"`. - -**A regexp may contain both regular symbols and character classes.** - -For instance, `pattern:CSS\d` matches a string `match:CSS` with a digit after it: - -```js run -let str = "CSS4 is cool"; -let reg = /CSS\d/ - -alert( str.match(reg) ); // CSS4 -``` - -Also we can use many character classes: - -```js run -alert( "I love HTML5!".match(/\s\w\w\w\w\d/) ); // ' HTML5' -``` - -The match (each character class corresponds to one result character): - -![](love-html5-classes.svg) - -## Word boundary: \b - -A word boundary `pattern:\b` -- is a special character class. - -It does not denote a character, but rather a boundary between characters. - -For instance, `pattern:\bJava\b` matches `match:Java` in the string `subject:Hello, Java!`, but not in the script `subject:Hello, JavaScript!`. - -```js run -alert( "Hello, Java!".match(/\bJava\b/) ); // Java -alert( "Hello, JavaScript!".match(/\bJava\b/) ); // null -``` - -The boundary has "zero width" in a sense that usually a character class means a character in the result (like a wordly character or a digit), but not in this case. - -The boundary is a test. - -When regular expression engine is doing the search, it's moving along the string in an attempt to find the match. At each string position it tries to find the pattern. - -When the pattern contains `pattern:\b`, it tests that the position in string is a word boundary, that is one of three variants: - -There are three different positions that qualify as word boundaries: - -- At string start, if the first string character is a word character `\w`. -- Between two characters in the string, where one is a word character `\w` and the other is not. -- At string end, if the last string character is a word character `\w`. - -For instance, in the string `subject:Hello, Java!` the following positions match `\b`: - -![](hello-java-boundaries.svg) - -So it matches `pattern:\bHello\b`, because: - -1. At the beginning of the string the first `\b` test matches. -2. Then the word `Hello` matches. -3. Then `\b` matches, as we're between `o` (a word character) and a space (not a word character). - -Pattern `pattern:\bJava\b` also matches. But not `pattern:\bHell\b` (because there's no word boundary after `l`) and not `Java!\b` (because the exclamation sign is not a wordly character, so there's no word boundary after it). - -```js run -alert( "Hello, Java!".match(/\bHello\b/) ); // Hello -alert( "Hello, Java!".match(/\bJava\b/) ); // Java -alert( "Hello, Java!".match(/\bHell\b/) ); // null (no match) -alert( "Hello, Java!".match(/\bJava!\b/) ); // null (no match) -``` - -Once again let's note that `pattern:\b` makes the searching engine to test for the boundary, so that `pattern:Java\b` finds `match:Java` only when followed by a word boundary, but it does not add a letter to the result. - -Usually we use `\b` to find standalone English words. So that if we want `"Java"` language then `pattern:\bJava\b` finds exactly a standalone word and ignores it when it's a part of another word, e.g. it won't match `match:Java` in `subject:JavaScript`. - -Another example: a regexp `pattern:\b\d\d\b` looks for standalone two-digit numbers. In other words, it requires that before and after `pattern:\d\d` must be a symbol different from `\w` (or beginning/end of the string). - -```js run -alert( "1 23 456 78".match(/\b\d\d\b/g) ); // 23,78 -``` - -```warn header="Word boundary doesn't work for non-Latin alphabets" -The word boundary check `\b` tests for a boundary between `\w` and something else. But `\w` means an English letter (or a digit or an underscore), so the test won't work for other characters (like cyrillic or hieroglyphs). - -Later we'll come by Unicode character classes that allow to solve the similar task for different languages. -``` - - -## Inverse classes - -For every character class there exists an "inverse class", denoted with the same letter, but uppercased. - -The "reverse" means that it matches all other characters, for instance: - -`\D` -: Non-digit: any character except `\d`, for instance a letter. - -`\S` -: Non-space: any character except `\s`, for instance a letter. - -`\W` -: Non-wordly character: anything but `\w`. - -`\B` -: Non-boundary: a test reverse to `\b`. - -In the beginning of the chapter we saw how to get all digits from the phone `subject:+7(903)-123-45-67`. - -One way was to match all digits and join them: - -```js run -let str = "+7(903)-123-45-67"; - -alert( str.match(/\d/g).join('') ); // 79031234567 -``` - -An alternative, shorter way is to find non-digits `\D` and remove them from the string: - - -```js run -let str = "+7(903)-123-45-67"; - -alert( str.replace(/\D/g, "") ); // 79031234567 -``` - -## Spaces are regular characters - -Usually we pay little attention to spaces. For us strings `subject:1-5` and `subject:1 - 5` are nearly identical. - -But if a regexp doesn't take spaces into account, it may fail to work. - -Let's try to find digits separated by a dash: - -```js run -alert( "1 - 5".match(/\d-\d/) ); // null, no match! -``` - -Here we fix it by adding spaces into the regexp `pattern:\d - \d`: - -```js run -alert( "1 - 5".match(/\d - \d/) ); // 1 - 5, now it works -``` - -**A space is a character. Equal in importance with any other character.** - -Of course, spaces in a regexp are needed only if we look for them. Extra spaces (just like any other extra characters) may prevent a match: - -```js run -alert( "1-5".match(/\d - \d/) ); // null, because the string 1-5 has no spaces -``` - -In other words, in a regular expression all characters matter, spaces too. - -## A dot is any character - -The dot `"."` is a special character class that matches "any character except a newline". - -For instance: - -```js run -alert( "Z".match(/./) ); // Z -``` - -Or in the middle of a regexp: - -```js run -let reg = /CS.4/; - -alert( "CSS4".match(reg) ); // CSS4 -alert( "CS-4".match(reg) ); // CS-4 -alert( "CS 4".match(reg) ); // CS 4 (space is also a character) -``` - -Please note that the dot means "any character", but not the "absense of a character". There must be a character to match it: - -```js run -alert( "CS4".match(/CS.4/) ); // null, no match because there's no character for the dot -``` - -### The dotall "s" flag - -Usually a dot doesn't match a newline character. - -For instance, `pattern:A.B` matches `match:A`, and then `match:B` with any character between them, except a newline. - -This doesn't match: - -```js run -alert( "A\nB".match(/A.B/) ); // null (no match) - -// a space character would match, or a letter, but not \n -``` - -Sometimes it's inconvenient, we really want "any character", newline included. - -That's what `s` flag does. If a regexp has it, then the dot `"."` match literally any character: - -```js run -alert( "A\nB".match(/A.B/s) ); // A\nB (match!) -``` - -## Summary - -There exist following character classes: - -- `pattern:\d` -- digits. -- `pattern:\D` -- non-digits. -- `pattern:\s` -- space symbols, tabs, newlines. -- `pattern:\S` -- all but `pattern:\s`. -- `pattern:\w` -- English letters, digits, underscore `'_'`. -- `pattern:\W` -- all but `pattern:\w`. -- `pattern:.` -- any character if with the regexp `'s'` flag, otherwise any except a newline. - -...But that's not all! - -The Unicode encoding, used by JavaScript for strings, provides many properties for characters, like: which language the letter belongs to (if a letter) it is it a punctuation sign, etc. - -Modern JavaScript allows to use these properties in regexps to look for characters, for instance: - -- A cyrillic letter is: `pattern:\p{Script=Cyrillic}` or `pattern:\p{sc=Cyrillic}`. -- A dash (be it a small hyphen `-` or a long dash `—`): `pattern:\p{Dash_Punctuation}` or `pattern:\p{pd}`. -- A currency symbol, such as `$`, `€` or another: `pattern:\p{Currency_Symbol}` or `pattern:\p{sc}`. -- ...And much more. Unicode has a lot of character categories that we can select from. - -These patterns require `'u'` regexp flag to work. More about that in the chapter [](info:regexp-unicode). diff --git a/03-regexp-unicode/article.md b/03-regexp-unicode/article.md new file mode 100644 index 000000000..7a14621b3 --- /dev/null +++ b/03-regexp-unicode/article.md @@ -0,0 +1,167 @@ +# Unicode: flag "u" and class \p{...} + +JavaScript uses [Unicode encoding](https://en.wikipedia.org/wiki/Unicode) for strings. Most characters are encoding with 2 bytes, but that allows to represent at most 65536 characters. + +That range is not big enough to encode all possible characters, that's why some rare characters are encoded with 4 bytes, for instance like `𝒳` (mathematical X) or `😄` (a smile), some hieroglyphs and so on. + +Here are the unicode values of some characters: + +| Character | Unicode | Bytes count in unicode | +|------------|---------|--------| +| a | `0x0061` | 2 | +| ≈ | `0x2248` | 2 | +|𝒳| `0x1d4b3` | 4 | +|𝒴| `0x1d4b4` | 4 | +|😄| `0x1f604` | 4 | + +So characters like `a` and `≈` occupy 2 bytes, while codes for `𝒳`, `𝒴` and `😄` are longer, they have 4 bytes. + +Long time ago, when JavaScript language was created, Unicode encoding was simpler: there were no 4-byte characters. So, some language features still handle them incorrectly. + +For instance, `length` thinks that here are two characters: + +```js run +alert('😄'.length); // 2 +alert('𝒳'.length); // 2 +``` + +...But we can see that there's only one, right? The point is that `length` treats 4 bytes as two 2-byte characters. That's incorrect, because they must be considered only together (so-called "surrogate pair", you can read about them in the article ). + +By default, regular expressions also treat 4-byte "long characters" as a pair of 2-byte ones. And, as it happens with strings, that may lead to odd results. We'll see that a bit later, in the article . + +Unlike strings, regular expressions have flag `pattern:u` that fixes such problems. With such flag, a regexp handles 4-byte characters correctly. And also Unicode property search becomes available, we'll get to it next. + +## Unicode properties \p{...} + +```warn header="Not supported in Firefox and Edge" +Despite being a part of the standard since 2018, unicode proeprties are not supported in Firefox ([bug](https://bugzilla.mozilla.org/show_bug.cgi?id=1361876)) and Edge ([bug](https://github.com/Microsoft/ChakraCore/issues/2969)). + +There's [XRegExp](http://xregexp.com) library that provides "extended" regular expressions with cross-browser support for unicode properties. +``` + +Every character in Unicode has a lot of properties. They describe what "category" the character belongs to, contain miscellaneous information about it. + +For instance, if a character has `Letter` property, it means that the character belongs to an alphabet (of any language). And `Number` property means that it's a digit: maybe Arabic or Chinese, and so on. + +We can search for characters with a property, written as `pattern:\p{…}`. To use `pattern:\p{…}`, a regular expression must have flag `pattern:u`. + +For instance, `\p{Letter}` denotes a letter in any of language. We can also use `\p{L}`, as `L` is an alias of `Letter`. There are shorter aliases for almost every property. + +In the example below three kinds of letters will be found: English, Georgean and Korean. + +```js run +let str = "A ბ ㄱ"; + +alert( str.match(/\p{L}/gu) ); // A,ბ,ㄱ +alert( str.match(/\p{L}/g) ); // null (no matches, as there's no flag "u") +``` + +Here's the main character categories and their subcategories: + +- Letter `L`: + - lowercase `Ll` + - modifier `Lm`, + - titlecase `Lt`, + - uppercase `Lu`, + - other `Lo`. +- Number `N`: + - decimal digit `Nd`, + - letter number `Nl`, + - other `No`. +- Punctuation `P`: + - connector `Pc`, + - dash `Pd`, + - initial quote `Pi`, + - final quote `Pf`, + - open `Ps`, + - close `Pe`, + - other `Po`. +- Mark `M` (accents etc): + - spacing combining `Mc`, + - enclosing `Me`, + - non-spacing `Mn`. +- Symbol `S`: + - currency `Sc`, + - modifier `Sk`, + - math `Sm`, + - other `So`. +- Separator `Z`: + - line `Zl`, + - paragraph `Zp`, + - space `Zs`. +- Other `C`: + - control `Cc`, + - format `Cf`, + - not assigned `Cn`, + -- private use `Co`, + - surrogate `Cs`. + + +So, e.g. if we need letters in lower case, we can write `pattern:\p{Ll}`, punctuation signs: `pattern:\p{P}` and so on. + +There are also other derived categories, like: +- `Alphabetic` (`Alpha`), includes Letters `L`, plus letter numbers `Nl` (e.g. Ⅻ - a character for the roman number 12), plus some other symbols `Other_Alphabetic` (`OAlpha`). +- `Hex_Digit` includes hexadecimal digits: `0-9`, `a-f`. +- ...And so on. + +Unicode supports many different properties, their full list would require a lot of space, so here are the references: + +- List all properties by a character: . +- List all characters by a property: . +- Short aliases for properties: . +- A full base of Unicode characters in text format, with all properties, is here: . + +### Example: hexadecimal numbers + +For instance, let's look for hexadecimal numbers, written as `xFF`, where `F` is a hex digit (0..1 or A..F). + +A hex digit can be denoted as `pattern:\p{Hex_Digit}`: + +```js run +let reg = /x\p{Hex_Digit}\p{Hex_Digit}/u; + +alert("number: xAF".match(reg)); // xAF +``` + +### Example: Chinese hieroglyphs + +Let's look for Chinese hieroglyphs. + +There's a unicode property `Script` (a writing system), that may have a value: `Cyrillic`, `Greek`, `Arabic`, `Han` (Chinese) and so on, [here's the full list]("https://en.wikipedia.org/wiki/Script_(Unicode)"). + +To look for characters in a given writing system we should use `pattern:Script=`, e.g. for Cyrillic letters: `pattern:\p{sc=Cyrillic}`, for Chinese hieroglyphs: `pattern:\p{sc=Han}`, and so on: + +```js run +let regexp = /\p{sc=Han}/gu; // returns Chinese hieroglyphs + +let str = `Hello Привет 你好 123_456`; + +alert( str.match(regexp) ); // 你,好 +``` + +### Example: currency + +Characters that denote a currency, such as `$`, `€`, `¥`, have unicode property `pattern:\p{Currency_Symbol}`, the short alias: `pattern:\p{Sc}`. + +Let's use it to look for prices in the format "currency, followed by a digit": + +```js run +let regexp = /\p{Sc}\d/gu; + +let str = `Prices: $2, €1, ¥9`; + +alert( str.match(regexp) ); // $2,€1,¥9 +``` + +Later, in the article we'll see how to look for numbers that contain many digits. + +## Summary + +Flag `pattern:u` enables the support of Unicode in regular expressions. + +That means two things: + +1. Characters of 4 bytes are handled correctly: as a single character, not two 2-byte characters. +2. Unicode properties can be used in the search: `\p{…}`. + +With Unicode properties we can look for words in given languages, special characters (quotes, currencies) and so on. diff --git a/12-regexp-anchors/1-start-end/solution.md b/04-regexp-anchors/1-start-end/solution.md similarity index 77% rename from 12-regexp-anchors/1-start-end/solution.md rename to 04-regexp-anchors/1-start-end/solution.md index 1a8cbe9a2..702f992d7 100644 --- a/12-regexp-anchors/1-start-end/solution.md +++ b/04-regexp-anchors/1-start-end/solution.md @@ -1,5 +1,4 @@ - -The empty string is the only match: it starts and immediately finishes. +An empty string is the only match: it starts and immediately finishes. The task once again demonstrates that anchors are not characters, but tests. diff --git a/12-regexp-anchors/1-start-end/task.md b/04-regexp-anchors/1-start-end/task.md similarity index 100% rename from 12-regexp-anchors/1-start-end/task.md rename to 04-regexp-anchors/1-start-end/task.md diff --git a/04-regexp-anchors/article.md b/04-regexp-anchors/article.md new file mode 100644 index 000000000..c34999ee5 --- /dev/null +++ b/04-regexp-anchors/article.md @@ -0,0 +1,52 @@ +# Anchors: string start ^ and end $ + +The caret `pattern:^` and dollar `pattern:$` characters have special meaning in a regexp. They are called "anchors". + +The caret `pattern:^` matches at the beginning of the text, and the dollar `pattern:$` -- at the end. + +For instance, let's test if the text starts with `Mary`: + +```js run +let str1 = "Mary had a little lamb"; +alert( /^Mary/.test(str1) ); // true +``` + +The pattern `pattern:^Mary` means: "string start and then Mary". + +Similar to this, we can test if the string ends with `snow` using `pattern:snow$`: + +```js run +let str1 = "it's fleece was white as snow"; +alert( /snow$/.test(str1) ); // true +``` + +In these particular cases we could use string methods `startsWith/endsWith` instead. Regular expressions should be used for more complex tests. + +## Testing for a full match + +Both anchors together `pattern:^...$` are often used to test whether or not a string fully matches the pattern. For instance, to check if the user input is in the right format. + +Let's check whether or not a string is a time in `12:34` format. That is: two digits, then a colon, and then another two digits. + +In regular expressions language that's `pattern:\d\d:\d\d`: + +```js run +let goodInput = "12:34"; +let badInput = "12:345"; + +let regexp = /^\d\d:\d\d$/; +alert( regexp.test(goodInput) ); // true +alert( regexp.test(badInput) ); // false +``` + +Here the match for `pattern:\d\d:\d\d` must start exactly after the beginning of the text `pattern:^`, and the end `pattern:$` must immediately follow. + +The whole string must be exactly in this format. If there's any deviation or an extra character, the result is `false`. + +Anchors behave differently if flag `pattern:m` is present. We'll see that in the next article. + +```smart header="Anchors have \"zero width\"" +Anchors `pattern:^` and `pattern:$` 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). +``` diff --git a/05-regexp-multiline-mode/article.md b/05-regexp-multiline-mode/article.md new file mode 100644 index 000000000..321218b37 --- /dev/null +++ b/05-regexp-multiline-mode/article.md @@ -0,0 +1,87 @@ +# Multiline mode of anchors ^ $, flag "m" + +The multiline mode is enabled by the flag `pattern:m`. + +It only affects the behavior of `pattern:^` and `pattern:$`. + +In the multiline mode they match not only at the beginning and the end of the string, but also at start/end of line. + +## Searching at line start ^ + +In the example below the text has multiple lines. The pattern `pattern:/^\d/gm` takes a digit from the beginning of each line: + +```js run +let str = `1st place: Winnie +2nd place: Piglet +3rd place: Eeyore`; + +*!* +alert( str.match(/^\d/gm) ); // 1, 2, 3 +*/!* +``` + +Without the flag `pattern:m` only the first digit is matched: + +```js run +let str = `1st place: Winnie +2nd place: Piglet +3rd place: Eeyore`; + +*!* +alert( str.match(/^\d/g) ); // 1 +*/!* +``` + +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 any line. + +```smart +"Start of a line" formally means "immediately after a line break": the test `pattern:^` in multiline mode matches at all positions preceeded by a newline character `\n`. + +And at the text start. +``` + +## Searching at line end $ + +The dollar sign `pattern:$` behaves similarly. + +The regular expression `pattern:\d$` finds the last digit in every line + +```js run +let str = `Winnie: 1 +Piglet: 2 +Eeyore: 3`; + +alert( str.match(/\d$/gm) ); // 1,2,3 +``` + +Without the flag `m`, the dollar `pattern:$` would only match the end of the whole text, so only the very last digit would be found. + +```smart +"End of a line" formally means "immediately before a line break": the test `pattern:^` in multiline mode matches at all positions succeeded by a newline character `\n`. + +And at the text end. +``` + +## Searching for \n instead of ^ $ + +To find a newline, we can use not only anchors `pattern:^` and `pattern:$`, but also the newline character `\n`. + +What's the difference? Let's see an example. + +Here we search for `pattern:\d\n` instead of `pattern:\d$`: + +```js run +let str = `Winnie: 1 +Piglet: 2 +Eeyore: 3`; + +alert( str.match(/\d\n/gm) ); // 1\n,2\n +``` + +As we can see, there are 2 matches instead of 3. + +That's because there's no newline after `subject:3` (there's text end though, so it matches `pattern:$`). + +Another difference: now every match includes a newline character `match:\n`. Unlike the anchors `pattern:^` `pattern:$`, that only test the condition (start/end of a line), `\n` is a character, so it becomes a part of the result. + +So, a `\n` in the pattern is used when we need newline characters in the result, while anchors are used to find something at the beginning/end of a line. diff --git a/06-regexp-boundary/1-find-time-hh-mm/solution.md b/06-regexp-boundary/1-find-time-hh-mm/solution.md new file mode 100644 index 000000000..d378d4c95 --- /dev/null +++ b/06-regexp-boundary/1-find-time-hh-mm/solution.md @@ -0,0 +1,6 @@ + +Ответ: `pattern:\b\d\d:\d\d\b`. + +```js run +alert( "Завтрак в 09:00 в комнате 123:456.".match( /\b\d\d:\d\d\b/ ) ); // 09:00 +``` diff --git a/06-regexp-boundary/1-find-time-hh-mm/task.md b/06-regexp-boundary/1-find-time-hh-mm/task.md new file mode 100644 index 000000000..16330a6dc --- /dev/null +++ b/06-regexp-boundary/1-find-time-hh-mm/task.md @@ -0,0 +1,9 @@ +# Найдите время + +Время имеет формат: `часы:минуты`. И часы, и минуты имеют две цифры, например, `09:00`. + +Введите регулярное выражение, чтобы найти время в строке: `subject:Завтрак в 09:00 в комнате 123:456.` + +P.S. В этой задаче пока нет необходимости проверять правильность времени, поэтому `25:99` также может быть верным результатом. + +P.P.S. Регулярное выражение не должно находить `123:456`. diff --git a/06-regexp-boundary/article.md b/06-regexp-boundary/article.md new file mode 100644 index 000000000..286a963ec --- /dev/null +++ b/06-regexp-boundary/article.md @@ -0,0 +1,53 @@ +# Word boundary: \b + +A word boundary `pattern:\b` is a test, just like `pattern:^` and `pattern:$`. + +When the regexp engine (program module that implements searching for regexps) comes across `pattern:\b`, it checks that the position in the string is a word boundary. + +There are three different positions that qualify as word boundaries: + +- At string start, if the first string character is a word character `pattern:\w`. +- Between two characters in the string, where one is a word character `pattern:\w` and the other is not. +- At string end, if the last string character is a word character `pattern:\w`. + +For instance, regexp `pattern:\bJava\b` will be found in `subject:Hello, Java!`, where `subject:Java` is a standalone word, but not in `subject:Hello, JavaScript!`. + +```js run +alert( "Hello, Java!".match(/\bJava\b/) ); // Java +alert( "Hello, JavaScript!".match(/\bJava\b/) ); // null +``` + +In the string `subject:Hello, Java!` following positions correspond to `pattern:\b`: + +![](hello-java-boundaries.svg) + +So, it matches the pattern `pattern:\bHello\b`, because: + +1. At the beginning of the string matches the first test `pattern:\b`. +2. Then matches the word `pattern:Hello`. +3. Then the test `pattern:\b` - matches again, as we're between `subject:o` and a space. + +Шаблон `pattern:\bJava\b` также совпадёт. Но не `pattern:\bHell\b` (потому что после `subject:l` нет границы слова), и не `pattern:Java!\b` (восклицательный знак не является "символом слова" `pattern:\w`, поэтому после него нет границы слова). + +```js run +alert( "Hello, Java!".match(/\bHello\b/) ); // Hello +alert( "Hello, Java!".match(/\bJava\b/) ); // Java +alert( "Hello, Java!".match(/\bHell\b/) ); // null (нет совпадения) +alert( "Hello, Java!".match(/\bJava!\b/) ); // null (нет совпадения) +``` + +Так как `pattern:\b` является проверкой, то не добавляет символ после границы к результату. + +Мы можем использовать `pattern:\b` не только со словами, но и с цифрами. + +Например, регулярное выражение `pattern:\b\d\d\b` ищет отдельно стоящие двузначные числа. Другими словами, оно требует, чтобы до и после `pattern:\d\d` был символ, отличный от `pattern:\w` (или начало/конец строки) + +```js run +alert( "1 23 456 78".match(/\b\d\d\b/g) ); // 23,78 +``` + +```warn header="Граница слова `pattern:\b` не работает для алфавитов, не основанных на латинице" +Проверка границы слова `pattern:\b` проверяет границу, должно быть `pattern:\w` с одной стороны и "не `pattern:\w`" - с другой. + +Но `pattern:\w` означает латинскую букву (или цифру или знак подчёркивания), поэтому проверка не будет работать для других символов (например, кириллицы или иероглифов). +``` diff --git a/03-regexp-character-classes/hello-java-boundaries.svg b/06-regexp-boundary/hello-java-boundaries.svg similarity index 100% rename from 03-regexp-character-classes/hello-java-boundaries.svg rename to 06-regexp-boundary/hello-java-boundaries.svg diff --git a/04-regexp-escaping/article.md b/07-regexp-escaping/article.md similarity index 96% rename from 04-regexp-escaping/article.md rename to 07-regexp-escaping/article.md index 909cd4856..cd118010a 100644 --- a/04-regexp-escaping/article.md +++ b/07-regexp-escaping/article.md @@ -75,7 +75,7 @@ The quotes "consume" backslashes and interpret them, for instance: - `\n` -- becomes a newline character, - `\u1234` -- becomes the Unicode character with such code, -- ...And when there's no special meaning: like `\d` or `\z`, then the backslash is simply removed. +- ...And when there's no special meaning: like `pattern:\d` or `\z`, then the backslash is simply removed. So the call to `new RegExp` gets a string without backslashes. That's why the search doesn't work! diff --git a/05-regexp-character-sets-and-ranges/1-find-range-1/solution.md b/08-regexp-character-sets-and-ranges/1-find-range-1/solution.md similarity index 100% rename from 05-regexp-character-sets-and-ranges/1-find-range-1/solution.md rename to 08-regexp-character-sets-and-ranges/1-find-range-1/solution.md diff --git a/05-regexp-character-sets-and-ranges/1-find-range-1/task.md b/08-regexp-character-sets-and-ranges/1-find-range-1/task.md similarity index 100% rename from 05-regexp-character-sets-and-ranges/1-find-range-1/task.md rename to 08-regexp-character-sets-and-ranges/1-find-range-1/task.md diff --git a/05-regexp-character-sets-and-ranges/2-find-time-2-formats/solution.md b/08-regexp-character-sets-and-ranges/2-find-time-2-formats/solution.md similarity index 100% rename from 05-regexp-character-sets-and-ranges/2-find-time-2-formats/solution.md rename to 08-regexp-character-sets-and-ranges/2-find-time-2-formats/solution.md diff --git a/05-regexp-character-sets-and-ranges/2-find-time-2-formats/task.md b/08-regexp-character-sets-and-ranges/2-find-time-2-formats/task.md similarity index 100% rename from 05-regexp-character-sets-and-ranges/2-find-time-2-formats/task.md rename to 08-regexp-character-sets-and-ranges/2-find-time-2-formats/task.md diff --git a/05-regexp-character-sets-and-ranges/article.md b/08-regexp-character-sets-and-ranges/article.md similarity index 97% rename from 05-regexp-character-sets-and-ranges/article.md rename to 08-regexp-character-sets-and-ranges/article.md index 7204f2b1c..3a94125c3 100644 --- a/05-regexp-character-sets-and-ranges/article.md +++ b/08-regexp-character-sets-and-ranges/article.md @@ -44,7 +44,7 @@ alert( "Exception 0xAF".match(/x[0-9A-F][0-9A-F]/g) ); // xAF Please note that in the word `subject:Exception` there's a substring `subject:xce`. It didn't match the pattern, because the letters are lowercase, while in the set `pattern:[0-9A-F]` they are uppercase. -If we want to find it too, then we can add a range `a-f`: `pattern:[0-9A-Fa-f]`. The `i` flag would allow lowercase too. +If we want to find it too, then we can add a range `a-f`: `pattern:[0-9A-Fa-f]`. The `pattern:i` flag would allow lowercase too. **Character classes are shorthands for certain character sets.** @@ -58,7 +58,7 @@ We can use character classes inside `[…]` as well. For instance, we want to match all wordly characters or a dash, for words like "twenty-third". We can't do it with `pattern:\w+`, because `pattern:\w` class does not include a dash. But we can use `pattern:[\w-]`. -We also can use several classes, for example `pattern:[\s\S]` matches spaces or non-spaces -- any character. That's wider than a dot `"."`, because the dot matches any character except a newline (unless `s` flag is set). +We also can use several classes, for example `pattern:[\s\S]` matches spaces or non-spaces -- any character. That's wider than a dot `"."`, because the dot matches any character except a newline (unless `pattern:s` flag is set). ## Excluding ranges @@ -69,7 +69,7 @@ They are denoted by a caret character `^` at the start and match any character * For instance: - `pattern:[^aeyo]` -- any character except `'a'`, `'e'`, `'y'` or `'o'`. -- `pattern:[^0-9]` -- any character except a digit, the same as `\D`. +- `pattern:[^0-9]` -- any character except a digit, the same as `pattern:\D`. - `pattern:[^\s]` -- any non-space character, same as `\S`. The example below looks for any characters except letters, digits and spaces: diff --git a/07-regexp-quantifiers/1-find-text-manydots/solution.md b/09-regexp-quantifiers/1-find-text-manydots/solution.md similarity index 100% rename from 07-regexp-quantifiers/1-find-text-manydots/solution.md rename to 09-regexp-quantifiers/1-find-text-manydots/solution.md diff --git a/07-regexp-quantifiers/1-find-text-manydots/task.md b/09-regexp-quantifiers/1-find-text-manydots/task.md similarity index 100% rename from 07-regexp-quantifiers/1-find-text-manydots/task.md rename to 09-regexp-quantifiers/1-find-text-manydots/task.md diff --git a/07-regexp-quantifiers/2-find-html-colors-6hex/solution.md b/09-regexp-quantifiers/2-find-html-colors-6hex/solution.md similarity index 91% rename from 07-regexp-quantifiers/2-find-html-colors-6hex/solution.md rename to 09-regexp-quantifiers/2-find-html-colors-6hex/solution.md index 4e85285b3..d4d297a1d 100644 --- a/07-regexp-quantifiers/2-find-html-colors-6hex/solution.md +++ b/09-regexp-quantifiers/2-find-html-colors-6hex/solution.md @@ -1,6 +1,6 @@ We need to look for `#` followed by 6 hexadecimal characters. -A hexadecimal character can be described as `pattern:[0-9a-fA-F]`. Or if we use the `i` flag, then just `pattern:[0-9a-f]`. +A hexadecimal character can be described as `pattern:[0-9a-fA-F]`. Or if we use the `pattern:i` flag, then just `pattern:[0-9a-f]`. Then we can look for 6 of them using the quantifier `pattern:{6}`. diff --git a/07-regexp-quantifiers/2-find-html-colors-6hex/task.md b/09-regexp-quantifiers/2-find-html-colors-6hex/task.md similarity index 100% rename from 07-regexp-quantifiers/2-find-html-colors-6hex/task.md rename to 09-regexp-quantifiers/2-find-html-colors-6hex/task.md diff --git a/07-regexp-quantifiers/article.md b/09-regexp-quantifiers/article.md similarity index 97% rename from 07-regexp-quantifiers/article.md rename to 09-regexp-quantifiers/article.md index 7f382dcc2..9b70d7227 100644 --- a/07-regexp-quantifiers/article.md +++ b/09-regexp-quantifiers/article.md @@ -2,7 +2,7 @@ Let's say we have a string like `+7(903)-123-45-67` and want to find all numbers in it. But unlike before, we are interested not in single digits, but full numbers: `7, 903, 123, 45, 67`. -A number is a sequence of 1 or more digits `\d`. To mark how many we need, we need to append a *quantifier*. +A number is a sequence of 1 or more digits `pattern:\d`. To mark how many we need, we need to append a *quantifier*. ## Quantity {n} diff --git a/08-regexp-greedy-and-lazy/1-lazy-greedy/solution.md b/10-regexp-greedy-and-lazy/1-lazy-greedy/solution.md similarity index 100% rename from 08-regexp-greedy-and-lazy/1-lazy-greedy/solution.md rename to 10-regexp-greedy-and-lazy/1-lazy-greedy/solution.md diff --git a/08-regexp-greedy-and-lazy/1-lazy-greedy/task.md b/10-regexp-greedy-and-lazy/1-lazy-greedy/task.md similarity index 100% rename from 08-regexp-greedy-and-lazy/1-lazy-greedy/task.md rename to 10-regexp-greedy-and-lazy/1-lazy-greedy/task.md diff --git a/08-regexp-greedy-and-lazy/3-find-html-comments/solution.md b/10-regexp-greedy-and-lazy/3-find-html-comments/solution.md similarity index 100% rename from 08-regexp-greedy-and-lazy/3-find-html-comments/solution.md rename to 10-regexp-greedy-and-lazy/3-find-html-comments/solution.md diff --git a/08-regexp-greedy-and-lazy/3-find-html-comments/task.md b/10-regexp-greedy-and-lazy/3-find-html-comments/task.md similarity index 100% rename from 08-regexp-greedy-and-lazy/3-find-html-comments/task.md rename to 10-regexp-greedy-and-lazy/3-find-html-comments/task.md diff --git a/08-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/solution.md b/10-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/solution.md similarity index 100% rename from 08-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/solution.md rename to 10-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/solution.md diff --git a/08-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/task.md b/10-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/task.md similarity index 100% rename from 08-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/task.md rename to 10-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/task.md diff --git a/08-regexp-greedy-and-lazy/article.md b/10-regexp-greedy-and-lazy/article.md similarity index 100% rename from 08-regexp-greedy-and-lazy/article.md rename to 10-regexp-greedy-and-lazy/article.md diff --git a/08-regexp-greedy-and-lazy/witch_greedy1.svg b/10-regexp-greedy-and-lazy/witch_greedy1.svg similarity index 100% rename from 08-regexp-greedy-and-lazy/witch_greedy1.svg rename to 10-regexp-greedy-and-lazy/witch_greedy1.svg diff --git a/08-regexp-greedy-and-lazy/witch_greedy2.svg b/10-regexp-greedy-and-lazy/witch_greedy2.svg similarity index 100% rename from 08-regexp-greedy-and-lazy/witch_greedy2.svg rename to 10-regexp-greedy-and-lazy/witch_greedy2.svg diff --git a/08-regexp-greedy-and-lazy/witch_greedy3.svg b/10-regexp-greedy-and-lazy/witch_greedy3.svg similarity index 100% rename from 08-regexp-greedy-and-lazy/witch_greedy3.svg rename to 10-regexp-greedy-and-lazy/witch_greedy3.svg diff --git a/08-regexp-greedy-and-lazy/witch_greedy4.svg b/10-regexp-greedy-and-lazy/witch_greedy4.svg similarity index 100% rename from 08-regexp-greedy-and-lazy/witch_greedy4.svg rename to 10-regexp-greedy-and-lazy/witch_greedy4.svg diff --git a/08-regexp-greedy-and-lazy/witch_greedy5.svg b/10-regexp-greedy-and-lazy/witch_greedy5.svg similarity index 100% rename from 08-regexp-greedy-and-lazy/witch_greedy5.svg rename to 10-regexp-greedy-and-lazy/witch_greedy5.svg diff --git a/08-regexp-greedy-and-lazy/witch_greedy6.svg b/10-regexp-greedy-and-lazy/witch_greedy6.svg similarity index 100% rename from 08-regexp-greedy-and-lazy/witch_greedy6.svg rename to 10-regexp-greedy-and-lazy/witch_greedy6.svg diff --git a/08-regexp-greedy-and-lazy/witch_lazy3.svg b/10-regexp-greedy-and-lazy/witch_lazy3.svg similarity index 100% rename from 08-regexp-greedy-and-lazy/witch_lazy3.svg rename to 10-regexp-greedy-and-lazy/witch_lazy3.svg diff --git a/08-regexp-greedy-and-lazy/witch_lazy4.svg b/10-regexp-greedy-and-lazy/witch_lazy4.svg similarity index 100% rename from 08-regexp-greedy-and-lazy/witch_lazy4.svg rename to 10-regexp-greedy-and-lazy/witch_lazy4.svg diff --git a/08-regexp-greedy-and-lazy/witch_lazy5.svg b/10-regexp-greedy-and-lazy/witch_lazy5.svg similarity index 100% rename from 08-regexp-greedy-and-lazy/witch_lazy5.svg rename to 10-regexp-greedy-and-lazy/witch_lazy5.svg diff --git a/08-regexp-greedy-and-lazy/witch_lazy6.svg b/10-regexp-greedy-and-lazy/witch_lazy6.svg similarity index 100% rename from 08-regexp-greedy-and-lazy/witch_lazy6.svg rename to 10-regexp-greedy-and-lazy/witch_lazy6.svg diff --git a/09-regexp-groups/1-find-webcolor-3-or-6/solution.md b/11-regexp-groups/1-find-webcolor-3-or-6/solution.md similarity index 100% rename from 09-regexp-groups/1-find-webcolor-3-or-6/solution.md rename to 11-regexp-groups/1-find-webcolor-3-or-6/solution.md diff --git a/09-regexp-groups/1-find-webcolor-3-or-6/task.md b/11-regexp-groups/1-find-webcolor-3-or-6/task.md similarity index 100% rename from 09-regexp-groups/1-find-webcolor-3-or-6/task.md rename to 11-regexp-groups/1-find-webcolor-3-or-6/task.md diff --git a/09-regexp-groups/2-find-decimal-numbers/solution.md b/11-regexp-groups/2-find-decimal-numbers/solution.md similarity index 100% rename from 09-regexp-groups/2-find-decimal-numbers/solution.md rename to 11-regexp-groups/2-find-decimal-numbers/solution.md diff --git a/09-regexp-groups/2-find-decimal-numbers/task.md b/11-regexp-groups/2-find-decimal-numbers/task.md similarity index 100% rename from 09-regexp-groups/2-find-decimal-numbers/task.md rename to 11-regexp-groups/2-find-decimal-numbers/task.md diff --git a/09-regexp-groups/5-parse-expression/solution.md b/11-regexp-groups/5-parse-expression/solution.md similarity index 100% rename from 09-regexp-groups/5-parse-expression/solution.md rename to 11-regexp-groups/5-parse-expression/solution.md diff --git a/09-regexp-groups/5-parse-expression/task.md b/11-regexp-groups/5-parse-expression/task.md similarity index 100% rename from 09-regexp-groups/5-parse-expression/task.md rename to 11-regexp-groups/5-parse-expression/task.md diff --git a/09-regexp-groups/article.md b/11-regexp-groups/article.md similarity index 100% rename from 09-regexp-groups/article.md rename to 11-regexp-groups/article.md diff --git a/09-regexp-groups/regexp-nested-groups.svg b/11-regexp-groups/regexp-nested-groups.svg similarity index 100% rename from 09-regexp-groups/regexp-nested-groups.svg rename to 11-regexp-groups/regexp-nested-groups.svg diff --git a/12-regexp-anchors/2-test-mac/solution.md b/12-regexp-anchors/2-test-mac/solution.md deleted file mode 100644 index 422bc65e4..000000000 --- a/12-regexp-anchors/2-test-mac/solution.md +++ /dev/null @@ -1,21 +0,0 @@ -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) -``` diff --git a/12-regexp-anchors/2-test-mac/task.md b/12-regexp-anchors/2-test-mac/task.md deleted file mode 100644 index e72655984..000000000 --- a/12-regexp-anchors/2-test-mac/task.md +++ /dev/null @@ -1,20 +0,0 @@ -# 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) -``` diff --git a/12-regexp-anchors/article.md b/12-regexp-anchors/article.md deleted file mode 100644 index 0c2dd578a..000000000 --- a/12-regexp-anchors/article.md +++ /dev/null @@ -1,55 +0,0 @@ -# String start ^ and finish $ - -The caret `pattern:'^'` and dollar `pattern:'$'` characters have special meaning in a regexp. They are called "anchors". - -The caret `pattern:^` matches at the beginning 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}`. - -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. diff --git a/10-regexp-backreferences/article.md b/12-regexp-backreferences/article.md similarity index 100% rename from 10-regexp-backreferences/article.md rename to 12-regexp-backreferences/article.md diff --git a/11-regexp-alternation/01-find-programming-language/solution.md b/13-regexp-alternation/01-find-programming-language/solution.md similarity index 100% rename from 11-regexp-alternation/01-find-programming-language/solution.md rename to 13-regexp-alternation/01-find-programming-language/solution.md diff --git a/11-regexp-alternation/01-find-programming-language/task.md b/13-regexp-alternation/01-find-programming-language/task.md similarity index 100% rename from 11-regexp-alternation/01-find-programming-language/task.md rename to 13-regexp-alternation/01-find-programming-language/task.md diff --git a/11-regexp-alternation/02-find-matching-bbtags/solution.md b/13-regexp-alternation/02-find-matching-bbtags/solution.md similarity index 79% rename from 11-regexp-alternation/02-find-matching-bbtags/solution.md rename to 13-regexp-alternation/02-find-matching-bbtags/solution.md index e448a4b12..dddaf9628 100644 --- a/11-regexp-alternation/02-find-matching-bbtags/solution.md +++ b/13-regexp-alternation/02-find-matching-bbtags/solution.md @@ -1,7 +1,7 @@ Opening tag is `pattern:\[(b|url|quote)\]`. -Then to find everything till the closing tag -- let's use the pattern `pattern:.*?` with flag `s` to match any character including the newline and then add a backreference to the closing tag. +Then to find everything till the closing tag -- let's use the pattern `pattern:.*?` with flag `pattern:s` to match any character including the newline and then add a backreference to the closing tag. The full pattern: `pattern:\[(b|url|quote)\].*?\[/\1\]`. diff --git a/11-regexp-alternation/02-find-matching-bbtags/task.md b/13-regexp-alternation/02-find-matching-bbtags/task.md similarity index 100% rename from 11-regexp-alternation/02-find-matching-bbtags/task.md rename to 13-regexp-alternation/02-find-matching-bbtags/task.md diff --git a/11-regexp-alternation/03-match-quoted-string/solution.md b/13-regexp-alternation/03-match-quoted-string/solution.md similarity index 100% rename from 11-regexp-alternation/03-match-quoted-string/solution.md rename to 13-regexp-alternation/03-match-quoted-string/solution.md diff --git a/11-regexp-alternation/03-match-quoted-string/task.md b/13-regexp-alternation/03-match-quoted-string/task.md similarity index 100% rename from 11-regexp-alternation/03-match-quoted-string/task.md rename to 13-regexp-alternation/03-match-quoted-string/task.md diff --git a/11-regexp-alternation/04-match-exact-tag/solution.md b/13-regexp-alternation/04-match-exact-tag/solution.md similarity index 100% rename from 11-regexp-alternation/04-match-exact-tag/solution.md rename to 13-regexp-alternation/04-match-exact-tag/solution.md diff --git a/11-regexp-alternation/04-match-exact-tag/task.md b/13-regexp-alternation/04-match-exact-tag/task.md similarity index 100% rename from 11-regexp-alternation/04-match-exact-tag/task.md rename to 13-regexp-alternation/04-match-exact-tag/task.md diff --git a/11-regexp-alternation/article.md b/13-regexp-alternation/article.md similarity index 100% rename from 11-regexp-alternation/article.md rename to 13-regexp-alternation/article.md diff --git a/13-regexp-multiline-mode/article.md b/13-regexp-multiline-mode/article.md deleted file mode 100644 index 955d96013..000000000 --- a/13-regexp-multiline-mode/article.md +++ /dev/null @@ -1,75 +0,0 @@ -# Multiline mode, flag "m" - -The multiline mode is enabled by the flag `pattern:/.../m`. - -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 ^ - -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 -let str = `1st place: Winnie -2nd place: Piglet -33rd place: Eeyore`; - -*!* -alert( str.match(/^\d+/gm) ); // 1, 2, 33 -*/!* -``` - -The regexp engine moves along the text and looks for a line start `pattern:^`, when finds -- continues to match the rest of the pattern `pattern:\d+`. - -Without the flag `pattern:/.../m` only the first number is matched: - -```js run -let str = `1st place: Winnie -2nd place: Piglet -33rd place: Eeyore`; - -*!* -alert( str.match(/^\d+/g) ); // 1 -*/!* -``` - -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 any line. - -## Line end $ - -The dollar sign `pattern:$` behaves similarly. - -The regular expression `pattern:\w+$` finds the last word in every line - -```js run -let str = `1st place: Winnie -2nd place: Piglet -33rd place: Eeyore`; - -alert( str.match(/\w+$/gim) ); // Winnie,Piglet,Eeyore -``` - -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. - -## Anchors ^$ versus \n - -To find a newline, we can use not only `pattern:^` and `pattern:$`, but also the newline character `\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 -let str = `1st place: Winnie -2nd place: Piglet -33rd place: Eeyore`; - -alert( str.match(/\w+\n/gim) ); // Winnie\n,Piglet\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. diff --git a/14-regexp-lookahead-lookbehind/article.md b/14-regexp-lookahead-lookbehind/article.md index e877cae47..8e36fb0bf 100644 --- a/14-regexp-lookahead-lookbehind/article.md +++ b/14-regexp-lookahead-lookbehind/article.md @@ -101,9 +101,9 @@ Lookaround types: | Pattern | type | matches | |--------------------|------------------|---------| -| `pattern:x(?=y)` | Positive lookahead | `x` if followed by `y` | -| `pattern:x(?!y)` | Negative lookahead | `x` if not followed by `y` | -| `pattern:(?<=y)x` | Positive lookbehind | `x` if after `y` | -| `pattern:(?. - -Let's briefly review them here. In short, normally characters are encoded with 2 bytes. That gives us 65536 characters maximum. But there are more characters in the world. - -So certain rare characters are encoded with 4 bytes, like `𝒳` (mathematical X) or `😄` (a smile). - -Here are the unicode values to compare: - -| Character | Unicode | Bytes | -|------------|---------|--------| -| `a` | 0x0061 | 2 | -| `≈` | 0x2248 | 2 | -|`𝒳`| 0x1d4b3 | 4 | -|`𝒴`| 0x1d4b4 | 4 | -|`😄`| 0x1f604 | 4 | - -So characters like `a` and `≈` occupy 2 bytes, and those rare ones take 4. - -The unicode is made in such a way that the 4-byte characters only have a meaning as a whole. - -In the past JavaScript did not know about that, and many string methods still have problems. For instance, `length` thinks that here are two characters: - -```js run -alert('😄'.length); // 2 -alert('𝒳'.length); // 2 -``` - -...But we can see that there's only one, right? The point is that `length` treats 4 bytes as two 2-byte characters. That's incorrect, because they must be considered only together (so-called "surrogate pair"). - -Normally, regular expressions also treat "long characters" as two 2-byte ones. - -That leads to odd results, for instance let's try to find `pattern:[𝒳𝒴]` in the string `subject:𝒳`: - -```js run -alert( '𝒳'.match(/[𝒳𝒴]/) ); // odd result (wrong match actually, "half-character") -``` - -The result is wrong, because by default the regexp engine does not understand surrogate pairs. - -So, it thinks that `[𝒳𝒴]` are not two, but four characters: -1. the left half of `𝒳` `(1)`, -2. the right half of `𝒳` `(2)`, -3. the left half of `𝒴` `(3)`, -4. the right half of `𝒴` `(4)`. - -We can list them like this: - -```js run -for(let i=0; i<'𝒳𝒴'.length; i++) { - alert('𝒳𝒴'.charCodeAt(i)); // 55349, 56499, 55349, 56500 -}; -``` - -So it finds only the "left half" of `𝒳`. - -In other words, the search works like `'12'.match(/[1234]/)`: only `1` is returned. - -## The "u" flag - -The `/.../u` flag fixes that. - -It enables surrogate pairs in the regexp engine, so the result is correct: - -```js run -alert( '𝒳'.match(/[𝒳𝒴]/u) ); // 𝒳 -``` - -Let's see one more example. - -If we forget the `u` flag and accidentally use surrogate pairs, then we can get an error: - -```js run -'𝒳'.match(/[𝒳-𝒴]/); // SyntaxError: invalid range in character class -``` - -Normally, regexps understand `[a-z]` as a "range of characters with codes between codes of `a` and `z`. - -But without `u` flag, surrogate pairs are assumed to be a "pair of independent characters", so `[𝒳-𝒴]` is like `[<55349><56499>-<55349><56500>]` (replaced each surrogate pair with code points). Now we can clearly see that the range `56499-55349` is unacceptable, as the left range border must be less than the right one. - -Using the `u` flag makes it work right: - -```js run -alert( '𝒴'.match(/[𝒳-𝒵]/u) ); // 𝒴 -``` diff --git a/21-regexp-unicode-properties/article.md b/21-regexp-unicode-properties/article.md deleted file mode 100644 index 2bb031d7a..000000000 --- a/21-regexp-unicode-properties/article.md +++ /dev/null @@ -1,86 +0,0 @@ - -# Unicode character properties \p - -[Unicode](https://en.wikipedia.org/wiki/Unicode), the encoding format used by JavaScript strings, has a lot of properties for different characters (or, technically, code points). They describe which "categories" character belongs to, and a variety of technical details. - -In regular expressions these can be set by `\p{…}`. And there must be flag `'u'`. - -For instance, `\p{Letter}` denotes a letter in any of language. We can also use `\p{L}`, as `L` is an alias of `Letter`, there are shorter aliases for almost every property. - -Here's the main tree of properties: - -- Letter `L`: - - lowercase `Ll`, modifier `Lm`, titlecase `Lt`, uppercase `Lu`, other `Lo` -- Number `N`: - - decimal digit `Nd`, letter number `Nl`, other `No` -- Punctuation `P`: - - connector `Pc`, dash `Pd`, initial quote `Pi`, final quote `Pf`, open `Ps`, close `Pe`, other `Po` -- Mark `M` (accents etc): - - spacing combining `Mc`, enclosing `Me`, non-spacing `Mn` -- Symbol `S`: - - currency `Sc`, modifier `Sk`, math `Sm`, other `So` -- Separator `Z`: - - line `Zl`, paragraph `Zp`, space `Zs` -- Other `C`: - - control `Cc`, format `Cf`, not assigned `Cn`, private use `Co`, surrogate `Cs` - -```smart header="More information" -Interested to see which characters belong to a property? There's a tool at for that. - -You could also explore properties at [Character Property Index](http://unicode.org/cldr/utility/properties.jsp). - -For the full Unicode Character Database in text format (along with all properties), see . -``` - -There are also other derived categories, like: -- `Alphabetic` (`Alpha`), includes Letters `L`, plus letter numbers `Nl` (e.g. roman numbers Ⅻ), plus some other symbols `Other_Alphabetic` (`OAltpa`). -- `Hex_Digit` includes hexadecimal digits: `0-9`, `a-f`. -- ...Unicode is a big beast, it includes a lot of properties. - -For instance, let's look for a 6-digit hex number: - -```js run -let reg = /\p{Hex_Digit}{6}/u; // flag 'u' is required - -alert("color: #123ABC".match(reg)); // 123ABC -``` - -There are also properties with a value. For instance, Unicode "Script" (a writing system) can be Cyrillic, Greek, Arabic, Han (Chinese) etc, the [list is long]("https://en.wikipedia.org/wiki/Script_(Unicode)"). - -To search for characters in certain scripts ("alphabets"), we should supply `Script=`, e.g. to search for cyrillic letters: `\p{sc=Cyrillic}`, for Chinese glyphs: `\p{sc=Han}`, etc: - -```js run -let regexp = /\p{sc=Han}+/gu; // get chinese words - -let str = `Hello Привет 你好 123_456`; - -alert( str.match(regexp) ); // 你好 -``` - -## Building multi-language \w - -The pattern `pattern:\w` means "wordly characters", but doesn't work for languages that use non-Latin alphabets, such as Cyrillic and others. It's just a shorthand for `[a-zA-Z0-9_]`, so `pattern:\w+` won't find any Chinese words etc. - -Let's make a "universal" regexp, that looks for wordly characters in any language. That's easy to do using Unicode properties: - -```js -/[\p{Alphabetic}\p{Mark}\p{Decimal_Number}\p{Connector_Punctuation}\p{Join_Control}]/u -``` - -Let's decipher. Just as `pattern:\w` is the same as `pattern:[a-zA-Z0-9_]`, we're making a set of our own, that includes: - -- `Alphabetic` for letters, -- `Mark` for accents, as in Unicode accents may be represented by separate code points, -- `Decimal_Number` for numbers, -- `Connector_Punctuation` for the `'_'` character and alike, -- `Join_Control` -– two special code points with hex codes `200c` and `200d`, used in ligatures e.g. in arabic. - -Or, if we replace long names with aliases (a list of aliases [here](https://www.unicode.org/Public/UCD/latest/ucd/PropertyValueAliases.txt)): - -```js run -let regexp = /([\p{Alpha}\p{M}\p{Nd}\p{Pc}\p{Join_C}]+)/gu; - -let str = `Hello Привет 你好 123_456`; - -alert( str.match(regexp) ); // Hello,Привет,你好,123_456 -``` diff --git a/index.md b/index.md index 7499c584d..ac25aaa67 100644 --- a/index.md +++ b/index.md @@ -1,7 +1,3 @@ # Regular expressions Regular expressions is a powerful way of doing search and replace in strings. - -In JavaScript regular expressions are implemented using objects of a built-in `RegExp` class and integrated with strings. - -Please note that regular expressions vary between programming languages. In this tutorial we concentrate on JavaScript. Of course there's a lot in common, but they are a somewhat different in Perl, Ruby, PHP etc. From 4f03133977d74fda19dcc8cef2396b32e637ad42 Mon Sep 17 00:00:00 2001 From: Ilya Kantor Date: Wed, 4 Sep 2019 19:35:17 +0300 Subject: [PATCH 059/101] WIP --- .../1-find-time-hh-mm/solution.md | 4 +-- 06-regexp-boundary/1-find-time-hh-mm/task.md | 10 +++---- 06-regexp-boundary/article.md | 21 +++++++-------- 07-regexp-escaping/article.md | 26 +++++++++---------- 4 files changed, 30 insertions(+), 31 deletions(-) diff --git a/06-regexp-boundary/1-find-time-hh-mm/solution.md b/06-regexp-boundary/1-find-time-hh-mm/solution.md index d378d4c95..829eda13e 100644 --- a/06-regexp-boundary/1-find-time-hh-mm/solution.md +++ b/06-regexp-boundary/1-find-time-hh-mm/solution.md @@ -1,6 +1,6 @@ -Ответ: `pattern:\b\d\d:\d\d\b`. +The answer: `pattern:\b\d\d:\d\d\b`. ```js run -alert( "Завтрак в 09:00 в комнате 123:456.".match( /\b\d\d:\d\d\b/ ) ); // 09:00 +alert( "Breakfast at 09:00 in the room 123:456.".match( /\b\d\d:\d\d\b/ ) ); // 09:00 ``` diff --git a/06-regexp-boundary/1-find-time-hh-mm/task.md b/06-regexp-boundary/1-find-time-hh-mm/task.md index 16330a6dc..95ab5777d 100644 --- a/06-regexp-boundary/1-find-time-hh-mm/task.md +++ b/06-regexp-boundary/1-find-time-hh-mm/task.md @@ -1,9 +1,9 @@ -# Найдите время +# Find the time -Время имеет формат: `часы:минуты`. И часы, и минуты имеют две цифры, например, `09:00`. +The time has a format: `hours:minutes`. Both hours and minutes has two digits, like `09:00`. -Введите регулярное выражение, чтобы найти время в строке: `subject:Завтрак в 09:00 в комнате 123:456.` +Make a regexp to find time in the string: `subject:Breakfast at 09:00 in the room 123:456.` -P.S. В этой задаче пока нет необходимости проверять правильность времени, поэтому `25:99` также может быть верным результатом. +P.S. In this task there's no need to check time correctness yet, so `25:99` can also be a valid result. -P.P.S. Регулярное выражение не должно находить `123:456`. +P.P.S. The regexp shouldn't match `123:456`. diff --git a/06-regexp-boundary/article.md b/06-regexp-boundary/article.md index 286a963ec..e4df252a4 100644 --- a/06-regexp-boundary/article.md +++ b/06-regexp-boundary/article.md @@ -25,29 +25,28 @@ So, it matches the pattern `pattern:\bHello\b`, because: 1. At the beginning of the string matches the first test `pattern:\b`. 2. Then matches the word `pattern:Hello`. -3. Then the test `pattern:\b` - matches again, as we're between `subject:o` and a space. +3. Then the test `pattern:\b` matches again, as we're between `subject:o` and a space. -Шаблон `pattern:\bJava\b` также совпадёт. Но не `pattern:\bHell\b` (потому что после `subject:l` нет границы слова), и не `pattern:Java!\b` (восклицательный знак не является "символом слова" `pattern:\w`, поэтому после него нет границы слова). +The pattern `pattern:\bJava\b` would also match. But not `pattern:\bHell\b` (because there's no word boundary after `l`) and not `Java!\b` (because the exclamation sign is not a wordly character `pattern:\w`, so there's no word boundary after it). ```js run alert( "Hello, Java!".match(/\bHello\b/) ); // Hello alert( "Hello, Java!".match(/\bJava\b/) ); // Java -alert( "Hello, Java!".match(/\bHell\b/) ); // null (нет совпадения) -alert( "Hello, Java!".match(/\bJava!\b/) ); // null (нет совпадения) +alert( "Hello, Java!".match(/\bHell\b/) ); // null (no match) +alert( "Hello, Java!".match(/\bJava!\b/) ); // null (no match) ``` -Так как `pattern:\b` является проверкой, то не добавляет символ после границы к результату. +We can use `pattern:\b` not only with words, but with digits as well. -Мы можем использовать `pattern:\b` не только со словами, но и с цифрами. - -Например, регулярное выражение `pattern:\b\d\d\b` ищет отдельно стоящие двузначные числа. Другими словами, оно требует, чтобы до и после `pattern:\d\d` был символ, отличный от `pattern:\w` (или начало/конец строки) +For example, the pattern `pattern:\b\d\d\b` looks for standalone 2-digit numbers. In other words, it looks for 2-digit numbers that are surrounded by characters different from `pattern:\w`, such as spaces or punctuation (or text start/end). ```js run alert( "1 23 456 78".match(/\b\d\d\b/g) ); // 23,78 +alert( "12,34,56".match(/\b\d\d\b/g) ); // 12,34,56 ``` -```warn header="Граница слова `pattern:\b` не работает для алфавитов, не основанных на латинице" -Проверка границы слова `pattern:\b` проверяет границу, должно быть `pattern:\w` с одной стороны и "не `pattern:\w`" - с другой. +```warn header="Word boundary `pattern:\b` doesn't work for non-latin alphabets" +The word boundary test `pattern:\b` checks that there should be `pattern:\w` on the one side from the position and "not `pattern:\w`" - on the other side. -Но `pattern:\w` означает латинскую букву (или цифру или знак подчёркивания), поэтому проверка не будет работать для других символов (например, кириллицы или иероглифов). +But `pattern:\w` means a latin letter `a-z` (or a digit or an underscore), so the test doesn't work for other characters, e.g. cyrillic letters or hieroglyphs. ``` diff --git a/07-regexp-escaping/article.md b/07-regexp-escaping/article.md index cd118010a..5169bd552 100644 --- a/07-regexp-escaping/article.md +++ b/07-regexp-escaping/article.md @@ -1,7 +1,7 @@ # Escaping, special characters -As we've seen, a backslash `"\"` is used to denote character classes. So it's a special character in regexps (just like in a regular string). +As we've seen, a backslash `pattern:\` is used to denote character classes, e.g. `pattern:\d`. So it's a special character in regexps (just like in regular strings). There are other special characters as well, that have special meaning in a regexp. They are used to do more powerful searches. Here's a full list of them: `pattern:[ \ ^ $ . | ? * + ( )`. @@ -9,7 +9,7 @@ Don't try to remember the list -- soon we'll deal with each of them separately a ## Escaping -Let's say we want to find a dot literally. Not "any character", but just a dot. +Let's say we want to find literally a dot. Not "any character", but just a dot. To use a special character as a regular one, prepend it with a backslash: `pattern:\.`. @@ -43,11 +43,11 @@ Here's what a search for a slash `'/'` looks like: alert( "/".match(/\//) ); // '/' ``` -On the other hand, if we're not using `/.../`, but create a regexp using `new RegExp`, then we don't need to escape it: +On the other hand, if we're not using `pattern:/.../`, but create a regexp using `new RegExp`, then we don't need to escape it: ```js run -alert( "/".match(new RegExp("/")) ); // '/' -``` +alert( "/".match(new RegExp("/")) ); // finds / +``` ## new RegExp @@ -61,25 +61,25 @@ let reg = new RegExp("\d\.\d"); alert( "Chapter 5.1".match(reg) ); // null ``` -The search worked with `pattern:/\d\.\d/`, but with `new RegExp("\d\.\d")` it doesn't work, why? +The similar search in one of previous examples worked with `pattern:/\d\.\d/`, but `new RegExp("\d\.\d")` doesn't work, why? -The reason is that backslashes are "consumed" by a string. Remember, regular strings have their own special characters like `\n`, and a backslash is used for escaping. +The reason is that backslashes are "consumed" by a string. As we may recall, regular strings have their own special characters, such as `\n`, and a backslash is used for escaping. -Please, take a look, what "\d\.\d" really is: +Here's how "\d\.\d" is preceived: ```js run alert("\d\.\d"); // d.d ``` -The quotes "consume" backslashes and interpret them, for instance: +String quotes "consume" backslashes and interpret them on their own, for instance: - `\n` -- becomes a newline character, - `\u1234` -- becomes the Unicode character with such code, - ...And when there's no special meaning: like `pattern:\d` or `\z`, then the backslash is simply removed. -So the call to `new RegExp` gets a string without backslashes. That's why the search doesn't work! +So `new RegExp` gets a string without backslashes. That's why the search doesn't work! -To fix it, we need to double backslashes, because quotes turn `\\` into `\`: +To fix it, we need to double backslashes, because string quotes turn `\\` into `\`: ```js run *!* @@ -94,6 +94,6 @@ alert( "Chapter 5.1".match(reg) ); // 5.1 ## Summary -- To search special characters `pattern:[ \ ^ $ . | ? * + ( )` literally, we need to prepend them with `\` ("escape them"). +- To search for special characters `pattern:[ \ ^ $ . | ? * + ( )` literally, we need to prepend them with a backslash `\` ("escape them"). - We also need to escape `/` if we're inside `pattern:/.../` (but not inside `new RegExp`). -- When passing a string `new RegExp`, we need to double backslashes `\\`, cause strings consume one of them. +- When passing a string `new RegExp`, we need to double backslashes `\\`, cause string quotes consume one of them. From 1491c396b904e710dd1b69170e2ca96659c9af8c Mon Sep 17 00:00:00 2001 From: Ilya Kantor Date: Thu, 5 Sep 2019 14:57:06 +0300 Subject: [PATCH 060/101] WIP --- 02-regexp-character-classes/article.md | 2 +- .../article.md | 121 ++++++-- 09-regexp-quantifiers/article.md | 64 ++-- .../3-find-html-comments/solution.md | 8 +- 10-regexp-greedy-and-lazy/article.md | 45 ++- .../1-find-webcolor-3-or-6/solution.md | 8 +- .../1-find-webcolor-3-or-6/task.md | 2 +- .../2-find-decimal-numbers/solution.md | 2 +- .../5-parse-expression/solution.md | 19 +- 11-regexp-groups/article.md | 280 ++++++++++++------ ...s.svg => regexp-nested-groups-matches.svg} | 0 .../regexp-nested-groups-pattern.svg | 1 + 12 files changed, 371 insertions(+), 181 deletions(-) rename 11-regexp-groups/{regexp-nested-groups.svg => regexp-nested-groups-matches.svg} (100%) create mode 100644 11-regexp-groups/regexp-nested-groups-pattern.svg diff --git a/02-regexp-character-classes/article.md b/02-regexp-character-classes/article.md index 881b6ba25..34240b6e9 100644 --- a/02-regexp-character-classes/article.md +++ b/02-regexp-character-classes/article.md @@ -41,7 +41,7 @@ Most used are: : A digit: a character from `0` to `9`. `pattern:\s` ("s" is from "space") -: A space symbol: includes spaces, tabs `\t`, newlines `\n` and few other rare characters: `\v`, `\f` and `\r`. +: A space symbol: includes spaces, tabs `\t`, newlines `\n` and few other rare characters, such as `\v`, `\f` and `\r`. `pattern:\w` ("w" is from "word") : A "wordly" character: either a letter of Latin alphabet or a digit or an underscore `_`. Non-Latin letters (like cyrillic or hindi) do not belong to `pattern:\w`. diff --git a/08-regexp-character-sets-and-ranges/article.md b/08-regexp-character-sets-and-ranges/article.md index 3a94125c3..6beca62be 100644 --- a/08-regexp-character-sets-and-ranges/article.md +++ b/08-regexp-character-sets-and-ranges/article.md @@ -22,7 +22,7 @@ So the example below gives no matches: alert( "Voila".match(/V[oi]la/) ); // null, no matches ``` -The pattern assumes: +The pattern searches for: - `pattern:V`, - then *one* of the letters `pattern:[oi]`, @@ -42,23 +42,56 @@ In the example below we're searching for `"x"` followed by two digits or letters alert( "Exception 0xAF".match(/x[0-9A-F][0-9A-F]/g) ); // xAF ``` -Please note that in the word `subject:Exception` there's a substring `subject:xce`. It didn't match the pattern, because the letters are lowercase, while in the set `pattern:[0-9A-F]` they are uppercase. +Here `pattern:[0-9A-F]` has two ranges: it searches for a character that is either a digit from `0` to `9` or a letter from `A` to `F`. -If we want to find it too, then we can add a range `a-f`: `pattern:[0-9A-Fa-f]`. The `pattern:i` flag would allow lowercase too. +If we'd like to look for lowercase letters as well, we can add the range `a-f`: `pattern:[0-9A-Fa-f]`. Or add the flag `pattern:i`. -**Character classes are shorthands for certain character sets.** +We can also use character classes inside `[…]`. +For instance, if we'd like to look for a wordly character `pattern:\w` or a hyphen `pattern:-`, then the set is `pattern:[\w-]`. + +Combining multiple classes is also possible, e.g. `pattern:[\s\d]` means "a space character or a digit". + +```smart header="Character classes are shorthands for certain character sets" For instance: - **\d** -- is the same as `pattern:[0-9]`, - **\w** -- is the same as `pattern:[a-zA-Z0-9_]`, -- **\s** -- is the same as `pattern:[\t\n\v\f\r ]` plus few other unicode space characters. +- **\s** -- is the same as `pattern:[\t\n\v\f\r ]`, plus few other rare unicode space characters. +``` + +### Example: multi-language \w + +As the character class `pattern:\w` is a shorthand for `pattern:[a-zA-Z0-9_]`, it can't find Chinese hieroglyphs, Cyrillic letters, etc. + +We can write a more universal pattern, that looks for wordly characters in any language. That's easy with unicode properties: `pattern:[\p{Alpha}\p{M}\p{Nd}\p{Pc}\p{Join_C}]`. -We can use character classes inside `[…]` as well. +Let's decipher it. Similar to `pattern:\w`, we're making a set of our own that includes characters with following unicode properties: -For instance, we want to match all wordly characters or a dash, for words like "twenty-third". We can't do it with `pattern:\w+`, because `pattern:\w` class does not include a dash. But we can use `pattern:[\w-]`. +- `Alphabetic` (`Alpha`) - for letters, +- `Mark` (`M`) - for accents, +- `Decimal_Number` (`Nd`) - for digits, +- `Connector_Punctuation` (`Pc`) - for the underscore `'_'` and similar characters, +- `Join_Control` (`Join_C`) - two special codes `200c` and `200d`, used in ligatures, e.g. in Arabic. -We also can use several classes, for example `pattern:[\s\S]` matches spaces or non-spaces -- any character. That's wider than a dot `"."`, because the dot matches any character except a newline (unless `pattern:s` flag is set). +An example of use: + +```js run +let regexp = /[\p{Alpha}\p{M}\p{Nd}\p{Pc}\p{Join_C}]/gu; + +let str = `Hi 你好 12`; + +// finds all letters and digits: +alert( str.match(regexp) ); // H,i,你,好,1,2 +``` + +Of course, we can edit this pattern: add unicode properties or remove them. Unicode properties are covered in more details in the article . + +```warn header="Unicode properties aren't supported in Edge and Firefox" +Unicode properties `pattern:p{…}` are not yet implemented in Edge and Firefox. If we really need them, we can use library [XRegExp](http://xregexp.com/). + +Or just use ranges of characters in a language that interests us, e.g. `pattern:[а-я]` for Cyrillic letters. +``` ## Excluding ranges @@ -78,22 +111,20 @@ The example below looks for any characters except letters, digits and spaces: alert( "alice15@gmail.com".match(/[^\d\sA-Z]/gi) ); // @ and . ``` -## No escaping in […] +## Escaping in […] -Usually when we want to find exactly the dot character, we need to escape it like `pattern:\.`. And if we need a backslash, then we use `pattern:\\`. +Usually when we want to find exactly a special character, we need to escape it like `pattern:\.`. And if we need a backslash, then we use `pattern:\\`, and so on. -In square brackets the vast majority of special characters can be used without escaping: +In square brackets we can use the vast majority of special characters without escaping: -- A dot `pattern:'.'`. -- A plus `pattern:'+'`. -- Parentheses `pattern:'( )'`. -- Dash `pattern:'-'` in the beginning or the end (where it does not define a range). -- A caret `pattern:'^'` if not in the beginning (where it means exclusion). -- And the opening square bracket `pattern:'['`. +- Symbols `pattern:. + ( )` never need escaping. +- A hyphen `pattern:-` is not escaped in the beginning or the end (where it does not define a range). +- A caret `pattern:^` is only escaped in the beginning (where it means exclusion). +- The closing square bracket `pattern:]` is always escaped (if we need to look for that symbol). -In other words, all special characters are allowed except where they mean something for square brackets. +In other words, all special characters are allowed without escaping, except when they mean something for square brackets. -A dot `"."` inside square brackets means just a dot. The pattern `pattern:[.,]` would look for one of characters: either a dot or a comma. +A dot `.` inside square brackets means just a dot. The pattern `pattern:[.,]` would look for one of characters: either a dot or a comma. In the example below the regexp `pattern:[-().^+]` looks for one of the characters `-().^+`: @@ -112,3 +143,55 @@ let reg = /[\-\(\)\.\^\+]/g; alert( "1 + 2 - 3".match(reg) ); // also works: +, - ``` + +## Ranges and flag "u" + +If there are surrogate pairs in the set, flag `pattern:u` is required for them to work correctly. + +For instance, let's look for `pattern:[𝒳𝒴]` in the string `subject:𝒳`: + +```js run +alert( '𝒳'.match(/[𝒳𝒴]/) ); // shows a strange character, like [?] +// (the search was performed incorrectly, half-character returned) +``` + +The result is incorrect, because by default regular expressions "don't know" about surrogate pairs. + +The regular expression engine thinks that `[𝒳𝒴]` -- are not two, but four characters: +1. left half of `𝒳` `(1)`, +2. right half of `𝒳` `(2)`, +3. left half of `𝒴` `(3)`, +4. right half of `𝒴` `(4)`. + +We can see their codes like this: + +```js run +for(let i=0; i<'𝒳𝒴'.length; i++) { + alert('𝒳𝒴'.charCodeAt(i)); // 55349, 56499, 55349, 56500 +}; +``` + +So, the example above finds and shows the left half of `𝒳`. + +If we add flag `pattern:u`, then the behavior will be correct: + +```js run +alert( '𝒳'.match(/[𝒳𝒴]/u) ); // 𝒳 +``` + +The similar situation occurs when looking for a range, such as `[𝒳-𝒴]`. + +If we forget to add flag `pattern:u`, there will be an error: + +```js run +'𝒳'.match(/[𝒳-𝒴]/); // Error: Invalid regular expression +``` + +The reason is that without flag `pattern:u` surrogate pairs are perceived as two characters, so `[𝒳-𝒴]` is interpreted as `[<55349><56499>-<55349><56500>]` (every surrogate pair is replaced with its codes). Now it's easy to see that the range `56499-55349` is invalid: its starting code `56499` is greater than the end `55349`. That's the formal reason for the error. + +With the flag `pattern:u` the pattern works correctly: + +```js run +// look for characters from 𝒳 to 𝒵 +alert( '𝒴'.match(/[𝒳-𝒵]/u) ); // 𝒴 +``` diff --git a/09-regexp-quantifiers/article.md b/09-regexp-quantifiers/article.md index 9b70d7227..1a7eecfeb 100644 --- a/09-regexp-quantifiers/article.md +++ b/09-regexp-quantifiers/article.md @@ -2,7 +2,7 @@ Let's say we have a string like `+7(903)-123-45-67` and want to find all numbers in it. But unlike before, we are interested not in single digits, but full numbers: `7, 903, 123, 45, 67`. -A number is a sequence of 1 or more digits `pattern:\d`. To mark how many we need, we need to append a *quantifier*. +A number is a sequence of 1 or more digits `pattern:\d`. To mark how many we need, we can append a *quantifier*. ## Quantity {n} @@ -12,7 +12,7 @@ A quantifier is appended to a character (or a character class, or a `[...]` set It has a few advanced forms, let's see examples: -The exact count: `{5}` +The exact count: `pattern:{5}` : `pattern:\d{5}` denotes exactly 5 digits, the same as `pattern:\d\d\d\d\d`. The example below looks for a 5-digit number: @@ -23,7 +23,7 @@ The exact count: `{5}` We can add `\b` to exclude longer numbers: `pattern:\b\d{5}\b`. -The range: `{3,5}`, match 3-5 times +The range: `pattern:{3,5}`, match 3-5 times : To find numbers from 3 to 5 digits we can put the limits into curly braces: `pattern:\d{3,5}` ```js run @@ -54,8 +54,8 @@ alert(numbers); // 7,903,123,45,67 There are shorthands for most used quantifiers: -`+` -: Means "one or more", the same as `{1,}`. +`pattern:+` +: Means "one or more", the same as `pattern:{1,}`. For instance, `pattern:\d+` looks for numbers: @@ -65,8 +65,8 @@ There are shorthands for most used quantifiers: alert( str.match(/\d+/g) ); // 7,903,123,45,67 ``` -`?` -: Means "zero or one", the same as `{0,1}`. In other words, it makes the symbol optional. +`pattern:?` +: Means "zero or one", the same as `pattern:{0,1}`. In other words, it makes the symbol optional. For instance, the pattern `pattern:ou?r` looks for `match:o` followed by zero or one `match:u`, and then `match:r`. @@ -78,16 +78,16 @@ There are shorthands for most used quantifiers: alert( str.match(/colou?r/g) ); // color, colour ``` -`*` -: Means "zero or more", the same as `{0,}`. That is, the character may repeat any times or be absent. +`pattern:*` +: Means "zero or more", the same as `pattern:{0,}`. That is, the character may repeat any times or be absent. - For example, `pattern:\d0*` looks for a digit followed by any number of zeroes: + For example, `pattern:\d0*` looks for a digit followed by any number of zeroes (may be many or none): ```js run alert( "100 10 1".match(/\d0*/g) ); // 100, 10, 1 ``` - Compare it with `'+'` (one or more): + Compare it with `pattern:+` (one or more): ```js run alert( "100 10 1".match(/\d0+/g) ); // 100, 10 @@ -98,43 +98,45 @@ There are shorthands for most used quantifiers: Quantifiers are used very often. They serve as the main "building block" of complex regular expressions, so let's see more examples. -Regexp "decimal fraction" (a number with a floating point): `pattern:\d+\.\d+` -: In action: - ```js run - alert( "0 1 12.345 7890".match(/\d+\.\d+/g) ); // 12.345 - ``` +**Regexp for decimal fractions (a number with a floating point): `pattern:\d+\.\d+`** -Regexp "open HTML-tag without attributes", like `` or `

`: `pattern:/<[a-z]+>/i` -: In action: +In action: +```js run +alert( "0 1 12.345 7890".match(/\d+\.\d+/g) ); // 12.345 +``` + +**Regexp for an "opening HTML-tag without attributes", such as `` or `

`.** + +1. The simplest one: `pattern:/<[a-z]+>/i` ```js run alert( " ... ".match(/<[a-z]+>/gi) ); // ``` - We look for character `pattern:'<'` followed by one or more Latin letters, and then `pattern:'>'`. + The regexp looks for character `pattern:'<'` followed by one or more Latin letters, and then `pattern:'>'`. + +2. Improved: `pattern:/<[a-z][a-z0-9]*>/i` -Regexp "open HTML-tag without attributes" (improved): `pattern:/<[a-z][a-z0-9]*>/i` -: Better regexp: according to the standard, HTML tag name may have a digit at any position except the first one, like `

`. + According to the standard, HTML tag name may have a digit at any position except the first one, like `

`. ```js run alert( "

Hi!

".match(/<[a-z][a-z0-9]*>/gi) ); //

``` -Regexp "opening or closing HTML-tag without attributes": `pattern:/<\/?[a-z][a-z0-9]*>/i` -: We added an optional slash `pattern:/?` before the tag. Had to escape it with a backslash, otherwise JavaScript would think it is the pattern end. +**Regexp "opening or closing HTML-tag without attributes": `pattern:/<\/?[a-z][a-z0-9]*>/i`** - ```js run - alert( "

Hi!

".match(/<\/?[a-z][a-z0-9]*>/gi) ); //

,

- ``` +We added an optional slash `pattern:/?` near the beginning of the pattern. Had to escape it with a backslash, otherwise JavaScript would think it is the pattern end. + +```js run +alert( "

Hi!

".match(/<\/?[a-z][a-z0-9]*>/gi) ); //

,

+``` ```smart header="To make a regexp more precise, we often need make it more complex" We can see one common rule in these examples: the more precise is the regular expression -- the longer and more complex it is. -For instance, for HTML tags we could use a simpler regexp: `pattern:<\w+>`. - -...But because `pattern:\w` means any Latin letter or a digit or `'_'`, the regexp also matches non-tags, for instance `match:<_>`. So it's much simpler than `pattern:<[a-z][a-z0-9]*>`, but less reliable. +For instance, for HTML tags we could use a simpler regexp: `pattern:<\w+>`. But as HTML has stricter restrictions for a tag name, `pattern:<[a-z][a-z0-9]*>` is more reliable. -Are we ok with `pattern:<\w+>` or we need `pattern:<[a-z][a-z0-9]*>`? +Can we use `pattern:<\w+>` or we need `pattern:<[a-z][a-z0-9]*>`? -In real life both variants are acceptable. Depends on how tolerant we can be to "extra" matches and whether it's difficult or not to filter them out by other means. +In real life both variants are acceptable. Depends on how tolerant we can be to "extra" matches and whether it's difficult or not to remove them from the result by other means. ``` diff --git a/10-regexp-greedy-and-lazy/3-find-html-comments/solution.md b/10-regexp-greedy-and-lazy/3-find-html-comments/solution.md index c066f3e36..b3290607c 100644 --- a/10-regexp-greedy-and-lazy/3-find-html-comments/solution.md +++ b/10-regexp-greedy-and-lazy/3-find-html-comments/solution.md @@ -1,13 +1,11 @@ We need to find the beginning of the comment `match:`. -The first idea could be `pattern:` -- the lazy quantifier makes the dot stop right before `match:-->`. +An acceptable variant is `pattern:` -- the lazy quantifier makes the dot stop right before `match:-->`. We also need to add flag `pattern:s` for the dot to include newlines. -But a dot in JavaScript means "any symbol except the newline". So multiline comments won't be found. - -We can use `pattern:[\s\S]` instead of the dot to match "anything": +Otherwise multiline comments won't be found: ```js run -let reg = //g; +let reg = //gs; let str = `... .. .. diff --git a/10-regexp-greedy-and-lazy/article.md b/10-regexp-greedy-and-lazy/article.md index 5670e57b5..e014c16d0 100644 --- a/10-regexp-greedy-and-lazy/article.md +++ b/10-regexp-greedy-and-lazy/article.md @@ -8,7 +8,7 @@ Let's take the following task as an example. We have a text and need to replace all quotes `"..."` with guillemet marks: `«...»`. They are preferred for typography in many countries. -For instance: `"Hello, world"` should become `«Hello, world»`. Some countries prefer other quotes, like `„Witam, świat!”` (Polish) or `「你好,世界」` (Chinese), but for our task let's choose `«...»`. +For instance: `"Hello, world"` should become `«Hello, world»`. There exist other quotes, such as `„Witam, świat!”` (Polish) or `「你好,世界」` (Chinese), but for our task let's choose `«...»`. The first thing to do is to locate quoted strings, and then we can replace them. @@ -35,7 +35,7 @@ That can be described as "greediness is the cause of all evil". To find a match, the regular expression engine uses the following algorithm: - For every position in the string - - Match the pattern at that position. + - Try to match the pattern at that position. - If there's no match, go to the next position. These common words do not make it obvious why the regexp fails, so let's elaborate how the search works for the pattern `pattern:".+"`. @@ -44,7 +44,7 @@ These common words do not make it obvious why the regexp fails, so let's elabora The regular expression engine tries to find it at the zero position of the source string `subject:a "witch" and her "broom" is one`, but there's `subject:a` there, so there's immediately no match. - Then it advances: goes to the next positions in the source string and tries to find the first character of the pattern there, and finally finds the quote at the 3rd position: + Then it advances: goes to the next positions in the source string and tries to find the first character of the pattern there, fails again, and finally finds the quote at the 3rd position: ![](witch_greedy1.svg) @@ -54,13 +54,13 @@ These common words do not make it obvious why the regexp fails, so let's elabora ![](witch_greedy2.svg) -3. Then the dot repeats because of the quantifier `pattern:.+`. The regular expression engine builds the match by taking characters one by one while it is possible. +3. Then the dot repeats because of the quantifier `pattern:.+`. The regular expression engine adds to the match one character after another. - ...When does it become impossible? All characters match the dot, so it only stops when it reaches the end of the string: + ...Until when? All characters match the dot, so it only stops when it reaches the end of the string: ![](witch_greedy3.svg) -4. Now the engine finished repeating for `pattern:.+` and tries to find the next character of the pattern. It's the quote `pattern:"`. But there's a problem: the string has finished, there are no more characters! +4. Now the engine finished repeating `pattern:.+` and tries to find the next character of the pattern. It's the quote `pattern:"`. But there's a problem: the string has finished, there are no more characters! The regular expression engine understands that it took too many `pattern:.+` and starts to *backtrack*. @@ -68,9 +68,9 @@ These common words do not make it obvious why the regexp fails, so let's elabora ![](witch_greedy4.svg) - Now it assumes that `pattern:.+` ends one character before the end and tries to match the rest of the pattern from that position. + Now it assumes that `pattern:.+` ends one character before the string end and tries to match the rest of the pattern from that position. - If there were a quote there, then that would be the end, but the last character is `subject:'e'`, so there's no match. + If there were a quote there, then the search would end, but the last character is `subject:'e'`, so there's no match. 5. ...So the engine decreases the number of repetitions of `pattern:.+` by one more character: @@ -84,19 +84,19 @@ These common words do not make it obvious why the regexp fails, so let's elabora 7. The match is complete. -8. So the first match is `match:"witch" and her "broom"`. The further search starts where the first match ends, but there are no more quotes in the rest of the string `subject:is one`, so no more results. +8. So the first match is `match:"witch" and her "broom"`. If the regular expression has flag `pattern:g`, then the search will continue from where the first match ends. There are no more quotes in the rest of the string `subject:is one`, so no more results. That's probably not what we expected, but that's how it works. -**In the greedy mode (by default) the quantifier is repeated as many times as possible.** +**In the greedy mode (by default) a quantifier is repeated as many times as possible.** -The regexp engine tries to fetch as many characters as it can by `pattern:.+`, and then shortens that one by one. +The regexp engine adds to the match as many characters as it can for `pattern:.+`, and then shortens that one by one, if the rest of the pattern doesn't match. -For our task we want another thing. That's what the lazy quantifier mode is for. +For our task we want another thing. That's where a lazy mode can help. ## Lazy mode -The lazy mode of quantifier is an opposite to the greedy mode. It means: "repeat minimal number of times". +The lazy mode of quantifiers is an opposite to the greedy mode. It means: "repeat minimal number of times". We can enable it by putting a question mark `pattern:'?'` after the quantifier, so that it becomes `pattern:*?` or `pattern:+?` or even `pattern:??` for `pattern:'?'`. @@ -149,20 +149,19 @@ Other quantifiers remain greedy. For instance: ```js run -alert( "123 456".match(/\d+ \d+?/g) ); // 123 4 +alert( "123 456".match(/\d+ \d+?/) ); // 123 4 ``` -1. The pattern `pattern:\d+` tries to match as many numbers as it can (greedy mode), so it finds `match:123` and stops, because the next character is a space `pattern:' '`. -2. Then there's a space in pattern, it matches. +1. The pattern `pattern:\d+` tries to match as many digits as it can (greedy mode), so it finds `match:123` and stops, because the next character is a space `pattern:' '`. +2. Then there's a space in the pattern, it matches. 3. Then there's `pattern:\d+?`. The quantifier is in lazy mode, so it finds one digit `match:4` and tries to check if the rest of the pattern matches from there. ...But there's nothing in the pattern after `pattern:\d+?`. The lazy mode doesn't repeat anything without a need. The pattern finished, so we're done. We have a match `match:123 4`. -4. The next search starts from the character `5`. ```smart header="Optimizations" -Modern regular expression engines can optimize internal algorithms to work faster. So they may work a bit different from the described algorithm. +Modern regular expression engines can optimize internal algorithms to work faster. So they may work a bit differently from the described algorithm. But to understand how regular expressions work and to build regular expressions, we don't need to know about that. They are only used internally to optimize things. @@ -264,7 +263,7 @@ That's what's going on: 2. Then it looks for `pattern:.*?`: takes one character (lazily!), check if there's a match for `pattern:" class="doc">` (none). 3. Then takes another character into `pattern:.*?`, and so on... until it finally reaches `match:" class="doc">`. -But the problem is: that's already beyond the link, in another tag `

`. Not what we want. +But the problem is: that's already beyond the link ``, in another tag `

`. Not what we want. Here's the picture of the match aligned with the text: @@ -273,11 +272,9 @@ Here's the picture of the match aligned with the text: ...

``` -So the laziness did not work for us here. +So, we need the pattern to look for ``, but both greedy and lazy variants have problems. -We need the pattern to look for ``, but both greedy and lazy variants have problems. - -The correct variant would be: `pattern:href="[^"]*"`. It will take all characters inside the `href` attribute till the nearest quote, just what we need. +The correct variant can be: `pattern:href="[^"]*"`. It will take all characters inside the `href` attribute till the nearest quote, just what we need. A working example: @@ -301,4 +298,4 @@ Greedy Lazy : Enabled by the question mark `pattern:?` after the quantifier. The regexp engine tries to match the rest of the pattern before each repetition of the quantifier. -As we've seen, the lazy mode is not a "panacea" from the greedy search. An alternative is a "fine-tuned" greedy search, with exclusions. Soon we'll see more examples of it. +As we've seen, the lazy mode is not a "panacea" from the greedy search. An alternative is a "fine-tuned" greedy search, with exclusions, as in the pattern `pattern:"[^"]+"`. diff --git a/11-regexp-groups/1-find-webcolor-3-or-6/solution.md b/11-regexp-groups/1-find-webcolor-3-or-6/solution.md index d653ff970..e173aba6f 100644 --- a/11-regexp-groups/1-find-webcolor-3-or-6/solution.md +++ b/11-regexp-groups/1-find-webcolor-3-or-6/solution.md @@ -1,12 +1,10 @@ A regexp to search 3-digit color `#abc`: `pattern:/#[a-f0-9]{3}/i`. -We can add exactly 3 more optional hex digits. We don't need more or less. Either we have them or we don't. +We can add exactly 3 more optional hex digits. We don't need more or less. The color has either 3 or 6 digits. -The simplest way to add them -- is to append to the regexp: `pattern:/#[a-f0-9]{3}([a-f0-9]{3})?/i` +Let's use the quantifier `pattern:{1,2}` for that: we'll have `pattern:/#([a-f0-9]{3}){1,2}/i`. -We can do it in a smarter way though: `pattern:/#([a-f0-9]{3}){1,2}/i`. - -Here the regexp `pattern:[a-f0-9]{3}` is in parentheses to apply the quantifier `pattern:{1,2}` to it as a whole. +Here the pattern `pattern:[a-f0-9]{3}` is enclosed in parentheses to apply the quantifier `pattern:{1,2}`. In action: diff --git a/11-regexp-groups/1-find-webcolor-3-or-6/task.md b/11-regexp-groups/1-find-webcolor-3-or-6/task.md index 4efd6f61f..d87914e99 100644 --- a/11-regexp-groups/1-find-webcolor-3-or-6/task.md +++ b/11-regexp-groups/1-find-webcolor-3-or-6/task.md @@ -11,4 +11,4 @@ let str = "color: #3f3; background-color: #AA00ef; and: #abcd"; alert( str.match(reg) ); // #3f3 #AA00ef ``` -P.S. This should be exactly 3 or 6 hex digits: values like `#abcd` should not match. +P.S. This should be exactly 3 or 6 hex digits. Values with 4 digits, such as `#abcd`, should not match. diff --git a/11-regexp-groups/2-find-decimal-numbers/solution.md b/11-regexp-groups/2-find-decimal-numbers/solution.md index dd2410847..3155f13c4 100644 --- a/11-regexp-groups/2-find-decimal-numbers/solution.md +++ b/11-regexp-groups/2-find-decimal-numbers/solution.md @@ -1,6 +1,6 @@ A positive number with an optional decimal part is (per previous task): `pattern:\d+(\.\d+)?`. -Let's add an optional `-` in the beginning: +Let's add the optional `pattern:-` in the beginning: ```js run let reg = /-?\d+(\.\d+)?/g; diff --git a/11-regexp-groups/5-parse-expression/solution.md b/11-regexp-groups/5-parse-expression/solution.md index 7707edb08..decb074dc 100644 --- a/11-regexp-groups/5-parse-expression/solution.md +++ b/11-regexp-groups/5-parse-expression/solution.md @@ -1,16 +1,19 @@ A regexp for a number is: `pattern:-?\d+(\.\d+)?`. We created it in previous tasks. -An operator is `pattern:[-+*/]`. +An operator is `pattern:[-+*/]`. The hyphen `pattern:-` goes first in the square brackets, because in the middle it would mean a character range, while we just want a character `-`. -Please note: -- Here the dash `pattern:-` goes first in the brackets, because in the middle it would mean a character range, while we just want a character `-`. -- A slash `/` should be escaped inside a JavaScript regexp `pattern:/.../`, we'll do that later. +The slash `/` should be escaped inside a JavaScript regexp `pattern:/.../`, we'll do that later. We need a number, an operator, and then another number. And optional spaces between them. The full regular expression: `pattern:-?\d+(\.\d+)?\s*[-+*/]\s*-?\d+(\.\d+)?`. -To get a result as an array let's put parentheses around the data that we need: numbers and the operator: `pattern:(-?\d+(\.\d+)?)\s*([-+*/])\s*(-?\d+(\.\d+)?)`. +It has 3 parts, with `pattern:\s*` between them: +1. `pattern:-?\d+(\.\d+)?` - the first number, +1. `pattern:[-+*/]` - the operator, +1. `pattern:-?\d+(\.\d+)?` - the second number. + +To make each of these parts a separate element of the result array, let's enclose them in parentheses: `pattern:(-?\d+(\.\d+)?)\s*([-+*/])\s*(-?\d+(\.\d+)?)`. In action: @@ -29,11 +32,11 @@ The result includes: - `result[4] == "12"` (forth group `(-?\d+(\.\d+)?)` -- the second number) - `result[5] == undefined` (fifth group `(\.\d+)?` -- the last decimal part is absent, so it's undefined) -We only want the numbers and the operator, without the full match or the decimal parts. +We only want the numbers and the operator, without the full match or the decimal parts, so let's "clean" the result a bit. -The full match (the arrays first item) can be removed by shifting the array `pattern:result.shift()`. +The full match (the arrays first item) can be removed by shifting the array `result.shift()`. -The decimal groups can be removed by making them into non-capturing groups, by adding `pattern:?:` to the beginning: `pattern:(?:\.\d+)?`. +Groups that contain decimal parts (number 2 and 4) `pattern:(.\d+)` can be excluded by adding `pattern:?:` to the beginning: `pattern:(?:\.\d+)?`. The final solution: diff --git a/11-regexp-groups/article.md b/11-regexp-groups/article.md index 0d858e44f..9a3bb04f1 100644 --- a/11-regexp-groups/article.md +++ b/11-regexp-groups/article.md @@ -4,83 +4,92 @@ A part of a pattern can be enclosed in parentheses `pattern:(...)`. This is call That has two effects: -1. It allows to place a part of the match into a separate array. -2. If we put a quantifier after the parentheses, it applies to the parentheses as a whole, not the last character. +1. It allows to get a part of the match as a separate item in the result array. +2. If we put a quantifier after the parentheses, it applies to the parentheses as a whole. -## Example +## Examples -In the example below the pattern `pattern:(go)+` finds one or more `match:'go'`: +Let's see how parentheses work in examples. + +### Example: gogogo + +Without parentheses, the pattern `pattern:go+` means `subject:g` character, followed by `subject:o` repeated one or more times. For instance, `match:goooo` or `match:gooooooooo`. + +Parentheses group characters together, so `pattern:(go)+` means `match:go`, `match:gogo`, `match:gogogo` and so on. ```js run alert( 'Gogogo now!'.match(/(go)+/i) ); // "Gogogo" ``` -Without parentheses, the pattern `pattern:/go+/` means `subject:g`, followed by `subject:o` repeated one or more times. For instance, `match:goooo` or `match:gooooooooo`. +### Example: domain -Parentheses group the word `pattern:(go)` together. +Let's make something more complex -- a regular expression to search for a website domain. -Let's make something more complex -- a regexp to match an email. - -Examples of emails: +For example: ``` -my@mail.com -john.smith@site.com.uk +mail.com +users.mail.com +smith.users.mail.com ``` -The pattern: `pattern:[-.\w]+@([\w-]+\.)+[\w-]{2,20}`. - -1. The first part `pattern:[-.\w]+` (before `@`) may include any alphanumeric word characters, a dot and a dash, to match `match:john.smith`. -2. Then `pattern:@`, and the domain. It may be a subdomain like `host.site.com.uk`, so we match it as "a word followed by a dot `pattern:([\w-]+\.)` (repeated), and then the last part must be a word: `match:com` or `match:uk` (but not very long: 2-20 characters). +As we can see, a domain consists of repeated words, a dot after each one except the last one. -That regexp is not perfect, but good enough to fix errors or occasional mistypes. - -For instance, we can find all emails in the string: +In regular expressions that's `pattern:(\w+\.)+\w+`: ```js run -let reg = /[-.\w]+@([\w-]+\.)+[\w-]{2,20}/g; +let regexp = /(\w+\.)+\w+/g; -alert("my@mail.com @ his@site.com.uk".match(reg)); // my@mail.com, his@site.com.uk +alert( "site.com my.site.com".match(regexp) ); // site.com,my.site.com ``` -In this example parentheses were used to make a group for repetitions `pattern:([\w-]+\.)+`. But there are other uses too, let's see them. +The search works, but the pattern can't match a domain with a hyphen, e.g. `my-site.com`, because the hyphen does not belong to class `pattern:\w`. -## Contents of parentheses +We can fix it by replacing `pattern:\w` with `pattern:[\w-]` in every word except the last one: `pattern:([\w-]+\.)+\w+`. -Parentheses are numbered from left to right. The search engine remembers the content matched by each of them and allows to reference it in the pattern or in the replacement string. +### Example: email -For instance, we'd like to find HTML tags `pattern:<.*?>`, and process them. +The previous example can be extended. We can create a regular expression for emails based on it. -Let's wrap the inner content into parentheses, like this: `pattern:<(.*?)>`. +The email format is: `name@domain`. Any word can be the name, hyphens and dots are allowed. In regular expressions that's `pattern:[-.\w]+`. -Then we'll get both the tag as a whole and its content: +The pattern: ```js run -let str = '

Hello, world!

'; -let reg = /<(.*?)>/; +let reg = /[-.\w]+@([\w-]+\.)+[\w-]+/g; -alert( str.match(reg) ); // Array: ["

", "h1"] +alert("my@mail.com @ his@site.com.uk".match(reg)); // my@mail.com, his@site.com.uk ``` -The call to [String#match](mdn:js/String/match) returns groups only if the regexp only looks for the first match, that is: has no `pattern:/.../g` flag. +That regexp is not perfect, but mostly works and helps to fix accidental mistypes. The only truly reliable check for an email can only be done by sending a letter. -If we need all matches with their groups then we can use `.matchAll` or `regexp.exec` as described in : +## Parentheses contents in the match + +Parentheses are numbered from left to right. The search engine remembers the content matched by each of them and allows to get it in the result. + +The method `str.match(regexp)`, if `regexp` has no flag `g`, looks for the first match and returns it as an array: + +1. At index `0`: the full match. +2. At index `1`: the contents of the first parentheses. +3. На позиции `2`: the contents of the second parentheses. +4. ...and so on... + +For instance, we'd like to find HTML tags `pattern:<.*?>`, and process them. It would be convenient to have tag content (what's inside the angles), in a separate variable. + +Let's wrap the inner content into parentheses, like this: `pattern:<(.*?)>`. + +Now we'll get both the tag as a whole `match:

` and its contents `match:h1` in the resulting array: ```js run let str = '

Hello, world!

'; -// two matches: opening

and closing

tags -let reg = /<(.*?)>/g; - -let matches = Array.from( str.matchAll(reg) ); +let tag = str.match(/<(.*?)>/); -alert(matches[0]); // Array: ["

", "h1"] -alert(matches[1]); // Array: ["

", "/h1"] +alert( tag[0] ); //

+alert( tag[1] ); // h1 ``` -Here we have two matches for `pattern:<(.*?)>`, each of them is an array with the full match and groups. - -## Nested groups +### Nested groups Parentheses can be nested. In this case the numbering also goes from left to right. @@ -90,7 +99,13 @@ For instance, when searching a tag in `subject:` we may be inte 2. The tag name: `match:span`. 3. The tag attributes: `match:class="my"`. -Let's add parentheses for them: +Let's add parentheses for them: `pattern:<(([a-z]+)\s*([^>]*))>`. + +Here's how they are numbered (left to right, by the opening paren): + +![](regexp-nested-groups-pattern.svg) + +In action: ```js run let str = ''; @@ -98,20 +113,25 @@ let str = ''; let reg = /<(([a-z]+)\s*([^>]*))>/; let result = str.match(reg); -alert(result); // , span class="my", span, class="my" +alert(result[0]); // +alert(result[1]); // span class="my" +alert(result[2]); // span +alert(result[3]); // class="my" ``` -Here's how groups look: +The zero index of `result` always holds the full match. + +Then groups, numbered from left to right by an opening paren. The first group is returned as `result[1]`. Here it encloses the whole tag content. -![](regexp-nested-groups.svg) +Then in `result[2]` goes the group from the second opening paren `pattern:([a-z]+)` - tag name, then in `result[3]` the tag: `pattern:([^>]*)`. -At the zero index of the `result` is always the full match. +The contents of every group in the string: -Then groups, numbered from left to right. Whichever opens first gives the first group `result[1]`. Here it encloses the whole tag content. +![](regexp-nested-groups-matches.svg) -Then in `result[2]` goes the group from the second opening `pattern:(` till the corresponding `pattern:)` -- tag name, then we don't group spaces, but group attributes for `result[3]`. +### Optional groups -**Even if a group is optional and doesn't exist in the match, the corresponding `result` array item is present (and equals `undefined`).** +Even if a group is optional and doesn't exist in the match (e.g. has the quantifier `pattern:(...)?`), the corresponding `result` array item is present and equals `undefined`. For instance, let's consider the regexp `pattern:a(z)?(c)?`. It looks for `"a"` optionally followed by `"z"` optionally followed by `"c"`. @@ -128,10 +148,10 @@ alert( match[2] ); // undefined The array has the length of `3`, but all groups are empty. -And here's a more complex match for the string `subject:ack`: +And here's a more complex match for the string `subject:ac`: ```js run -let match = 'ack'.match(/a(z)?(c)?/) +let match = 'ac'.match(/a(z)?(c)?/) alert( match.length ); // 3 alert( match[0] ); // ac (whole match) @@ -141,11 +161,90 @@ alert( match[2] ); // c The array length is permanent: `3`. But there's nothing for the group `pattern:(z)?`, so the result is `["ac", undefined, "c"]`. +## Searching for all matches with groups: matchAll + +```warn header="`matchAll` is a new method, polyfill may be needed" +The method `matchAll` is not supported in old browsers. + +A polyfill may be required, such as . +``` + +When we search for all matches (flag `pattern:g`), the `match` method does not return contents for groups. + +For example, let's find all tags in a string: + +```js run +let str = '

'; + +let tags = str.match(/<(.*?)>/g); + +alert( tags ); //

,

+``` + +The result is an array of matches, but without details about each of them. But in practice we usually need contents of capturing groups in the result. + +To get them, we should search using the method `str.matchAll(regexp)`. + +It was added to JavaScript language long after `match`, as its "new and improved version". + +Just like `match`, it looks for matches, but there are 3 differences: + +1. It returns not an array, but an iterable object. +2. When the flag `pattern:g` is present, it returns every match as an array with groups. +3. If there are no matches, it returns not `null`, but an empty iterable object. + +For instance: + +```js run +let results = '

'.matchAll(/<(.*?)>/gi); + +// results - is not an array, but an iterable object +alert(results); // [object RegExp String Iterator] +alert(results[0]); // undefined + +results = Array.from(results); // let's turn it into array + +alert(results[0]); //

,h1 (1st tag) +alert(results[1]); //

,h2 (2nd tag) +``` + +As we can see, the first difference is very important. We can't get the match as `results[0]`, because that object isn't pseudoarray. We can turn it into a real `Array` using `Array.from`. There are more details about pseudoarrays and iterables in the article . + +There's no need in `Array.from` if we're looping over results: + +```js run +let results = '

'.matchAll(/<(.*?)>/gi); + +for(let result of results) { + alert(result); + // первый вывод:

,h1 + // второй:

,h2 +} +``` + +...Or using destructuring: + +```js +let [tag1, tag2] = '

'.matchAll(/<(.*?)>/gi); +``` + +```smart header="Why is a result of `matchAll` an iterable object, not an array?" +Why is the method designed like that? The reason is simple - for the optimization. + +The call to `matchAll` does not perform the search. Instead, it returns an iterable object, without the results initially. The search is performed each time we iterate over it, e.g. in the loop. + +So, there will be found as many results as needed, not more. + +E.g. there are potentially 100 matches in the text, but in a `for..of` loop we found 5 of them, then decided it's enough and make a `break`. Then the engine won't spend time finding other 95 mathces. +``` + ## Named groups -Remembering groups by their numbers is hard. For simple patterns it's doable, but for more complex ones we can give names to parentheses. +Remembering groups by their numbers is hard. For simple patterns it's doable, but for more complex ones counting parentheses is inconvenient. We have a much better option: give names to parentheses. -That's done by putting `pattern:?` immediately after the opening paren, like this: +That's done by putting `pattern:?` immediately after the opening paren. + +For example, let's look for a date in the format "year-month-day": ```js run *!* @@ -162,49 +261,51 @@ alert(groups.day); // 30 As you can see, the groups reside in the `.groups` property of the match. -We can also use them in the replacement string, as `pattern:$` (like `$1..9`, but a name instead of a digit). +To look for all dates, we can add flag `pattern:g`. -For instance, let's reformat the date into `day.month.year`: +We'll also need `matchAll` to obtain full matches, together with groups: ```js run -let dateRegexp = /(?[0-9]{4})-(?[0-9]{2})-(?[0-9]{2})/; +let dateRegexp = /(?[0-9]{4})-(?[0-9]{2})-(?[0-9]{2})/g; -let str = "2019-04-30"; +let str = "2019-10-30 2020-01-01"; + +let results = str.matchAll(dateRegexp); -let rearranged = str.replace(dateRegexp, '$.$.$'); +for(let result of results) { + let {year, month, day} = result.groups; -alert(rearranged); // 30.04.2019 + alert(`${day}.${month}.${year}`); + // first alert: 30.10.2019 + // second: 01.01.2020 +} ``` -If we use a function for the replacement, then named `groups` object is always the last argument: +## Capturing groups in replacement -```js run -let dateRegexp = /(?[0-9]{4})-(?[0-9]{2})-(?[0-9]{2})/; +Method `str.replace(regexp, replacement)` that replaces all matches with `regexp` in `str` allows to use parentheses contents in the `replacement` string. That's done using `pattern:$n`, where `pattern:n` is the group number. -let str = "2019-04-30"; +For example, -let rearranged = str.replace(dateRegexp, - (str, year, month, day, offset, input, groups) => - `${groups.day}.${groups.month}.${groups.year}` -); +```js run +let str = "John Bull"; +let regexp = /(\w+) (\w+)/; -alert(rearranged); // 30.04.2019 +alert( str.replace(regexp, '$2, $1') ); // Bull, John ``` -Usually, when we intend to use named groups, we don't need positional arguments of the function. For the majority of real-life cases we only need `str` and `groups`. +For named parentheses the reference will be `pattern:$`. -So we can write it a little bit shorter: +For example, let's reformat dates from "year-month-day" to "day.month.year": -```js -let rearranged = str.replace(dateRegexp, (str, ...args) => { - let {year, month, day} = args.pop(); - alert(str); // 2019-04-30 - alert(year); // 2019 - alert(month); // 04 - alert(day); // 30 -}); -``` +```js run +let regexp = /(?[0-9]{4})-(?[0-9]{2})-(?[0-9]{2})/g; +let str = "2019-10-30, 2020-01-01"; + +alert( str.replace(regexp, '$.$.$') ); +// 30.10.2019, 01.01.2020 +``` ## Non-capturing groups with ?: @@ -212,21 +313,23 @@ Sometimes we need parentheses to correctly apply a quantifier, but we don't want A group may be excluded by adding `pattern:?:` in the beginning. -For instance, if we want to find `pattern:(go)+`, but don't want to remember the contents (`go`) in a separate array item, we can write: `pattern:(?:go)+`. +For instance, if we want to find `pattern:(go)+`, but don't want the parentheses contents (`go`) as a separate array item, we can write: `pattern:(?:go)+`. -In the example below we only get the name "John" as a separate member of the `results` array: +In the example below we only get the name `match:John` as a separate member of the match: ```js run -let str = "Gogo John!"; +let str = "Gogogo John!"; + *!* -// exclude Gogo from capturing +// ?: exludes 'go' from capturing let reg = /(?:go)+ (\w+)/i; */!* let result = str.match(reg); -alert( result.length ); // 2 +alert( result[0] ); // Gogogo John (full match) alert( result[1] ); // John +alert( result.length ); // 2 (no more items in the array) ``` ## Summary @@ -235,8 +338,13 @@ Parentheses group together a part of the regular expression, so that the quantif Parentheses groups are numbered left-to-right, and can optionally be named with `(?...)`. -The content, matched by a group, can be referenced both in the replacement string as `$1`, `$2` etc, or by the name `$name` if named. +The content, matched by a group, can be obtained in the results: + +- The method `str.match` returns capturing groups only without flag `pattern:g`. +- The method `str.matchAll` always returns capturing groups. + +If the parentheses have no name, then their contents is available in the match array by its number. Named parentheses are also available in the property `groups`. -So, parentheses groups are called "capturing groups", as they "capture" a part of the match. We get that part separately from the result as a member of the array or in `.groups` if it's named. +We can also use parentheses contents in the replacement string in `str.replace`: by the number `$n` or the name `$`. -We can exclude the group from remembering (make in "non-capturing") by putting `?:` at the start: `(?:...)`, that's used if we'd like to apply a quantifier to the whole group, but don't need it in the result. +A group may be excluded from remembering by adding `pattern:?:` in its start. That's used when we need to apply a quantifier to the whole group, but don't remember it as a separate item in the results array. We also can't reference such parentheses in the replacement string. diff --git a/11-regexp-groups/regexp-nested-groups.svg b/11-regexp-groups/regexp-nested-groups-matches.svg similarity index 100% rename from 11-regexp-groups/regexp-nested-groups.svg rename to 11-regexp-groups/regexp-nested-groups-matches.svg diff --git a/11-regexp-groups/regexp-nested-groups-pattern.svg b/11-regexp-groups/regexp-nested-groups-pattern.svg new file mode 100644 index 000000000..ce61ff3a7 --- /dev/null +++ b/11-regexp-groups/regexp-nested-groups-pattern.svg @@ -0,0 +1 @@ +< (( [a-z]+ ) \s* ( [^>]* )) >1span class="my"2span3class="my" \ No newline at end of file From 760cb737bb15575f24a05a78b032afda795a9a0b Mon Sep 17 00:00:00 2001 From: Ilya Kantor Date: Fri, 6 Sep 2019 01:15:24 +0300 Subject: [PATCH 061/101] regexp --- 11-regexp-groups/01-test-mac/solution.md | 21 ++ 11-regexp-groups/01-test-mac/task.md | 20 ++ .../solution.md | 0 .../task.md | 0 .../solution.md | 0 .../task.md | 0 .../solution.md | 0 .../task.md | 0 11-regexp-groups/article.md | 4 +- 12-regexp-backreferences/article.md | 41 ++- 13-regexp-alternation/article.md | 37 ++- .../2-insert-after-head/solution.md | 29 ++ .../2-insert-after-head/task.md | 30 ++ 14-regexp-lookahead-lookbehind/article.md | 87 +++-- .../article.md | 301 ++++++++++++++++++ .../article.md | 297 ----------------- 16 files changed, 505 insertions(+), 362 deletions(-) create mode 100644 11-regexp-groups/01-test-mac/solution.md create mode 100644 11-regexp-groups/01-test-mac/task.md rename 11-regexp-groups/{1-find-webcolor-3-or-6 => 02-find-webcolor-3-or-6}/solution.md (100%) rename 11-regexp-groups/{1-find-webcolor-3-or-6 => 02-find-webcolor-3-or-6}/task.md (100%) rename 11-regexp-groups/{2-find-decimal-numbers => 03-find-decimal-numbers}/solution.md (100%) rename 11-regexp-groups/{2-find-decimal-numbers => 03-find-decimal-numbers}/task.md (100%) rename 11-regexp-groups/{5-parse-expression => 04-parse-expression}/solution.md (100%) rename 11-regexp-groups/{5-parse-expression => 04-parse-expression}/task.md (100%) create mode 100644 14-regexp-lookahead-lookbehind/2-insert-after-head/solution.md create mode 100644 14-regexp-lookahead-lookbehind/2-insert-after-head/task.md create mode 100644 15-regexp-catastrophic-backtracking/article.md delete mode 100644 15-regexp-infinite-backtracking-problem/article.md diff --git a/11-regexp-groups/01-test-mac/solution.md b/11-regexp-groups/01-test-mac/solution.md new file mode 100644 index 000000000..c16f0565a --- /dev/null +++ b/11-regexp-groups/01-test-mac/solution.md @@ -0,0 +1,21 @@ +A two-digit hex number is `pattern:[0-9a-f]{2}` (assuming the flag `pattern:i` is set). + +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) +``` diff --git a/11-regexp-groups/01-test-mac/task.md b/11-regexp-groups/01-test-mac/task.md new file mode 100644 index 000000000..e72655984 --- /dev/null +++ b/11-regexp-groups/01-test-mac/task.md @@ -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) +``` diff --git a/11-regexp-groups/1-find-webcolor-3-or-6/solution.md b/11-regexp-groups/02-find-webcolor-3-or-6/solution.md similarity index 100% rename from 11-regexp-groups/1-find-webcolor-3-or-6/solution.md rename to 11-regexp-groups/02-find-webcolor-3-or-6/solution.md diff --git a/11-regexp-groups/1-find-webcolor-3-or-6/task.md b/11-regexp-groups/02-find-webcolor-3-or-6/task.md similarity index 100% rename from 11-regexp-groups/1-find-webcolor-3-or-6/task.md rename to 11-regexp-groups/02-find-webcolor-3-or-6/task.md diff --git a/11-regexp-groups/2-find-decimal-numbers/solution.md b/11-regexp-groups/03-find-decimal-numbers/solution.md similarity index 100% rename from 11-regexp-groups/2-find-decimal-numbers/solution.md rename to 11-regexp-groups/03-find-decimal-numbers/solution.md diff --git a/11-regexp-groups/2-find-decimal-numbers/task.md b/11-regexp-groups/03-find-decimal-numbers/task.md similarity index 100% rename from 11-regexp-groups/2-find-decimal-numbers/task.md rename to 11-regexp-groups/03-find-decimal-numbers/task.md diff --git a/11-regexp-groups/5-parse-expression/solution.md b/11-regexp-groups/04-parse-expression/solution.md similarity index 100% rename from 11-regexp-groups/5-parse-expression/solution.md rename to 11-regexp-groups/04-parse-expression/solution.md diff --git a/11-regexp-groups/5-parse-expression/task.md b/11-regexp-groups/04-parse-expression/task.md similarity index 100% rename from 11-regexp-groups/5-parse-expression/task.md rename to 11-regexp-groups/04-parse-expression/task.md diff --git a/11-regexp-groups/article.md b/11-regexp-groups/article.md index 9a3bb04f1..855568be7 100644 --- a/11-regexp-groups/article.md +++ b/11-regexp-groups/article.md @@ -65,7 +65,7 @@ That regexp is not perfect, but mostly works and helps to fix accidental mistype ## Parentheses contents in the match -Parentheses are numbered from left to right. The search engine remembers the content matched by each of them and allows to get it in the result. +Parentheses are numbered from left to right. The search engine memorizes the content matched by each of them and allows to get it in the result. The method `str.match(regexp)`, if `regexp` has no flag `g`, looks for the first match and returns it as an array: @@ -347,4 +347,4 @@ If the parentheses have no name, then their contents is available in the match a We can also use parentheses contents in the replacement string in `str.replace`: by the number `$n` or the name `$`. -A group may be excluded from remembering by adding `pattern:?:` in its start. That's used when we need to apply a quantifier to the whole group, but don't remember it as a separate item in the results array. We also can't reference such parentheses in the replacement string. +A group may be excluded from numbering by adding `pattern:?:` in its start. That's used when we need to apply a quantifier to the whole group, but don't want it as a separate item in the results array. We also can't reference such parentheses in the replacement string. diff --git a/12-regexp-backreferences/article.md b/12-regexp-backreferences/article.md index eff5cab45..07d2ca07c 100644 --- a/12-regexp-backreferences/article.md +++ b/12-regexp-backreferences/article.md @@ -1,31 +1,31 @@ -# Backreferences in pattern: \n and \k +# Backreferences in pattern: \N and \k -We can use the contents of capturing groups `(...)` not only in the result or in the replacement string, but also in the pattern itself. +We can use the contents of capturing groups `pattern:(...)` not only in the result or in the replacement string, but also in the pattern itself. -## Backreference by number: \n +## Backreference by number: \N -A group can be referenced in the pattern using `\n`, where `n` is the group number. +A group can be referenced in the pattern using `pattern:\N`, where `N` is the group number. -To make things clear let's consider a task. +To make clear why that's helpful, let's consider a task. -We need to find a quoted string: either a single-quoted `subject:'...'` or a double-quoted `subject:"..."` -- both variants need to match. +We need to find quoted strings: either single-quoted `subject:'...'` or a double-quoted `subject:"..."` -- both variants should match. -How to look for them? +How to find them? -We can put both kinds of quotes in the square brackets: `pattern:['"](.*?)['"]`, but it would find strings with mixed quotes, like `match:"...'` and `match:'..."`. That would lead to incorrect matches when one quote appears inside other ones, like the string `subject:"She's the one!"`: +We can put both kinds of quotes in the square brackets: `pattern:['"](.*?)['"]`, but it would find strings with mixed quotes, like `match:"...'` and `match:'..."`. That would lead to incorrect matches when one quote appears inside other ones, like in the string `subject:"She's the one!"`: ```js run let str = `He said: "She's the one!".`; let reg = /['"](.*?)['"]/g; -// The result is not what we expect +// The result is not what we'd like to have alert( str.match(reg) ); // "She' ``` -As we can see, the pattern found an opening quote `match:"`, then the text is consumed lazily till the other quote `match:'`, that closes the match. +As we can see, the pattern found an opening quote `match:"`, then the text is consumed till the other quote `match:'`, that closes the match. -To make sure that the pattern looks for the closing quote exactly the same as the opening one, we can wrap it into a capturing group and use the backreference. +To make sure that the pattern looks for the closing quote exactly the same as the opening one, we can wrap it into a capturing group and backreference it: `pattern:(['"])(.*?)\1`. Here's the correct code: @@ -39,20 +39,27 @@ let reg = /(['"])(.*?)\1/g; alert( str.match(reg) ); // "She's the one!" ``` -Now it works! The regular expression engine finds the first quote `pattern:(['"])` and remembers the content of `pattern:(...)`, that's the first capturing group. +Now it works! The regular expression engine finds the first quote `pattern:(['"])` and memorizes its content. That's the first capturing group. Further in the pattern `pattern:\1` means "find the same text as in the first group", exactly the same quote in our case. -Please note: +Similar to that, `pattern:\2` would mean the contents of the second group, `pattern:\3` - the 3rd group, and so on. -- To reference a group inside a replacement string -- we use `$1`, while in the pattern -- a backslash `\1`. -- If we use `?:` in the group, then we can't reference it. Groups that are excluded from capturing `(?:...)` are not remembered by the engine. +```smart +If we use `?:` in the group, then we can't reference it. Groups that are excluded from capturing `(?:...)` are not memorized by the engine. +``` + +```warn header="Don't mess up: in the pattern `pattern:\1`, in the replacement: `pattern:$1`" +In the replacement string we use a dollar sign: `pattern:$1`, while in the pattern - a backslash `pattern:\1`. +``` ## Backreference by name: `\k` -For named groups, we can backreference by `\k`. +If a regexp has many parentheses, it's convenient to give them names. + +To reference a named group we can use `pattern:\k<имя>`. -The same example with the named group: +In the example below the group with quotes is named `pattern:?`, so the backreference is `pattern:\k`: ```js run let str = `He said: "She's the one!".`; diff --git a/13-regexp-alternation/article.md b/13-regexp-alternation/article.md index b26f7e4a6..5dcb9e865 100644 --- a/13-regexp-alternation/article.md +++ b/13-regexp-alternation/article.md @@ -18,7 +18,7 @@ let str = "First HTML appeared, then CSS, then JavaScript"; alert( str.match(reg) ); // 'HTML', 'CSS', 'JavaScript' ``` -We already know a similar thing -- square brackets. They allow to choose between multiple character, for instance `pattern:gr[ae]y` matches `match:gray` or `match:grey`. +We already saw a similar thing -- square brackets. They allow to choose between multiple characters, for instance `pattern:gr[ae]y` matches `match:gray` or `match:grey`. Square brackets allow only characters or character sets. Alternation allows any expressions. A regexp `pattern:A|B|C` means one of expressions `A`, `B` or `C`. @@ -27,30 +27,41 @@ For instance: - `pattern:gr(a|e)y` means exactly the same as `pattern:gr[ae]y`. - `pattern:gra|ey` means `match:gra` or `match:ey`. -To separate a part of the pattern for alternation we usually enclose it in parentheses, like this: `pattern:before(XXX|YYY)after`. +To apply alternation to a chosen part of the pattern, we can enclose it in parentheses: +- `pattern:I love HTML|CSS` matches `match:I love HTML` or `match:CSS`. +- `pattern:I love (HTML|CSS)` matches `match:I love HTML` or `match:I love CSS`. -## Regexp for time +## Example: regexp for time -In previous chapters there was a task to build a regexp for searching time in the form `hh:mm`, for instance `12:00`. But a simple `pattern:\d\d:\d\d` is too vague. It accepts `25:99` as the time (as 99 seconds match the pattern). +In previous articles there was a task to build a regexp for searching time in the form `hh:mm`, for instance `12:00`. But a simple `pattern:\d\d:\d\d` is too vague. It accepts `25:99` as the time (as 99 seconds match the pattern, but that time is invalid). -How can we make a better one? +How can we make a better pattern? -We can apply more careful matching. First, the hours: +We can use more careful matching. First, the hours: -- If the first digit is `0` or `1`, then the next digit can by anything. -- Or, if the first digit is `2`, then the next must be `pattern:[0-3]`. +- If the first digit is `0` or `1`, then the next digit can be any: `pattern:[01]\d`. +- Otherwise, if the first digit is `2`, then the next must be `pattern:[0-3]`. +- (no other first digit is allowed) -As a regexp: `pattern:[01]\d|2[0-3]`. +We can write both variants in a regexp using alternation: `pattern:[01]\d|2[0-3]`. -Next, the minutes must be from `0` to `59`. In the regexp language that means `pattern:[0-5]\d`: the first digit `0-5`, and then any digit. +Next, minutes must be from `00` to `59`. In the regular expression language that can be written as `pattern:[0-5]\d`: the first digit `0-5`, and then any digit. -Let's glue them together into the pattern: `pattern:[01]\d|2[0-3]:[0-5]\d`. +If we glue minutes and seconds together, we get the pattern: `pattern:[01]\d|2[0-3]:[0-5]\d`. We're almost done, but there's a problem. The alternation `pattern:|` now happens to be between `pattern:[01]\d` and `pattern:2[0-3]:[0-5]\d`. -That's wrong, as it should be applied only to hours `[01]\d` OR `2[0-3]`. That's a common mistake when starting to work with regular expressions. +That is: minutes are added to the second alternation variant, here's a clear picture: -The correct variant: +``` +[01]\d | 2[0-3]:[0-5]\d +``` + +That pattern looks for `pattern:[01]\d` or `pattern:2[0-3]:[0-5]\d`. + +But that's wrong, the alternation should only be used in the "hours" part of the regular expression, to allow `pattern:[01]\d` OR `pattern:2[0-3]`. Let's correct that by enclosing "hours" into parentheses: `pattern:([01]\d|2[0-3]):[0-5]\d`. + +The final solution: ```js run let reg = /([01]\d|2[0-3]):[0-5]\d/g; diff --git a/14-regexp-lookahead-lookbehind/2-insert-after-head/solution.md b/14-regexp-lookahead-lookbehind/2-insert-after-head/solution.md new file mode 100644 index 000000000..980a7fe66 --- /dev/null +++ b/14-regexp-lookahead-lookbehind/2-insert-after-head/solution.md @@ -0,0 +1,29 @@ + +Для того, чтобы вставить после тега ``, нужно вначале его найти. Будем использовать регулярное выражение `pattern:`. + +Далее, нам нужно оставить сам тег `` на месте и добавить текст после него. + +Это можно сделать вот так: +```js run +let str = '......'; +str = str.replace(//, '$&

Hello

'); + +alert(str); // ...

Hello

... +``` + +В строке замены `$&` означает само совпадение, то есть мы заменяем `pattern:` заменяется на самого себя плюс `

Hello

`. + +Альтернативный вариант - использовать ретроспективную проверку: + +```js run +let str = '......'; +str = str.replace(/(?<=)/, `

Hello

`); + +alert(str); // ...

Hello

... +``` + +Такое регулярное выражение на каждой позиции будет проверять, не идёт ли прямо перед ней `pattern:`. Если да - совпадение найдено. Но сам тег `pattern:` в совпадение не входит, он только участвует в проверке. А других символов после проверки в нём нет, так что текст совпадения будет пустым. + +Происходит замена "пустой строки", перед которой идёт `pattern:` на `

Hello

`. Что, как раз, и есть вставка этой строки после ``. + +P.S. Этому регулярному выражению не помешают флаги: `pattern://si`, чтобы в "точку" входил перевод строки (тег может занимать несколько строк), а также чтобы теги в другом регистре типа `match:` тоже находились. diff --git a/14-regexp-lookahead-lookbehind/2-insert-after-head/task.md b/14-regexp-lookahead-lookbehind/2-insert-after-head/task.md new file mode 100644 index 000000000..7bdfcd679 --- /dev/null +++ b/14-regexp-lookahead-lookbehind/2-insert-after-head/task.md @@ -0,0 +1,30 @@ +# Вставьте после фрагмента + +Есть строка с HTML-документом. + +Вставьте после тега `` (у него могут быть атрибуты) строку `

Hello

`. + +Например: + +```js +let reg = /ваше регулярное выражение/; + +let str = ` + + + ... + + +`; + +str = str.replace(reg, `

Hello

`); +``` + +После этого значение `str`: +```html + +

Hello

+ ... + + +``` diff --git a/14-regexp-lookahead-lookbehind/article.md b/14-regexp-lookahead-lookbehind/article.md index 8e36fb0bf..1115c5023 100644 --- a/14-regexp-lookahead-lookbehind/article.md +++ b/14-regexp-lookahead-lookbehind/article.md @@ -1,54 +1,82 @@ # Lookahead and lookbehind -Sometimes we need to match a pattern only if followed by another pattern. For instance, we'd like to get the price from a string like `subject:1 turkey costs 30€`. +Sometimes we need to find only those matches for a pattern that are followed or preceeded by another pattern. -We need a number (let's say a price has no decimal point) followed by `subject:€` sign. +There's a special syntax for that, called "lookahead" and "lookbehind", together referred to as "lookaround". -That's what lookahead is for. +For the start, let's find the price from the string like `subject:1 turkey costs 30€`. That is: a number, followed by `subject:€` sign. ## Lookahead -The syntax is: `pattern:x(?=y)`, it means "look for `pattern:x`, but match only if followed by `pattern:y`". +The syntax is: `pattern:X(?=Y)`, it means "look for `pattern:X`, but match only if followed by `pattern:Y`". There may be any pattern instead of `pattern:X` and `pattern:Y`. -For an integer amount followed by `subject:€`, the regexp will be `pattern:\d+(?=€)`: +For an integer number followed by `subject:€`, the regexp will be `pattern:\d+(?=€)`: ```js run let str = "1 turkey costs 30€"; -alert( str.match(/\d+(?=€)/) ); // 30 (correctly skipped the sole number 1) +alert( str.match(/\d+(?=€)/) ); // 30, the number 1 is ignored, as it's not followed by € ``` -Let's say we want a quantity instead, that is a number, NOT followed by `subject:€`. +Please note: the lookahead is merely a test, the contents of the parentheses `pattern:(?=...)` is not included in the result `match:30`. -Here a negative lookahead can be applied. +When we look for `pattern:X(?=Y)`, the regular expression engine finds `pattern:X` and then checks if there's `pattern:Y` immediately after it. If it's not so, then the potential match is skipped, and the search continues. -The syntax is: `pattern:x(?!y)`, it means "search `pattern:x`, but only if not followed by `pattern:y`". +More complex tests are possible, e.g. `pattern:X(?=Y)(?=Z)` means: + +1. Find `pattern:X`. +2. Check if `pattern:Y` is immediately after `pattern:X` (skip if isn't). +3. Check if `pattern:Z` is immediately after `pattern:X` (skip if isn't). +4. If both tests passed, then it's the match. + +In other words, such pattern means that we're looking for `pattern:X` followed by `pattern:Y` and `pattern:Z` at the same time. + +That's only possible if patterns `pattern:Y` and `pattern:Z` aren't mutually exclusive. + +For example, `pattern:\d+(?=\s)(?=.*30)` looks for `pattern:\d+` only if it's followed by a space, and there's `30` somewhere after it: + +```js run +let str = "1 turkey costs 30€"; + +alert( str.match(/\d+(?=\s)(?=.*30)/) ); // 1 +``` + +In our string that exactly matches the number `1`. + +## Negative lookahead + +Let's say that we want a quantity instead, not a price from the same string. That's a number `pattern:\d+`, NOT followed by `subject:€`. + +For that, a negative lookahead can be applied. + +The syntax is: `pattern:X(?!Y)`, it means "search `pattern:X`, but only if not followed by `pattern:Y`". ```js run let str = "2 turkeys cost 60€"; -alert( str.match(/\d+(?!€)/) ); // 2 (correctly skipped the price) +alert( str.match(/\d+(?!€)/) ); // 2 (the price is skipped) ``` ## Lookbehind -Lookahead allows to add a condition for "what goes after". +Lookahead allows to add a condition for "what follows". -Lookbehind is similar, but it looks behind. That is, it allows to match a pattern only if there's something before. +Lookbehind is similar, but it looks behind. That is, it allows to match a pattern only if there's something before it. The syntax is: -- Positive lookbehind: `pattern:(?<=y)x`, matches `pattern:x`, but only if it follows after `pattern:y`. -- Negative lookbehind: `pattern:(? + +```js run +let reg = /^(\d+)*$/; + +let str = "012345678901234567890123456789!"; + +// will take a very long time +alert( reg.test(str) ); +``` + +So what's wrong with the regexp? + +First, one may notice that the regexp `pattern:(\d+)*` is a little bit strange. The quantifier `pattern:*` looks extraneous. If we want a number, we can use `pattern:\d+`. + +Indeed, the regexp is artificial. But the reason why it is slow is the same as those we saw above. So let's understand it, and then the previous example will become obvious. + +What happens during the search of `pattern:^(\d+)*$` in the line `subject:123456789!` (shortened a bit for clarity), why does it take so long? + +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 + ``` + + Then it tries to apply the star quantifier, but there are no more digits, so it the star doesn't give anything. + + The next in the pattern is the string end `pattern:$`, but in the text we have `subject:!`, so there's no match: + + ``` + X + \d+........$ + (123456789)! + ``` + +2. As there's no match, the greedy quantifier `pattern:+` decreases the count of repetitions, backtracks one character back. + + Now `pattern:\d+` takes all digits except the last one: + ``` + \d+....... + (12345678)9! + ``` +3. Then the engine tries to continue the search from the new position (`9`). + + The star `pattern:(\d+)*` can be applied -- it gives the number `match:9`: + + ``` + + \d+.......\d+ + (12345678)(9)! + ``` + + The engine tries to match `pattern:$` again, but fails, because meets `subject:!`: + + ``` + X + \d+.......\d+ + (12345678)(9)z + ``` + + +4. There's no match, so the engine will continue backtracking, decreasing the number of repetitions. 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. + + All possible combinations are attempted. Here are their examples. + + The first number `pattern:\d+` has 7 digits, and then a number of 2 digits: + + ``` + X + \d+......\d+ + (1234567)(89)! + ``` + + The first number has 7 digits, and then two numbers of 1 digit each: + + ``` + X + \d+......\d+\d+ + (1234567)(8)(9)! + ``` + + The first number has 6 digits, and then a number of 3 digits: + + ``` + X + \d+.......\d+ + (123456)(789)! + ``` + + The first number has 6 digits, and then 2 numbers: + + ``` + X + \d+.....\d+ \d+ + (123456)(78)(9)! + ``` + + ...And so on. + + +There are many ways to split a set of digits `123456789` into numbers. To be precise, there are 2n-1, where `n` is the length of the set. + +For `n=20` there are about 1 million combinations, for `n=30` - a thousand times more. Trying each of them is exactly the reason why the search takes so long. + +What to do? + +Should we turn on the lazy mode? + +Unfortunately, that won't help: if we replace `pattern:\d+` with `pattern:\d+?`, the regexp will still hang. The order of combinations will change, but not their total count. + +Some regular expression engines have tricky tests and finite automations that allow to avoid going through all combinations or make it much faster, but not all engines, and not in all cases. + +## Back to words and strings + +The similar thing happens in our first example, when we look words by pattern `pattern:^(\w+\s?)*$` in the string `subject:An input that hangs!`. + +The reason is that a word can be represented as one `pattern:\w+` or many: + +``` +(input) +(inpu)(t) +(inp)(u)(t) +(in)(p)(ut) +... +``` + +For a human, it's obvious that there may be no match, because the string ends with an exclamation sign `!`, but the regular expression expects a wordly character `pattern:\w` or a space `pattern:\s` at the end. But the engine doesn't know that. + +It tries all combinations of how the regexp `pattern:(\w+\s?)*` can "consume" the string, including variants with spaces `pattern:(\w+\s)*` and without them `pattern:(\w+)*` (because spaces `pattern:\s?` are optional). As there are many such combinations, the search takes a lot of time. + +## How to fix? + +There are two main approaches to fixing the problem. + +The first is to lower the number of possible combinations. + +Let's rewrite the regular expression as `pattern:^(\w+\s)*\w*` - we'll look for any number of words followed by a space `pattern:(\w+\s)*`, and then (optionally) a word `pattern:\w*`. + +This regexp is equivalent to the previous one (matches the same) and works well: + +```js run +let reg = /^(\w+\s)*\w*$/; +let str = "An input string that takes a long time or even makes this regex to hang!"; + +alert( reg.test(str) ); // false +``` + +Why did the problem disappear? + +Now the star `pattern:*` goes after `pattern:\w+\s` instead of `pattern:\w+\s?`. It became impossible to represent one word of the string with multiple successive `pattern:\w+`. The time needed to try such combinations is now saved. + +For example, the previous pattern `pattern:(\w+\s?)*` could match the word `subject:string` as two `pattern:\w+`: + +```js run +\w+\w+ +string +``` + +The previous pattern, due to the optional `pattern:\s` allowed variants `pattern:\w+`, `pattern:\w+\s`, `pattern:\w+\w+` and so on. + +With the rewritten pattern `pattern:(\w+\s)*`, that's impossible: there may be `pattern:\w+\s` or `pattern:\w+\s\w+\s`, but not `pattern:\w+\w+`. So the overall combinations count is greatly decreased. + +## Preventing backtracking + +It's not always convenient to rewrite a regexp. And it's not always obvious how to do it. + +The alternative approach is to forbid backtracking for the quantifier. + +The regular expressions engine tries many combinations that are obviously wrong for a human. + +E.g. in the regexp `pattern:(\d+)*$` it's obvious for a human, that `pattern:+` shouldn't backtrack. If we replace one `pattern:\d+` with two separate `pattern:\d+\d+`, nothing changes: + +``` +\d+........ +(123456789)! + +\d+...\d+.... +(1234)(56789)! +``` + +And in the original example `pattern:^(\w+\s?)*$` we may want to forbid backtracking in `pattern:\w+`. That is: `pattern:\w+` should match a whole word, with the maximal possible length. There's no need to lower the repetitions count in `pattern:\w+`, try to split it into two words `pattern:\w+\w+` and so on. + +Modern regular expression engines support possessive quantifiers for that. They are like greedy ones, but don't backtrack (so they are actually simpler than regular quantifiers). + +There are also so-called "atomic capturing groups" - a way to disable backtracking inside parentheses. + +Unfortunately, in JavaScript they are not supported. But there's another way. + +### Lookahead to the rescue! + +We can prevent backtracking using lookahead. + +The pattern to take as much repetitions of `pattern:\w` as possible without backtracking is: `pattern:(?=(\w+))\1`. + +Let's decipher it: +- Lookahead `pattern:?=` looks forward for the longest word `pattern:\w+` starting at the current position. +- The contents of parentheses with `pattern:?=...` isn't memorized by the engine, so wrap `pattern:\w+` into parentheses. Then the engine will memorize their contents +- ...And allow us to reference it in the pattern as `pattern:\1`. + +That is: we look ahead - and if there's a word `pattern:\w+`, then match it as `pattern:\1`. + +Why? That's because the lookahead finds a word `pattern:\w+` as a whole and we capture it into the pattern with `pattern:\1`. So we essentially implemented a possessive plus `pattern:+` quantifier. It captures only the whole word `pattern:\w+`, not a part of it. + +For instance, in the word `subject:JavaScript` it may not only match `match:Java`, but leave out `match:Script` to match the rest of the pattern. + +Here's the comparison of two patterns: + +```js run +alert( "JavaScript".match(/\w+Script/)); // JavaScript +alert( "JavaScript".match(/(?=(\w+))\1Script/)); // null +``` + +1. In the first variant `pattern:\w+` first captures the whole word `subject:JavaScript` but then `pattern:+` backtracks character by character, to try to match the rest of the pattern, until it finally succeeds (when `pattern:\w+` matches `match:Java`). +2. In the second variant `pattern:(?=(\w+))` looks ahead and finds the word `subject:JavaScript`, that is included into the pattern as a whole by `pattern:\1`, so there remains no way to find `subject:Script` after it. + +We can put a more complex regular expression into `pattern:(?=(\w+))\1` instead of `pattern:\w`, when we need to forbid backtracking for `pattern:+` after it. + +```smart +There's more about the relation between possessive quantifiers and lookahead in articles [Regex: Emulate Atomic Grouping (and Possessive Quantifiers) with LookAhead](http://instanceof.me/post/52245507631/regex-emulate-atomic-grouping-with-lookahead) and [Mimicking Atomic Groups](http://blog.stevenlevithan.com/archives/mimic-atomic-groups). +``` + +Let's rewrite the first example using lookahead to prevent backtracking: + +```js run +let reg = /^((?=(\w+))\2\s?)*$/; + +alert( reg.test("A good string") ); // true + +let str = "An input string that takes a long time or even makes this regex to hang!"; + +alert( reg.test(str) ); // false, works and fast! +``` + +Here `pattern:\2` is used instead of `pattern:\1`, because there are additional outer parentheses. To avoid messing up with the numbers, we can give the parentheses a name, e.g. `pattern:(?\w+)`. + +```js run +// parentheses are named ?, referenced as \k +let reg = /^((?=(?\w+))\k\s?)*$/; + +let str = "An input string that takes a long time or even makes this regex to hang!"; + +alert( reg.test(str) ); // false + +alert( reg.test("A correct string") ); // true +``` + +The problem described in this article is called "catastrophic backtracking". + +We covered two ways how to solve it: +- Rewrite the regexp to lower the possible combinations count. +- Prevent backtracking. diff --git a/15-regexp-infinite-backtracking-problem/article.md b/15-regexp-infinite-backtracking-problem/article.md deleted file mode 100644 index 67f3e93c2..000000000 --- a/15-regexp-infinite-backtracking-problem/article.md +++ /dev/null @@ -1,297 +0,0 @@ -# Infinite backtracking problem - -Some regular expressions are looking simple, but can execute veeeeeery long time, and even "hang" the JavaScript engine. - -Sooner or later most developers occasionally face such behavior. - -The typical situation -- a regular expression works fine sometimes, but for certain strings it "hangs" consuming 100% of CPU. - -In a web-browser it kills the page. Not a good thing for sure. - -For server-side JavaScript it may become a vulnerability, and it uses regular expressions to process user data. Bad input will make the process hang, causing denial of service. The author personally saw and reported such vulnerabilities even for very well-known and widely used programs. - -So the problem is definitely worth to deal with. - -## Introduction - -The plan will be like this: - -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. - -For instance let's consider searching tags in HTML. - -We want to find all tags, with or without attributes -- like `subject:
`. We need the regexp to work reliably, because HTML comes from the internet and can be messy. - -In particular, we need it to match tags like `` -- with `<` and `>` in attributes. That's allowed by [HTML standard](https://html.spec.whatwg.org/multipage/syntax.html#syntax-attributes). - -A simple regexp like `pattern:<[^>]+>` doesn't work, because it stops at the first `>`, and we need to ignore `<>` if inside an attribute: - -```js run -// the match doesn't reach the end of the tag - wrong! -alert( ''.match(/<[^>]+>/) ); // `. - -That regexp is not perfect! It doesn't support all the details of HTML syntax, such as unquoted values, and there are other ways to improve, but let's not add complexity. It will demonstrate the problem for us. - -The regexp seems to work: - -```js run -let reg = /<\w+(\s*\w+="[^"]*"\s*)*>/g; - -let str='...... ...'; - -alert( str.match(reg) ); // , -``` - -Great! It found both the long tag `match:` and the short one `match:`. - -Now, that we've got a seemingly working solution, let's get to the infinite backtracking itself. - -## Infinite backtracking - -If you run our regexp on the input below, it may hang the browser (or another JavaScript host): - -```js run -let reg = /<\w+(\s*\w+="[^"]*"\s*)*>/g; - -let str = ``. - -Unfortunately, the regexp still hangs: - -```js run -// only search for space-delimited attributes -let reg = /<(\s*\w+=\w+\s*)*>/g; - -let str = `` in the string `subject:` at the end, so the match is impossible, but the regexp engine doesn't know about it. The search backtracks trying different combinations of `pattern:(\s*\w+=\w+\s*)`: - -``` -(a=b a=b a=b) (a=b) -(a=b a=b) (a=b a=b) -(a=b) (a=b a=b a=b) -... -``` - -As there are many combinations, it takes a lot of time. - -## How to fix? - -The backtracking checks many variants that are an obvious fail for a human. - -For instance, in the pattern `pattern:(\d+)*$` a human can easily see that `pattern:(\d+)*` does not need to backtrack `pattern:+`. There's no difference between one or two `\d+`: - -``` -\d+........ -(123456789)z - -\d+...\d+.... -(1234)(56789)z -``` - -Let's get back to more real-life example: `pattern:<(\s*\w+=\w+\s*)*>`. We want it to find pairs `name=value` (as many as it can). - -What we would like to do is to forbid backtracking. - -There's totally no need to decrease the number of repetitions. - -In other words, if it found three `name=value` pairs and then can't find `>` after them, then there's no need to decrease the count of repetitions. There are definitely no `>` after those two (we backtracked one `name=value` pair, it's there): - -``` -(name=value) name=value -``` - -Modern regexp engines support so-called "possessive" quantifiers for that. They are like greedy, but don't backtrack at all. Pretty simple, they capture whatever they can, and the search continues. There's also another tool called "atomic groups" that forbid backtracking inside parentheses. - -Unfortunately, but both these features are not supported by JavaScript. - -### Lookahead to the rescue - -We can forbid backtracking using lookahead. - -The pattern to take as much repetitions as possible without backtracking is: `pattern:(?=(a+))\1`. - -In other words: -- The lookahead `pattern:?=` looks for the maximal count `pattern:a+` from the current position. -- And then they are "consumed into the result" by the backreference `pattern:\1` (`pattern:\1` corresponds to the content of the second parentheses, that is `pattern:a+`). - -There will be no backtracking, because lookahead does not backtrack. If, for -example, it found 5 instances of `pattern:a+` and the further match failed, -it won't go back to the 4th instance. - -```smart -There's more about the relation between possessive quantifiers and lookahead in articles [Regex: Emulate Atomic Grouping (and Possessive Quantifiers) with LookAhead](http://instanceof.me/post/52245507631/regex-emulate-atomic-grouping-with-lookahead) and [Mimicking Atomic Groups](http://blog.stevenlevithan.com/archives/mimic-atomic-groups). -``` - -So this trick makes the problem disappear. - -Let's fix the regexp for a tag with attributes from the beginning of the chapter`pattern:<\w+(\s*\w+=(\w+|"[^"]*")\s*)*>`. We'll use lookahead to prevent backtracking of `name=value` pairs: - -```js run -// regexp to search name=value -let attrReg = /(\s*\w+=(\w+|"[^"]*")\s*)/ - -// use new RegExp to nicely insert its source into (?=(a+))\1 -let fixedReg = new RegExp(`<\\w+(?=(${attrReg.source}*))\\1>`, 'g'); - -let goodInput = '...... ...'; - -let badInput = `, -alert( badInput.match(fixedReg) ); // null (no results, fast!) -``` - -Great, it works! We found both a long tag `match:` and a small one `match:`, and (!) didn't hang the engine on the bad input. - -Please note the `attrReg.source` property. `RegExp` objects provide access to their source string in it. That's convenient when we want to insert one regexp into another. From d0c46d6c325a864cd4054ae47adbb64b43df9ac2 Mon Sep 17 00:00:00 2001 From: Ilya Kantor Date: Fri, 6 Sep 2019 16:48:59 +0300 Subject: [PATCH 062/101] WIP --- 01-regexp-introduction/article.md | 6 +- 02-regexp-character-classes/article.md | 22 +- 03-regexp-unicode/article.md | 4 +- 07-regexp-escaping/article.md | 8 +- 11-regexp-groups/article.md | 18 +- 16-regexp-sticky/article.md | 122 ++++++-- 17-regexp-methods/article.md | 412 ++++++++++--------------- 7 files changed, 280 insertions(+), 312 deletions(-) diff --git a/01-regexp-introduction/article.md b/01-regexp-introduction/article.md index 5cbe011ac..186991bac 100644 --- a/01-regexp-introduction/article.md +++ b/01-regexp-introduction/article.md @@ -81,7 +81,7 @@ It has 3 working modes: ```js run let str = "We will, we will rock you"; - alert( str.match(/we/gi) ); // We,we (an array of 2 matches) + alert( str.match(/we/gi) ); // We,we (an array of 2 substrings that match) ``` Please note that both `match:We` and `match:we` are found, because flag `pattern:i` makes the regular expression case-insensitive. @@ -159,9 +159,9 @@ The method `regexp.test(str)` looks for at least one match, if found, returns `t ```js run let str = "I love JavaScript"; -let reg = /LOVE/i; +let regexp = /LOVE/i; -alert( reg.test(str) ); // true +alert( regexp.test(str) ); // true ``` Further in this chapter we'll study more regular expressions, come across many other examples and also meet other methods. diff --git a/02-regexp-character-classes/article.md b/02-regexp-character-classes/article.md index 34240b6e9..b13272dbb 100644 --- a/02-regexp-character-classes/article.md +++ b/02-regexp-character-classes/article.md @@ -13,9 +13,9 @@ For instance, the let's find the first digit in the phone number: ```js run let str = "+7(903)-123-45-67"; -let reg = /\d/; +let regexp = /\d/; -alert( str.match(reg) ); // 7 +alert( str.match(regexp) ); // 7 ``` Without the flag `pattern:g`, the regular expression only looks for the first match, that is the first digit `pattern:\d`. @@ -25,12 +25,12 @@ Let's add the `pattern:g` flag to find all digits: ```js run let str = "+7(903)-123-45-67"; -let reg = /\d/g; +let regexp = /\d/g; -alert( str.match(reg) ); // array of matches: 7,9,0,3,1,2,3,4,5,6,7 +alert( str.match(regexp) ); // array of matches: 7,9,0,3,1,2,3,4,5,6,7 // let's make the digits-only phone number of them: -alert( str.match(reg).join('') ); // 79035419441 +alert( str.match(regexp).join('') ); // 79035419441 ``` That was a character class for digits. There are other character classes as well. @@ -54,9 +54,9 @@ For instance, `pattern:CSS\d` matches a string `match:CSS` with a digit after it ```js run let str = "Is there CSS4?"; -let reg = /CSS\d/ +let regexp = /CSS\d/ -alert( str.match(reg) ); // CSS4 +alert( str.match(regexp) ); // CSS4 ``` Also we can use many character classes: @@ -113,11 +113,11 @@ alert( "Z".match(/./) ); // Z Or in the middle of a regexp: ```js run -let reg = /CS.4/; +let regexp = /CS.4/; -alert( "CSS4".match(reg) ); // CSS4 -alert( "CS-4".match(reg) ); // CS-4 -alert( "CS 4".match(reg) ); // CS 4 (space is also a character) +alert( "CSS4".match(regexp) ); // CSS4 +alert( "CS-4".match(regexp) ); // CS-4 +alert( "CS 4".match(regexp) ); // CS 4 (space is also a character) ``` Please note that a dot means "any character", but not the "absense of a character". There must be a character to match it: diff --git a/03-regexp-unicode/article.md b/03-regexp-unicode/article.md index 7a14621b3..82c00d7df 100644 --- a/03-regexp-unicode/article.md +++ b/03-regexp-unicode/article.md @@ -118,9 +118,9 @@ For instance, let's look for hexadecimal numbers, written as `xFF`, where `F` is A hex digit can be denoted as `pattern:\p{Hex_Digit}`: ```js run -let reg = /x\p{Hex_Digit}\p{Hex_Digit}/u; +let regexp = /x\p{Hex_Digit}\p{Hex_Digit}/u; -alert("number: xAF".match(reg)); // xAF +alert("number: xAF".match(regexp)); // xAF ``` ### Example: Chinese hieroglyphs diff --git a/07-regexp-escaping/article.md b/07-regexp-escaping/article.md index 5169bd552..7bf989471 100644 --- a/07-regexp-escaping/article.md +++ b/07-regexp-escaping/article.md @@ -56,9 +56,9 @@ If we are creating a regular expression with `new RegExp`, then we don't have to For instance, consider this: ```js run -let reg = new RegExp("\d\.\d"); +let regexp = new RegExp("\d\.\d"); -alert( "Chapter 5.1".match(reg) ); // null +alert( "Chapter 5.1".match(regexp) ); // null ``` The similar search in one of previous examples worked with `pattern:/\d\.\d/`, but `new RegExp("\d\.\d")` doesn't work, why? @@ -87,9 +87,9 @@ let regStr = "\\d\\.\\d"; */!* alert(regStr); // \d\.\d (correct now) -let reg = new RegExp(regStr); +let regexp = new RegExp(regStr); -alert( "Chapter 5.1".match(reg) ); // 5.1 +alert( "Chapter 5.1".match(regexp) ); // 5.1 ``` ## Summary diff --git a/11-regexp-groups/article.md b/11-regexp-groups/article.md index 855568be7..8a4fdd719 100644 --- a/11-regexp-groups/article.md +++ b/11-regexp-groups/article.md @@ -200,7 +200,8 @@ let results = '

'.matchAll(/<(.*?)>/gi); // results - is not an array, but an iterable object alert(results); // [object RegExp String Iterator] -alert(results[0]); // undefined + +alert(results[0]); // undefined (*) results = Array.from(results); // let's turn it into array @@ -208,7 +209,7 @@ alert(results[0]); //

,h1 (1st tag) alert(results[1]); //

,h2 (2nd tag) ``` -As we can see, the first difference is very important. We can't get the match as `results[0]`, because that object isn't pseudoarray. We can turn it into a real `Array` using `Array.from`. There are more details about pseudoarrays and iterables in the article . +As we can see, the first difference is very important, as demonstrated in the line `(*)`. We can't get the match as `results[0]`, because that object isn't pseudoarray. We can turn it into a real `Array` using `Array.from`. There are more details about pseudoarrays and iterables in the article . There's no need in `Array.from` if we're looping over results: @@ -228,6 +229,19 @@ for(let result of results) { let [tag1, tag2] = '

'.matchAll(/<(.*?)>/gi); ``` +Every match, returned by `matchAll`, has the same format as returned by `match` without flag `pattern:g`: it's an array with additional properties `index` (match index in the string) and `input` (source string): + +```js run +let results = '

'.matchAll(/<(.*?)>/gi); + +let [tag1, tag2] = results; + +alert( tag1[0] ); //

+alert( tag1[1] ); // h1 +alert( tag1.index ); // 0 +alert( tag1.input ); //

+``` + ```smart header="Why is a result of `matchAll` an iterable object, not an array?" Why is the method designed like that? The reason is simple - for the optimization. diff --git a/16-regexp-sticky/article.md b/16-regexp-sticky/article.md index 60c784dc0..f3650c916 100644 --- a/16-regexp-sticky/article.md +++ b/16-regexp-sticky/article.md @@ -1,73 +1,127 @@ # Sticky flag "y", searching at position +The flag `pattern:y` allows to perform the search at the given position in the source string. + To grasp the use case of `pattern:y` flag, and see how great it is, let's explore a practical use case. -One of common tasks for regexps is "parsing": when we get a text and analyze it for logical components, build a structure. +One of common tasks for regexps is "lexical analysis": we get a text, e.g. in a programming language, and analyze it for structural elements. + +For instance, HTML has tags and attributes, JavaScript code has functions, variables, and so on. + +Writing lexical analyzers is a special area, with its own tools and algorithms, so we don't go deep in there, but there's a common task: to read something at the given position. + +E.g. we have a code string `subject:let varName = "value"`, and we need to read the variable name from it, that starts at position `4`. -For instance, there are HTML parsers for browser pages, that turn text into a structured document. There are parsers for programming languages, like JavaScript, etc. +We'll look for variable name using regexp `pattern:\w+`. Actually, JavaScript variable names need a bit more complex regexp for accurate matching, but here it doesn't matter. -Writing parsers is a special area, with its own tools and algorithms, so we don't go deep in there, but there's a very common question in them, and, generally, for text analysis: "What kind of entity is at the given position?". +A call to `str.match(/\w+/)` will find only the first word in the line. Or all words with the flag `pattern:g`. But we need only one word at position `4`. -For instance, for a programming language variants can be like: -- Is it a "name" `pattern:\w+`? -- Or is it a number `pattern:\d+`? -- Or an operator `pattern:[+-/*]`? -- (a syntax error if it's not anything in the expected list) +To search from the given position, we can use method `regexp.exec(str)`. -So, we should try to match a couple of regular expressions, and make a decision what's at the given position. +If the `regexp` doesn't have flags `pattern:g` or `pattern:y`, then this method looks for the first match in the string `str`, exactly like `str.match(regexp)`. Such simple no-flags case doesn't interest us here. -In JavaScript, how can we perform a search starting from a given position? Regular calls start searching from the text start. +If there's flag `pattern:g`, then it performs the search in the string `str`, starting from position stored in its `regexp.lastIndex` property. And, if it finds a match, then sets `regexp.lastIndex` to the index immediately after the match. -We'd like to avoid creating substrings, as this slows down the execution considerably. +When a regexp is created, its `lastIndex` is `0`. -One option is to use `regexp.exec` with `regexp.lastIndex` property, but that's not what we need, as this would search the text starting from `lastIndex`, while we only need to text the match *exactly* at the given position. +So, successive calls to `regexp.exec(str)` return matches one after another. -Here's a (failing) attempt to use `lastIndex`: +An example (with flag `pattern:g`): ```js run -let str = "(text before) function ..."; +let str = 'let varName'; -// attempting to find function at position 5: -let regexp = /function/g; // must use "g" flag, otherwise lastIndex is ignored -regexp.lastIndex = 5 +let regexp = /\w+/g; +alert(regexp.lastIndex); // 0 (initially lastIndex=0) -alert (regexp.exec(str)); // function +let word1 = regexp.exec(str); +alert(word1[0]); // let (1st word) +alert(regexp.lastIndex); // 3 (position after the match) + +let word2 = regexp.exec(str); +alert(word2[0]); // varName (2nd word) +alert(regexp.lastIndex); // 11 (position after the match) + +let word3 = regexp.exec(str); +alert(word3); // null (no more matches) +alert(regexp.lastIndex); // 0 (resets at search end) ``` -The match is found, because `regexp.exec` starts to search from the given position and goes on by the text, successfully matching "function" later. +Every match is returned as an array with groups and additional properties. -We could work around that by checking if "`regexp.exec(str).index` property is `5`, and if not, ignore the match. But the main problem here is performance. The regexp engine does a lot of unnecessary work by scanning at further positions. The delays are clearly noticeable if the text is long, because there are many such searches in a parser. +We can get all matches in the loop: -## The "y" flag +```js run +let str = 'let varName'; +let regexp = /\w+/g; -So we've came to the problem: how to search for a match exactly at the given position. +let result; -That's what `pattern:y` flag does. It makes the regexp search only at the `lastIndex` position. +while (result = regexp.exec(str)) { + alert( `Found ${result[0]} at position ${result.index}` ); + // Found let at position 0, then + // Found varName at position 4 +} +``` + +Such use of `regexp.exec` is an alternative to method `str.matchAll`. + +Unlike other methods, we can set our own `lastIndex`, to start the search from the given position. -Here's an example +For instance, let's find a word, starting from position `4`: ```js run -let str = "(text before) function ..."; +let str = 'let varName = "value"'; + +let regexp = /\w+/g; // without flag "g", property lastIndex is ignored *!* -let regexp = /function/y; -regexp.lastIndex = 5; +regexp.lastIndex = 4; */!* -alert (regexp.exec(str)); // null (no match, unlike "g" flag!) +let word = regexp.exec(str); +alert(word); // varName +``` + +We performed a search of `pattern:\w+`, starting from position `regexp.lastIndex = 4`. + +Please note: the search starts at position `lastIndex` and then goes further. If there's no word at position `lastIndex`, but it's somewhere after it, then it will be found: + +```js run +let str = 'let varName = "value"'; + +let regexp = /\w+/g; *!* -regexp.lastIndex = 14; +regexp.lastIndex = 3; */!* -alert (regexp.exec(str)); // function (match!) +let word = regexp.exec(str); +alert(word[0]); // varName +alert(word.index); // 4 ``` -As we can see, now the regexp is only matched at the given position. +...So, with flag `pattern:g` property `lastIndex` sets the starting position for the search. + +**Flag `pattern:y` makes `regexp.exec` to look exactly at position `lastIndex`, not before, not after it.** + +Here's the same search with flag `pattern:y`: + +```js run +let str = 'let varName = "value"'; + +let regexp = /\w+/y; + +regexp.lastIndex = 3; +alert( regexp.exec(str) ); // null (there's a space at position 3, not a word) + +regexp.lastIndex = 4; +alert( regexp.exec(str) ); // varName (word at position 4) +``` -So what `pattern:y` does is truly unique, and very important for writing parsers. +As we can see, regexp `pattern:/\w+/y` doesn't match at position `3` (unlike the flag `pattern:g`), but matches at position `4`. -The `pattern:y` flag allows to test a regular expression exactly at the given position and when we understand what's there, we can move on -- step by step examining the text. +Imagine, we have a long text, and there are no matches in it, at all. Then searching with flag `pattern:g` will go till the end of the text, and this will take significantly more time than the search with flag `pattern:y`. -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 `pattern:y` flag is exactly the right thing here. +In such tasks like lexical analysis, there are usually many searches at an exact position. Using flag `pattern:y` is the key for a good performance. diff --git a/17-regexp-methods/article.md b/17-regexp-methods/article.md index 1e78f5b58..1017eb06c 100644 --- a/17-regexp-methods/article.md +++ b/17-regexp-methods/article.md @@ -1,217 +1,122 @@ # Methods of RegExp and String -There are two sets of methods to deal with regular expressions. +In this article we'll cover various methods that work with regexps in-depth. -1. First, regular expressions are objects of the built-in [RegExp](mdn:js/RegExp) class, which provides many methods. -2. Additionally, there are methods in regular strings that can work with regexps. +## str.match(regexp) +The method `str.match(regexp)` finds matches for `regexp` in the string `str`. -## Recipes +It has 3 modes: -Which method to use depends on what we'd like to do. +1. If the `regexp` doesn't have flag `pattern:g`, then it returns the first match as an array with capturing groups and properties `index` (position of the match), `input` (input string, equals `str`): -Methods become much easier to understand if we separate them by their use in real-life tasks. + ```js run + let str = "I love JavaScript"; -So, here are general recipes, the details to follow: + let result = str.match(/Java(Script)/); -**To search for all matches:** + alert( result[0] ); // JavaScript (full match) + alert( result[1] ); // Script (first capturing group) + alert( result.length ); // 2 -Use regexp `pattern:g` flag and: -- Get a flat array of matches -- `str.match(reg)` -- Get an array or matches with details -- `str.matchAll(reg)`. + // Additional information: + alert( result.index ); // 0 (match position) + alert( result.input ); // I love JavaScript (source string) + ``` -**To search for the first match only:** -- Get the full first match -- `str.match(reg)` (without `pattern:g` flag). -- Get the string position of the first match -- `str.search(reg)`. -- Check if there's a match -- `regexp.test(str)`. -- Find the match from the given position -- `regexp.exec(str)` (set `regexp.lastIndex` to position). +2. If the `regexp` has flag `pattern:g`, then it returns an array of all matches as strings, without capturing groups and other details. + ```js run + let str = "I love JavaScript"; -**To replace all matches:** -- Replace with another string or a function result -- `str.replace(reg, str|func)` + let result = str.match(/Java(Script)/g); -**To split the string by a separator:** -- `str.split(str|reg)` + alert( result[0] ); // JavaScript + alert( result.length ); // 1 + ``` -Now you can continue reading this chapter to get the details about every method... But if you're reading for the first time, then you probably want to know more about regexps. So you can move to the next chapter, and then return here if something about a method is unclear. +3. If there are no matches, no matter if there's flag `pattern:g` or not, `null` is returned. -## str.search(reg) + That's an important nuance. If there are no matches, we don't get an empty array, but `null`. It's easy to make a mistake forgetting about it, e.g.: -We've seen this method already. It returns the position of the first match or `-1` if none found: + ```js run + let str = "I love JavaScript"; -```js run -let str = "A drop of ink may make a million think"; - -alert( str.search( *!*/a/i*/!* ) ); // 0 (first match at zero position) -``` - -**The important limitation: `search` only finds the first match.** - -We can't find next matches using `search`, there's just no syntax for that. But there are other methods that can. - -## str.match(reg), no "g" flag - -The behavior of `str.match` varies depending on whether `reg` has `pattern:g` flag or not. - -First, if there's no `pattern:g` flag, then `str.match(reg)` looks for the first match only. - -The result is an array with that match and additional properties: - -- `index` -- the position of the match inside the string, -- `input` -- the subject string. - -For instance: - -```js run -let str = "Fame is the thirst of youth"; + let result = str.match(/HTML/); -let result = str.match( *!*/fame/i*/!* ); + alert(result); // null + alert(result.length); // Error: Cannot read property 'length' of null + ``` -alert( result[0] ); // Fame (the match) -alert( result.index ); // 0 (at the zero position) -alert( result.input ); // "Fame is the thirst of youth" (the string) -``` - -A match result may have more than one element. - -**If a part of the pattern is delimited by parentheses `(...)`, then it becomes a separate element in the array.** - -If parentheses have a name, designated by `(?...)` at their start, then `result.groups[name]` has the content. We'll see that later in the chapter [about groups](info:regexp-groups). - -For instance: - -```js run -let str = "JavaScript is a programming language"; + If we want the result to be an array, we can write like this: -let result = str.match( *!*/JAVA(SCRIPT)/i*/!* ); + ```js + let result = str.match(regexp) || []; + ``` -alert( result[0] ); // JavaScript (the whole match) -alert( result[1] ); // script (the part of the match that corresponds to the parentheses) -alert( result.index ); // 0 -alert( result.input ); // JavaScript is a programming language -``` - -Due to the `pattern:i` flag the search is case-insensitive, so it finds `match:JavaScript`. The part of the match that corresponds to `pattern:SCRIPT` becomes a separate array item. - -So, this method is used to find one full match with all details. - - -## str.match(reg) with "g" flag +## str.matchAll(regexp) -When there's a `"g"` flag, then `str.match` returns an array of all matches. There are no additional properties in that array, and parentheses do not create any elements. +[recent browser="new"] -For instance: +The method `str.matchAll(regexp)` is a "newer, improved" variant of `str.match`. -```js run -let str = "HO-Ho-ho!"; +It's used mainly to search for all matches with all groups. -let result = str.match( *!*/ho/ig*/!* ); +There are 3 differences from `match`: -alert( result ); // HO, Ho, ho (array of 3 matches, case-insensitive) -``` +1. It returns an iterable object with matches instead of an array. We can make a regular array from it using `Array.from`. +2. Every match is returned as an array with capturing groups (the same format as `str.match` without flag `pattern:g`). +3. If there are no results, it returns not `null`, but an empty iterable object. -Parentheses do not change anything, here we go: +Usage example: ```js run -let str = "HO-Ho-ho!"; - -let result = str.match( *!*/h(o)/ig*/!* ); - -alert( result ); // HO, Ho, ho -``` - -**So, with `pattern:g` flag `str.match` returns a simple array of all matches, without details.** +let str = '

Hello, world!

'; +let reg = /<(.*?)>/g; -If we want to get information about match positions and contents of parentheses then we should use `matchAll` method that we'll cover below. +let matchAll = str.matchAll(reg); -````warn header="If there are no matches, `str.match` returns `null`" -Please note, that's important. If there are no matches, the result is not an empty array, but `null`. +alert(matchAll); // [object RegExp String Iterator], not array, but an iterable -Keep that in mind to evade pitfalls like this: - -```js run -let str = "Hey-hey-hey!"; +matchAll = Array.from(matchAll); // array now -alert( str.match(/Z/g).length ); // Error: Cannot read property 'length' of null +let firstMatch = matchAll[0]; +alert( firstMatch[0] ); //

+alert( firstMatch[1] ); // h1 +alert( firstMatch.index ); // 0 +alert( firstMatch.input ); //

Hello, world!

``` -Here `str.match(/Z/g)` is `null`, it has no `length` property. -```` +If we use `for..of` to loop over `matchAll` matches, then we don't need `Array.from`, разумеется, не нужен. -## str.matchAll(regexp) +## str.split(regexp|substr, limit) -The method `str.matchAll(regexp)` is used to find all matches with all details. +Splits the string using the regexp (or a substring) as a delimiter. -For instance: +We can use `split` with strings, like this: ```js run -let str = "Javascript or JavaScript? Should we uppercase 'S'?"; - -let result = str.matchAll( *!*/java(script)/ig*/!* ); - -let [match1, match2] = result; - -alert( match1[0] ); // Javascript (the whole match) -alert( match1[1] ); // script (the part of the match that corresponds to the parentheses) -alert( match1.index ); // 0 -alert( match1.input ); // = str (the whole original string) - -alert( match2[0] ); // JavaScript (the whole match) -alert( match2[1] ); // Script (the part of the match that corresponds to the parentheses) -alert( match2.index ); // 14 -alert( match2.input ); // = str (the whole original string) +alert('12-34-56'.split('-')) // array of [12, 34, 56] ``` -````warn header="`matchAll` returns an iterable, not array" -For instance, if we try to get the first match by index, it won't work: +But we can split by a regular expression, the same way: ```js run -let str = "Javascript or JavaScript??"; - -let result = str.matchAll( /javascript/ig ); - -*!* -alert(result[0]); // undefined (?! there must be a match) -*/!* +alert('12, 34, 56'.split(/,\s*/)) // array of [12, 34, 56] ``` -The reason is that the iterator is not an array. We need to run `Array.from(result)` on it, or use `for..of` loop to get matches. +## str.search(regexp) -In practice, if we need all matches, then `for..of` works, so it's not a problem. - -And, to get only few matches, we can use destructuring: +The method `str.search(regexp)` returns the position of the first match or `-1` if none found: ```js run -let str = "Javascript or JavaScript??"; - -*!* -let [firstMatch] = str.matchAll( /javascript/ig ); -*/!* - -alert(firstMatch); // Javascript -``` -```` - -```warn header="`matchAll` is supernew, may need a polyfill" -The method may not work in old browsers. A polyfill might be needed (this site uses core-js). - -Or you could make a loop with `regexp.exec`, explained below. -``` - -## str.split(regexp|substr, limit) - -Splits the string using the regexp (or a substring) as a delimiter. - -We already used `split` with strings, like this: +let str = "A drop of ink may make a million think"; -```js run -alert('12-34-56'.split('-')) // array of [12, 34, 56] +alert( str.search( /ink/i ) ); // 10 (first match position) ``` -But we can split by a regular expression, the same way: +**The important limitation: `search` only finds the first match.** -```js run -alert('12-34-56'.split(/-/)) // array of [12, 34, 56] -``` +If we need positions of further matches, we should use other means, such as finding them all with `str.matchAll(regexp)`. ## str.replace(str|reg, str|func) @@ -226,11 +131,11 @@ alert('12-34-56'.replace("-", ":")) // 12:34-56 There's a pitfall though. -**When the first argument of `replace` is a string, it only looks for the first match.** +**When the first argument of `replace` is a string, it only replaces the first match.** You can see that in the example above: only the first `"-"` is replaced by `":"`. -To find all dashes, we need to use not the string `"-"`, but a regexp `pattern:/-/g`, with an obligatory `pattern:g` flag: +To find all hyphens, we need to use not the string `"-"`, but a regexp `pattern:/-/g`, with the obligatory `pattern:g` flag: ```js run // replace all dashes by a colon @@ -239,31 +144,15 @@ alert( '12-34-56'.replace( *!*/-/g*/!*, ":" ) ) // 12:34:56 The second argument is a replacement string. We can use special character in it: -| Symbol | Inserts | +| Symbols | Action in the replacement string | +| Symbols | Action in the replacement string | |--------|--------| -|`$$`|`"$"` | -|`$&`|the whole match| -|$`|a part of the string before the match| -|`$'`|a part of the string after the match| -|`$n`|if `n` is a 1-2 digit number, then it means the contents of n-th parentheses counting from left to right, otherwise it means a parentheses with the given name | - - -For instance if we use `$&` in the replacement string, that means "put the whole match here". - -Let's use it to prepend all entries of `"John"` with `"Mr."`: - -```js run -let str = "John Doe, John Smith and John Bull"; - -// for each John - replace it with Mr. and then John -alert(str.replace(/John/g, 'Mr.$&')); // Mr.John Doe, Mr.John Smith and Mr.John Bull -``` - -Quite often we'd like to reuse parts of the source string, recombine them in the replacement or wrap into something. - -To do so, we should: -1. First, mark the parts by parentheses in regexp. -2. Use `$1`, `$2` (and so on) in the replacement string to get the content matched by 1st, 2nd and so on parentheses. +|`$&`|inserts the whole match| +|$`|inserts a part of the string before the match| +|`$'`|inserts a part of the string after the match| +|`$n`|if `n` is a 1-2 digit number, inserts the contents of n-th capturing group, for details see [](info:regexp-groups)| +|`$`|inserts the contents of the parentheses with the given `name`, for details see [](info:regexp-groups)| +|`$$`|inserts character `$` | For instance: @@ -276,126 +165,137 @@ alert(str.replace(/(john) (smith)/i, '$2, $1')) // Smith, John **For situations that require "smart" replacements, the second argument can be a function.** -It will be called for each match, and its result will be inserted as a replacement. +It will be called for each match, and the returned value will be inserted as a replacement. -For instance: +The function is called with arguments `func(match, p1, p2, ..., pn, offset, input, groups)`: + +1. `match` -- the match, +2. `p1, p2, ..., pn` -- contents of capturing groups (if there are any), +3. `offset` -- position of the match, +4. `input` -- the source string, +5. `groups` -- an object with named groups. + +If there are no parentheses in the regexp, then there are only 3 arguments: `func(str, offset, input)`. + +For example, let's uppercase all matches: ```js run -let i = 0; +let str = "html and css"; + +let result = str.replace(/html|css/gi, str => str.toUpperCase()); -// replace each "ho" by the result of the function -alert("HO-Ho-ho".replace(/ho/gi, function() { - return ++i; -})); // 1-2-3 +alert(result); // HTML and CSS ``` -In the example above the function just returns the next number every time, but usually the result is based on the match. +Replace each match by its position in the string: -The function is called with arguments `func(str, p1, p2, ..., pn, offset, input, groups)`: +```js run +alert("Ho-Ho-ho".replace(/ho/gi, (match, offset) => offset)); // 0-3-6 +``` -1. `str` -- the match, -2. `p1, p2, ..., pn` -- contents of parentheses (if there are any), -3. `offset` -- position of the match, -4. `input` -- the source string, -5. `groups` -- an object with named groups (see chapter [](info:regexp-groups)). +In the example below there are two parentheses, so the replacement function is called with 5 arguments: the first is the full match, then 2 parentheses, and after it (not used in the example) the match position and the source string: -If there are no parentheses in the regexp, then there are only 3 arguments: `func(str, offset, input)`. +```js run +let str = "John Smith"; + +let result = str.replace(/(\w+) (\w+)/, (match, name, surname) => `${surname}, ${name}`); + +alert(result); // Smith, John +``` + +If there are many groups, it's convenient to use rest parameters to access them: -Let's use it to show full information about matches: + +Если в регулярном выражении много скобочных групп, то бывает удобно использовать остаточные аргументы для обращения к ним: ```js run -// show and replace all matches -function replacer(str, offset, input) { - alert(`Found ${str} at position ${offset} in string ${input}`); - return str.toLowerCase(); -} +let str = "John Smith"; -let result = "HO-Ho-ho".replace(/ho/gi, replacer); -alert( 'Result: ' + result ); // Result: ho-ho-ho +let result = str.replace(/(\w+) (\w+)/, (...match) => `${match[2]}, ${match[1]}`); -// shows each match: -// Found HO at position 0 in string HO-Ho-ho -// Found Ho at position 3 in string HO-Ho-ho -// Found ho at position 6 in string HO-Ho-ho +alert(result); // Smith, John ``` -In the example below there are two parentheses, so `replacer` is called with 5 arguments: `str` is the full match, then parentheses, and then `offset` and `input`: +Or, if we're using named groups, then `groups` object with them is always the last, so we can obtain it like this: ```js run -function replacer(str, name, surname, offset, input) { - // name is the first parentheses, surname is the second one - return surname + ", " + name; -} - let str = "John Smith"; -alert(str.replace(/(John) (Smith)/, replacer)) // Smith, John +let result = str.replace(/(?\w+) (?\w+)/, (...match) => { + let groups = match.pop(); + + return `${groups.surname}, ${groups.name}`; +}); + +alert(result); // Smith, John ``` Using a function gives us the ultimate replacement power, because it gets all the information about the match, has access to outer variables and can do everything. ## regexp.exec(str) -We've already seen these searching methods: +The method `regexp.exec(str)` method returns a match for `regexp` in the string `str`. Unlike previous methods, it's called on a regexp, not on a string. -- `search` -- looks for the position of the match, -- `match` -- if there's no `pattern:g` flag, returns the first match with parentheses and all details, -- `match` -- if there's a `pattern:g` flag -- returns all matches, without details parentheses, -- `matchAll` -- returns all matches with details. +It behaves differently depending on whether the regexp has flag `pattern:g`. -The `regexp.exec` method is the most flexible searching method of all. Unlike previous methods, `exec` should be called on a regexp, rather than on a string. +If there's no `pattern:g`, then `regexp.exec(str)` returns the first match exactly as `str.match(reg)`. This behavior doesn't bring anything new. -It behaves differently depending on whether the regexp has the `pattern:g` flag. +But if there's flag `pattern:g`, then: +- A call to `regexp.exec(str)` returns the first match and saves the position immediately after it in the property `regexp.lastIndex`. +- The next such call starts the search from position `regexp.lastIndex`, returns the next match and saves the position after it in `regexp.lastIndex`. +- ...And so on. +- If there are no matches, `regexp.exec` returns `null` and resets `regexp.lastIndex` to `0`. -If there's no `pattern:g`, then `regexp.exec(str)` returns the first match, exactly as `str.match(reg)`. Such behavior does not give us anything new. +So, repeated calls return all matches one after another, using property `regexp.lastIndex` to keep track of the current search position. -But if there's `pattern:g`, then: -- `regexp.exec(str)` returns the first match and *remembers* the position after it in `regexp.lastIndex` property. -- The next call starts to search from `regexp.lastIndex` and returns the next match. -- If there are no more matches then `regexp.exec` returns `null` and `regexp.lastIndex` is set to `0`. - -We could use it to get all matches with their positions and parentheses groups in a loop, instead of `matchAll`: +In the past, before the method `str.matchAll` was added to JavaScript, calls of `regexp.exec` were used in the loop to get all matches with groups: ```js run -let str = 'A lot about JavaScript at https://javascript.info'; - +let str = 'More about JavaScript at https://javascript.info'; let regexp = /javascript/ig; let result; while (result = regexp.exec(str)) { - alert( `Found ${result[0]} at ${result.index}` ); - // shows: Found JavaScript at 12, then: - // shows: Found javascript at 34 + alert( `Found ${result[0]} at position ${result.index}` ); + // Found JavaScript at position 11, then + // Found javascript at position 33 } ``` -Surely, `matchAll` does the same, at least for modern browsers. But what `matchAll` can't do -- is to search from a given position. +This works now as well, although for newer browsers `str.matchAll` is usually more convenient. -Let's search from position `13`. What we need is to assign `regexp.lastIndex=13` and call `regexp.exec`: +**We can use `regexp.exec` to search from a given position by manually setting `lastIndex`.** -```js run -let str = "A lot about JavaScript at https://javascript.info"; +For instance: -let regexp = /javascript/ig; -*!* -regexp.lastIndex = 13; -*/!* +```js run +let str = 'Hello, world!'; -let result; +let reg = /\w+/g; // without flag "g", lastIndex property is ignored +regexp.lastIndex = 5; // search from 5th position (from the comma) -while (result = regexp.exec(str)) { - alert( `Found ${result[0]} at ${result.index}` ); - // shows: Found javascript at 34 -} +alert( regexp.exec(str) ); // world ``` -Now, starting from the given position `13`, there's only one match. +If the regexp has flag `pattern:y`, then the search will be performed exactly at the position `regexp.lastIndex`, not any further. + +Let's replace flag `pattern:g` with `pattern:y` in the example above. There will be no matches, as there's no word at position `5`: + +```js run +let str = 'Hello, world!'; + +let reg = /\w+/y; +regexp.lastIndex = 5; // search exactly at position 5 + +alert( regexp.exec(str) ); // null +``` +That's convenient for situations when we need to "read" something from the string by a regexp at the exact position, not somewhere further. ## regexp.test(str) -The method `regexp.test(str)` looks for a match and returns `true/false` whether it finds it. +The method `regexp.test(str)` looks for a match and returns `true/false` whether it exists. For instance: @@ -416,7 +316,7 @@ alert( *!*/love/i*/!*.test(str) ); // false alert( str.search(*!*/love/i*/!*) != -1 ); // false ``` -If the regexp has `'g'` flag, then `regexp.test` advances `regexp.lastIndex` property, just like `regexp.exec`. +If the regexp has flag `pattern:g`, then `regexp.test` looks from `regexp.lastIndex` property and updates it, just like `regexp.exec`. So we can use it to search from a given position: From 33807c9e538c7d8d9c5fc31a7026f669a987f616 Mon Sep 17 00:00:00 2001 From: Ilya Kantor Date: Fri, 6 Sep 2019 16:50:41 +0300 Subject: [PATCH 063/101] reg->regexp --- .../2-find-time-2-formats/solution.md | 4 +-- .../2-find-time-2-formats/task.md | 4 +-- .../article.md | 8 ++--- .../1-find-text-manydots/solution.md | 4 +-- .../1-find-text-manydots/task.md | 4 +-- .../2-find-html-colors-6hex/solution.md | 4 +-- .../2-find-html-colors-6hex/task.md | 4 +-- .../3-find-html-comments/solution.md | 4 +-- .../3-find-html-comments/task.md | 4 +-- .../4-find-html-tags-greedy-lazy/solution.md | 4 +-- .../4-find-html-tags-greedy-lazy/task.md | 4 +-- 10-regexp-greedy-and-lazy/article.md | 34 +++++++++---------- 11-regexp-groups/01-test-mac/solution.md | 10 +++--- 11-regexp-groups/01-test-mac/task.md | 10 +++--- .../02-find-webcolor-3-or-6/solution.md | 8 ++--- .../02-find-webcolor-3-or-6/task.md | 4 +-- .../03-find-decimal-numbers/solution.md | 4 +-- .../03-find-decimal-numbers/task.md | 4 +-- .../04-parse-expression/solution.md | 8 ++--- 11-regexp-groups/article.md | 12 +++---- 12-regexp-backreferences/article.md | 12 +++---- .../01-find-programming-language/solution.md | 8 ++--- .../01-find-programming-language/task.md | 4 +-- .../02-find-matching-bbtags/solution.md | 4 +-- .../02-find-matching-bbtags/task.md | 8 ++--- .../03-match-quoted-string/solution.md | 4 +-- .../04-match-exact-tag/solution.md | 4 +-- .../04-match-exact-tag/task.md | 4 +-- 13-regexp-alternation/article.md | 8 ++--- .../1-find-non-negative-integers/solution.md | 8 ++--- .../1-find-non-negative-integers/task.md | 4 +-- .../2-insert-after-head/task.md | 4 +-- 14-regexp-lookahead-lookbehind/article.md | 8 ++--- .../article.md | 30 ++++++++-------- 17-regexp-methods/article.md | 12 +++---- 35 files changed, 132 insertions(+), 132 deletions(-) diff --git a/08-regexp-character-sets-and-ranges/2-find-time-2-formats/solution.md b/08-regexp-character-sets-and-ranges/2-find-time-2-formats/solution.md index 91568d033..69ade1b19 100644 --- a/08-regexp-character-sets-and-ranges/2-find-time-2-formats/solution.md +++ b/08-regexp-character-sets-and-ranges/2-find-time-2-formats/solution.md @@ -1,8 +1,8 @@ Answer: `pattern:\d\d[-:]\d\d`. ```js run -let reg = /\d\d[-:]\d\d/g; -alert( "Breakfast at 09:00. Dinner at 21-30".match(reg) ); // 09:00, 21-30 +let regexp = /\d\d[-:]\d\d/g; +alert( "Breakfast at 09:00. Dinner at 21-30".match(regexp) ); // 09:00, 21-30 ``` Please note that the dash `pattern:'-'` has a special meaning in square brackets, but only between other characters, not when it's in the beginning or at the end, so we don't need to escape it. diff --git a/08-regexp-character-sets-and-ranges/2-find-time-2-formats/task.md b/08-regexp-character-sets-and-ranges/2-find-time-2-formats/task.md index 868115bdf..c8441caf4 100644 --- a/08-regexp-character-sets-and-ranges/2-find-time-2-formats/task.md +++ b/08-regexp-character-sets-and-ranges/2-find-time-2-formats/task.md @@ -5,8 +5,8 @@ The time can be in the format `hours:minutes` or `hours-minutes`. Both hours and Write a regexp to find time: ```js -let reg = /your regexp/g; -alert( "Breakfast at 09:00. Dinner at 21-30".match(reg) ); // 09:00, 21-30 +let regexp = /your regexp/g; +alert( "Breakfast at 09:00. Dinner at 21-30".match(regexp) ); // 09:00, 21-30 ``` P.S. In this task we assume that the time is always correct, there's no need to filter out bad strings like "45:67". Later we'll deal with that too. diff --git a/08-regexp-character-sets-and-ranges/article.md b/08-regexp-character-sets-and-ranges/article.md index 6beca62be..cb6a27e9d 100644 --- a/08-regexp-character-sets-and-ranges/article.md +++ b/08-regexp-character-sets-and-ranges/article.md @@ -130,18 +130,18 @@ In the example below the regexp `pattern:[-().^+]` looks for one of the characte ```js run // No need to escape -let reg = /[-().^+]/g; +let regexp = /[-().^+]/g; -alert( "1 + 2 - 3".match(reg) ); // Matches +, - +alert( "1 + 2 - 3".match(regexp) ); // Matches +, - ``` ...But if you decide to escape them "just in case", then there would be no harm: ```js run // Escaped everything -let reg = /[\-\(\)\.\^\+]/g; +let regexp = /[\-\(\)\.\^\+]/g; -alert( "1 + 2 - 3".match(reg) ); // also works: +, - +alert( "1 + 2 - 3".match(regexp) ); // also works: +, - ``` ## Ranges and flag "u" diff --git a/09-regexp-quantifiers/1-find-text-manydots/solution.md b/09-regexp-quantifiers/1-find-text-manydots/solution.md index d4ddb1369..21b8762ec 100644 --- a/09-regexp-quantifiers/1-find-text-manydots/solution.md +++ b/09-regexp-quantifiers/1-find-text-manydots/solution.md @@ -2,8 +2,8 @@ Solution: ```js run -let reg = /\.{3,}/g; -alert( "Hello!... How goes?.....".match(reg) ); // ..., ..... +let regexp = /\.{3,}/g; +alert( "Hello!... How goes?.....".match(regexp) ); // ..., ..... ``` Please note that the dot is a special character, so we have to escape it and insert as `\.`. diff --git a/09-regexp-quantifiers/1-find-text-manydots/task.md b/09-regexp-quantifiers/1-find-text-manydots/task.md index 6fd91bdcf..4140b4a98 100644 --- a/09-regexp-quantifiers/1-find-text-manydots/task.md +++ b/09-regexp-quantifiers/1-find-text-manydots/task.md @@ -9,6 +9,6 @@ Create a regexp to find ellipsis: 3 (or more?) dots in a row. Check it: ```js -let reg = /your regexp/g; -alert( "Hello!... How goes?.....".match(reg) ); // ..., ..... +let regexp = /your regexp/g; +alert( "Hello!... How goes?.....".match(regexp) ); // ..., ..... ``` diff --git a/09-regexp-quantifiers/2-find-html-colors-6hex/solution.md b/09-regexp-quantifiers/2-find-html-colors-6hex/solution.md index d4d297a1d..b9e1f85a5 100644 --- a/09-regexp-quantifiers/2-find-html-colors-6hex/solution.md +++ b/09-regexp-quantifiers/2-find-html-colors-6hex/solution.md @@ -7,11 +7,11 @@ Then we can look for 6 of them using the quantifier `pattern:{6}`. As a result, we have the regexp: `pattern:/#[a-f0-9]{6}/gi`. ```js run -let reg = /#[a-f0-9]{6}/gi; +let regexp = /#[a-f0-9]{6}/gi; let str = "color:#121212; background-color:#AA00ef bad-colors:f#fddee #fd2" -alert( str.match(reg) ); // #121212,#AA00ef +alert( str.match(regexp) ); // #121212,#AA00ef ``` The problem is that it finds the color in longer sequences: diff --git a/09-regexp-quantifiers/2-find-html-colors-6hex/task.md b/09-regexp-quantifiers/2-find-html-colors-6hex/task.md index 80ed625fc..9a1923a7e 100644 --- a/09-regexp-quantifiers/2-find-html-colors-6hex/task.md +++ b/09-regexp-quantifiers/2-find-html-colors-6hex/task.md @@ -5,11 +5,11 @@ Create a regexp to search HTML-colors written as `#ABCDEF`: first `#` and then 6 An example of use: ```js -let reg = /...your regexp.../ +let regexp = /...your regexp.../ let str = "color:#121212; background-color:#AA00ef bad-colors:f#fddee #fd2 #12345678"; -alert( str.match(reg) ) // #121212,#AA00ef +alert( str.match(regexp) ) // #121212,#AA00ef ``` P.S. In this task we do not need other color formats like `#123` or `rgb(1,2,3)` etc. diff --git a/10-regexp-greedy-and-lazy/3-find-html-comments/solution.md b/10-regexp-greedy-and-lazy/3-find-html-comments/solution.md index b3290607c..0244963d1 100644 --- a/10-regexp-greedy-and-lazy/3-find-html-comments/solution.md +++ b/10-regexp-greedy-and-lazy/3-find-html-comments/solution.md @@ -5,11 +5,11 @@ An acceptable variant is `pattern:` -- the lazy quantifier makes the d Otherwise multiline comments won't be found: ```js run -let reg = //gs; +let regexp = //gs; let str = `... .. .. `; -alert( str.match(reg) ); // '', '' +alert( str.match(regexp) ); // '', '' ``` diff --git a/10-regexp-greedy-and-lazy/3-find-html-comments/task.md b/10-regexp-greedy-and-lazy/3-find-html-comments/task.md index 81fd5c634..551d9c725 100644 --- a/10-regexp-greedy-and-lazy/3-find-html-comments/task.md +++ b/10-regexp-greedy-and-lazy/3-find-html-comments/task.md @@ -3,11 +3,11 @@ Find all HTML comments in the text: ```js -let reg = /your regexp/g; +let regexp = /your regexp/g; let str = `... .. .. `; -alert( str.match(reg) ); // '', '' +alert( str.match(regexp) ); // '', '' ``` diff --git a/10-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/solution.md b/10-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/solution.md index c453926fa..b4d9f7496 100644 --- a/10-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/solution.md +++ b/10-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/solution.md @@ -2,9 +2,9 @@ The solution is `pattern:<[^<>]+>`. ```js run -let reg = /<[^<>]+>/g; +let regexp = /<[^<>]+>/g; let str = '<>
'; -alert( str.match(reg) ); // '', '', '' +alert( str.match(regexp) ); // '', '', '' ``` diff --git a/10-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/task.md b/10-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/task.md index e3c39c373..8e96c921d 100644 --- a/10-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/task.md +++ b/10-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/task.md @@ -5,11 +5,11 @@ Create a regular expression to find all (opening and closing) HTML tags with the An example of use: ```js run -let reg = /your regexp/g; +let regexp = /your regexp/g; let str = '<> '; -alert( str.match(reg) ); // '', '', '' +alert( str.match(regexp) ); // '', '', '' ``` Here we assume that tag attributes may not contain `<` and `>` (inside squotes too), that simplifies things a bit. diff --git a/10-regexp-greedy-and-lazy/article.md b/10-regexp-greedy-and-lazy/article.md index e014c16d0..4298e7c80 100644 --- a/10-regexp-greedy-and-lazy/article.md +++ b/10-regexp-greedy-and-lazy/article.md @@ -17,11 +17,11 @@ A regular expression like `pattern:/".+"/g` (a quote, then something, then the o Let's try it: ```js run -let reg = /".+"/g; +let regexp = /".+"/g; let str = 'a "witch" and her "broom" is one'; -alert( str.match(reg) ); // "witch" and her "broom" +alert( str.match(regexp) ); // "witch" and her "broom" ``` ...We can see that it works not as intended! @@ -105,11 +105,11 @@ To make things clear: usually a question mark `pattern:?` is a quantifier by its The regexp `pattern:/".+?"/g` works as intended: it finds `match:"witch"` and `match:"broom"`: ```js run -let reg = /".+?"/g; +let regexp = /".+?"/g; let str = 'a "witch" and her "broom" is one'; -alert( str.match(reg) ); // witch, broom +alert( str.match(regexp) ); // witch, broom ``` To clearly understand the change, let's trace the search step by step. @@ -175,11 +175,11 @@ With regexps, there's often more than one way to do the same thing. In our case we can find quoted strings without lazy mode using the regexp `pattern:"[^"]+"`: ```js run -let reg = /"[^"]+"/g; +let regexp = /"[^"]+"/g; let str = 'a "witch" and her "broom" is one'; -alert( str.match(reg) ); // witch, broom +alert( str.match(regexp) ); // witch, broom ``` The regexp `pattern:"[^"]+"` gives correct results, because it looks for a quote `pattern:'"'` followed by one or more non-quotes `pattern:[^"]`, and then the closing quote. @@ -201,20 +201,20 @@ The first idea might be: `pattern://g`. Let's check it: ```js run let str = '......'; -let reg = //g; +let regexp = //g; // Works! -alert( str.match(reg) ); // +alert( str.match(regexp) ); // ``` It worked. But let's see what happens if there are many links in the text? ```js run let str = '...... ...'; -let reg = //g; +let regexp = //g; // Whoops! Two links in one match! -alert( str.match(reg) ); // ... +alert( str.match(regexp) ); // ... ``` Now the result is wrong for the same reason as our "witches" example. The quantifier `pattern:.*` took too many characters. @@ -230,10 +230,10 @@ Let's modify the pattern by making the quantifier `pattern:.*?` lazy: ```js run let str = '...... ...'; -let reg = //g; +let regexp = //g; // Works! -alert( str.match(reg) ); // , +alert( str.match(regexp) ); // , ``` Now it seems to work, there are two matches: @@ -247,10 +247,10 @@ Now it seems to work, there are two matches: ```js run let str = '......

...'; -let reg = //g; +let regexp = //g; // Wrong match! -alert( str.match(reg) ); // ...

+alert( str.match(regexp) ); // ...

``` Now it fails. The match includes not just a link, but also a lot of text after it, including ``. @@ -281,11 +281,11 @@ A working example: ```js run let str1 = '......

...'; let str2 = '...... ...'; -let reg = //g; +let regexp = //g; // Works! -alert( str1.match(reg) ); // null, no matches, that's correct -alert( str2.match(reg) ); // , +alert( str1.match(regexp) ); // null, no matches, that's correct +alert( str2.match(regexp) ); // , ``` ## Summary diff --git a/11-regexp-groups/01-test-mac/solution.md b/11-regexp-groups/01-test-mac/solution.md index c16f0565a..26f7888f7 100644 --- a/11-regexp-groups/01-test-mac/solution.md +++ b/11-regexp-groups/01-test-mac/solution.md @@ -9,13 +9,13 @@ Now let's show that the match should capture all the text: start at the beginnin Finally: ```js run -let reg = /^[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}$/i; +let regexp = /^[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}$/i; -alert( reg.test('01:32:54:67:89:AB') ); // true +alert( regexp.test('01:32:54:67:89:AB') ); // true -alert( reg.test('0132546789AB') ); // false (no colons) +alert( regexp.test('0132546789AB') ); // false (no colons) -alert( reg.test('01:32:54:67:89') ); // false (5 numbers, need 6) +alert( regexp.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) +alert( regexp.test('01:32:54:67:89:ZZ') ) // false (ZZ in the end) ``` diff --git a/11-regexp-groups/01-test-mac/task.md b/11-regexp-groups/01-test-mac/task.md index e72655984..029a4803a 100644 --- a/11-regexp-groups/01-test-mac/task.md +++ b/11-regexp-groups/01-test-mac/task.md @@ -8,13 +8,13 @@ Write a regexp that checks whether a string is MAC-address. Usage: ```js -let reg = /your regexp/; +let regexp = /your regexp/; -alert( reg.test('01:32:54:67:89:AB') ); // true +alert( regexp.test('01:32:54:67:89:AB') ); // true -alert( reg.test('0132546789AB') ); // false (no colons) +alert( regexp.test('0132546789AB') ); // false (no colons) -alert( reg.test('01:32:54:67:89') ); // false (5 numbers, must be 6) +alert( regexp.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) +alert( regexp.test('01:32:54:67:89:ZZ') ) // false (ZZ ad the end) ``` diff --git a/11-regexp-groups/02-find-webcolor-3-or-6/solution.md b/11-regexp-groups/02-find-webcolor-3-or-6/solution.md index e173aba6f..0806dc4fd 100644 --- a/11-regexp-groups/02-find-webcolor-3-or-6/solution.md +++ b/11-regexp-groups/02-find-webcolor-3-or-6/solution.md @@ -9,19 +9,19 @@ Here the pattern `pattern:[a-f0-9]{3}` is enclosed in parentheses to apply the q In action: ```js run -let reg = /#([a-f0-9]{3}){1,2}/gi; +let regexp = /#([a-f0-9]{3}){1,2}/gi; let str = "color: #3f3; background-color: #AA00ef; and: #abcd"; -alert( str.match(reg) ); // #3f3 #AA00ef #abc +alert( str.match(regexp) ); // #3f3 #AA00ef #abc ``` There's a minor problem here: the pattern found `match:#abc` in `subject:#abcd`. To prevent that we can add `pattern:\b` to the end: ```js run -let reg = /#([a-f0-9]{3}){1,2}\b/gi; +let regexp = /#([a-f0-9]{3}){1,2}\b/gi; let str = "color: #3f3; background-color: #AA00ef; and: #abcd"; -alert( str.match(reg) ); // #3f3 #AA00ef +alert( str.match(regexp) ); // #3f3 #AA00ef ``` diff --git a/11-regexp-groups/02-find-webcolor-3-or-6/task.md b/11-regexp-groups/02-find-webcolor-3-or-6/task.md index d87914e99..09108484a 100644 --- a/11-regexp-groups/02-find-webcolor-3-or-6/task.md +++ b/11-regexp-groups/02-find-webcolor-3-or-6/task.md @@ -4,11 +4,11 @@ Write a RegExp that matches colors in the format `#abc` or `#abcdef`. That is: ` Usage example: ```js -let reg = /your regexp/g; +let regexp = /your regexp/g; let str = "color: #3f3; background-color: #AA00ef; and: #abcd"; -alert( str.match(reg) ); // #3f3 #AA00ef +alert( str.match(regexp) ); // #3f3 #AA00ef ``` P.S. This should be exactly 3 or 6 hex digits. Values with 4 digits, such as `#abcd`, should not match. diff --git a/11-regexp-groups/03-find-decimal-numbers/solution.md b/11-regexp-groups/03-find-decimal-numbers/solution.md index 3155f13c4..c4349f9a0 100644 --- a/11-regexp-groups/03-find-decimal-numbers/solution.md +++ b/11-regexp-groups/03-find-decimal-numbers/solution.md @@ -3,9 +3,9 @@ A positive number with an optional decimal part is (per previous task): `pattern Let's add the optional `pattern:-` in the beginning: ```js run -let reg = /-?\d+(\.\d+)?/g; +let regexp = /-?\d+(\.\d+)?/g; let str = "-1.5 0 2 -123.4."; -alert( str.match(reg) ); // -1.5, 0, 2, -123.4 +alert( str.match(regexp) ); // -1.5, 0, 2, -123.4 ``` diff --git a/11-regexp-groups/03-find-decimal-numbers/task.md b/11-regexp-groups/03-find-decimal-numbers/task.md index 459350680..4f5a73fff 100644 --- a/11-regexp-groups/03-find-decimal-numbers/task.md +++ b/11-regexp-groups/03-find-decimal-numbers/task.md @@ -5,9 +5,9 @@ Write a regexp that looks for all decimal numbers including integer ones, with t An example of use: ```js -let reg = /your regexp/g; +let regexp = /your regexp/g; let str = "-1.5 0 2 -123.4."; -alert( str.match(reg) ); // -1.5, 0, 2, -123.4 +alert( str.match(regexp) ); // -1.5, 0, 2, -123.4 ``` diff --git a/11-regexp-groups/04-parse-expression/solution.md b/11-regexp-groups/04-parse-expression/solution.md index decb074dc..130c57be3 100644 --- a/11-regexp-groups/04-parse-expression/solution.md +++ b/11-regexp-groups/04-parse-expression/solution.md @@ -18,9 +18,9 @@ To make each of these parts a separate element of the result array, let's enclos In action: ```js run -let reg = /(-?\d+(\.\d+)?)\s*([-+*\/])\s*(-?\d+(\.\d+)?)/; +let regexp = /(-?\d+(\.\d+)?)\s*([-+*\/])\s*(-?\d+(\.\d+)?)/; -alert( "1.2 + 12".match(reg) ); +alert( "1.2 + 12".match(regexp) ); ``` The result includes: @@ -42,9 +42,9 @@ The final solution: ```js run function parse(expr) { - let reg = /(-?\d+(?:\.\d+)?)\s*([-+*\/])\s*(-?\d+(?:\.\d+)?)/; + let regexp = /(-?\d+(?:\.\d+)?)\s*([-+*\/])\s*(-?\d+(?:\.\d+)?)/; - let result = expr.match(reg); + let result = expr.match(regexp); if (!result) return []; result.shift(); diff --git a/11-regexp-groups/article.md b/11-regexp-groups/article.md index 8a4fdd719..acc59b7c5 100644 --- a/11-regexp-groups/article.md +++ b/11-regexp-groups/article.md @@ -56,9 +56,9 @@ The email format is: `name@domain`. Any word can be the name, hyphens and dots a The pattern: ```js run -let reg = /[-.\w]+@([\w-]+\.)+[\w-]+/g; +let regexp = /[-.\w]+@([\w-]+\.)+[\w-]+/g; -alert("my@mail.com @ his@site.com.uk".match(reg)); // my@mail.com, his@site.com.uk +alert("my@mail.com @ his@site.com.uk".match(regexp)); // my@mail.com, his@site.com.uk ``` That regexp is not perfect, but mostly works and helps to fix accidental mistypes. The only truly reliable check for an email can only be done by sending a letter. @@ -110,9 +110,9 @@ In action: ```js run let str = ''; -let reg = /<(([a-z]+)\s*([^>]*))>/; +let regexp = /<(([a-z]+)\s*([^>]*))>/; -let result = str.match(reg); +let result = str.match(regexp); alert(result[0]); // alert(result[1]); // span class="my" alert(result[2]); // span @@ -336,10 +336,10 @@ let str = "Gogogo John!"; *!* // ?: exludes 'go' from capturing -let reg = /(?:go)+ (\w+)/i; +let regexp = /(?:go)+ (\w+)/i; */!* -let result = str.match(reg); +let result = str.match(regexp); alert( result[0] ); // Gogogo John (full match) alert( result[1] ); // John diff --git a/12-regexp-backreferences/article.md b/12-regexp-backreferences/article.md index 07d2ca07c..83beb803a 100644 --- a/12-regexp-backreferences/article.md +++ b/12-regexp-backreferences/article.md @@ -17,10 +17,10 @@ We can put both kinds of quotes in the square brackets: `pattern:['"](.*?)['"]`, ```js run let str = `He said: "She's the one!".`; -let reg = /['"](.*?)['"]/g; +let regexp = /['"](.*?)['"]/g; // The result is not what we'd like to have -alert( str.match(reg) ); // "She' +alert( str.match(regexp) ); // "She' ``` As we can see, the pattern found an opening quote `match:"`, then the text is consumed till the other quote `match:'`, that closes the match. @@ -33,10 +33,10 @@ Here's the correct code: let str = `He said: "She's the one!".`; *!* -let reg = /(['"])(.*?)\1/g; +let regexp = /(['"])(.*?)\1/g; */!* -alert( str.match(reg) ); // "She's the one!" +alert( str.match(regexp) ); // "She's the one!" ``` Now it works! The regular expression engine finds the first quote `pattern:(['"])` and memorizes its content. That's the first capturing group. @@ -65,8 +65,8 @@ In the example below the group with quotes is named `pattern:?`, so the b let str = `He said: "She's the one!".`; *!* -let reg = /(?['"])(.*?)\k/g; +let regexp = /(?['"])(.*?)\k/g; */!* -alert( str.match(reg) ); // "She's the one!" +alert( str.match(regexp) ); // "She's the one!" ``` diff --git a/13-regexp-alternation/01-find-programming-language/solution.md b/13-regexp-alternation/01-find-programming-language/solution.md index 3419aa498..e33f9cf2f 100644 --- a/13-regexp-alternation/01-find-programming-language/solution.md +++ b/13-regexp-alternation/01-find-programming-language/solution.md @@ -4,11 +4,11 @@ The first idea can be to list the languages with `|` in-between. But that doesn't work right: ```js run -let reg = /Java|JavaScript|PHP|C|C\+\+/g; +let regexp = /Java|JavaScript|PHP|C|C\+\+/g; let str = "Java, JavaScript, PHP, C, C++"; -alert( str.match(reg) ); // Java,Java,PHP,C,C +alert( str.match(regexp) ); // Java,Java,PHP,C,C ``` 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. @@ -25,9 +25,9 @@ There are two solutions for that problem: In action: ```js run -let reg = /Java(Script)?|C(\+\+)?|PHP/g; +let regexp = /Java(Script)?|C(\+\+)?|PHP/g; let str = "Java, JavaScript, PHP, C, C++"; -alert( str.match(reg) ); // Java,JavaScript,PHP,C,C++ +alert( str.match(regexp) ); // Java,JavaScript,PHP,C,C++ ``` diff --git a/13-regexp-alternation/01-find-programming-language/task.md b/13-regexp-alternation/01-find-programming-language/task.md index 61b9526f7..e0f7af95c 100644 --- a/13-regexp-alternation/01-find-programming-language/task.md +++ b/13-regexp-alternation/01-find-programming-language/task.md @@ -5,7 +5,7 @@ There are many programming languages, for instance 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; +let regexp = /your regexp/g; -alert("Java JavaScript PHP C++ C".match(reg)); // Java JavaScript PHP C++ C +alert("Java JavaScript PHP C++ C".match(regexp)); // Java JavaScript PHP C++ C ``` diff --git a/13-regexp-alternation/02-find-matching-bbtags/solution.md b/13-regexp-alternation/02-find-matching-bbtags/solution.md index dddaf9628..971e2e629 100644 --- a/13-regexp-alternation/02-find-matching-bbtags/solution.md +++ b/13-regexp-alternation/02-find-matching-bbtags/solution.md @@ -8,7 +8,7 @@ The full pattern: `pattern:\[(b|url|quote)\].*?\[/\1\]`. In action: ```js run -let reg = /\[(b|url|quote)\].*?\[\/\1\]/gs; +let regexp = /\[(b|url|quote)\].*?\[\/\1\]/gs; let str = ` [b]hello![/b] @@ -17,7 +17,7 @@ let str = ` [/quote] `; -alert( str.match(reg) ); // [b]hello![/b],[quote][url]http://google.com[/url][/quote] +alert( str.match(regexp) ); // [b]hello![/b],[quote][url]http://google.com[/url][/quote] ``` Please note that we had to escape a slash for the closing tag `pattern:[/\1]`, because normally the slash closes the pattern. diff --git a/13-regexp-alternation/02-find-matching-bbtags/task.md b/13-regexp-alternation/02-find-matching-bbtags/task.md index 8cc59deb3..2893a4508 100644 --- a/13-regexp-alternation/02-find-matching-bbtags/task.md +++ b/13-regexp-alternation/02-find-matching-bbtags/task.md @@ -32,17 +32,17 @@ Create a regexp to find all BB-tags with their contents. For instance: ```js -let reg = /your regexp/flags; +let regexp = /your regexp/flags; let str = "..[url]http://google.com[/url].."; -alert( str.match(reg) ); // [url]http://google.com[/url] +alert( str.match(regexp) ); // [url]http://google.com[/url] ``` If tags are nested, then we need the outer tag (if we want we can continue the search in its content): ```js -let reg = /your regexp/flags; +let regexp = /your regexp/flags; let str = "..[url][b]http://google.com[/b][/url].."; -alert( str.match(reg) ); // [url][b]http://google.com[/b][/url] +alert( str.match(regexp) ); // [url][b]http://google.com[/b][/url] ``` diff --git a/13-regexp-alternation/03-match-quoted-string/solution.md b/13-regexp-alternation/03-match-quoted-string/solution.md index 143be870c..5a007aee0 100644 --- a/13-regexp-alternation/03-match-quoted-string/solution.md +++ b/13-regexp-alternation/03-match-quoted-string/solution.md @@ -10,8 +10,8 @@ Step by step: In action: ```js run -let reg = /"(\\.|[^"\\])*"/g; +let regexp = /"(\\.|[^"\\])*"/g; let str = ' .. "test me" .. "Say \\"Hello\\"!" .. "\\\\ \\"" .. '; -alert( str.match(reg) ); // "test me","Say \"Hello\"!","\\ \"" +alert( str.match(regexp) ); // "test me","Say \"Hello\"!","\\ \"" ``` diff --git a/13-regexp-alternation/04-match-exact-tag/solution.md b/13-regexp-alternation/04-match-exact-tag/solution.md index 70c4de91a..5d4ba8d96 100644 --- a/13-regexp-alternation/04-match-exact-tag/solution.md +++ b/13-regexp-alternation/04-match-exact-tag/solution.md @@ -10,7 +10,7 @@ In the regexp language: `pattern:|\s.*?>)`. In action: ```js run -let reg = /|\s.*?>)/g; +let regexp = /|\s.*?>)/g; -alert( ' \ No newline at end of file + \ No newline at end of file diff --git a/10-regexp-greedy-and-lazy/witch_greedy1.svg b/10-regexp-greedy-and-lazy/witch_greedy1.svg index 13db398d5..2eaf636cd 100644 --- a/10-regexp-greedy-and-lazy/witch_greedy1.svg +++ b/10-regexp-greedy-and-lazy/witch_greedy1.svg @@ -1 +1 @@ -a "witch" and her "broom" is one \ No newline at end of file +a "witch" and her "broom" is one \ No newline at end of file diff --git a/10-regexp-greedy-and-lazy/witch_greedy2.svg b/10-regexp-greedy-and-lazy/witch_greedy2.svg index 6ed66590c..0489875a6 100644 --- a/10-regexp-greedy-and-lazy/witch_greedy2.svg +++ b/10-regexp-greedy-and-lazy/witch_greedy2.svg @@ -1 +1 @@ -a "witch" and her "broom" is one \ No newline at end of file +a "witch" and her "broom" is one \ No newline at end of file diff --git a/10-regexp-greedy-and-lazy/witch_greedy3.svg b/10-regexp-greedy-and-lazy/witch_greedy3.svg index e37f8e75f..f5175e5c3 100644 --- a/10-regexp-greedy-and-lazy/witch_greedy3.svg +++ b/10-regexp-greedy-and-lazy/witch_greedy3.svg @@ -1 +1 @@ -a "witch" and her "broom" is one \ No newline at end of file +a "witch" and her "broom" is one \ No newline at end of file diff --git a/10-regexp-greedy-and-lazy/witch_greedy4.svg b/10-regexp-greedy-and-lazy/witch_greedy4.svg index a1ec3f2be..61b37fb9c 100644 --- a/10-regexp-greedy-and-lazy/witch_greedy4.svg +++ b/10-regexp-greedy-and-lazy/witch_greedy4.svg @@ -1 +1 @@ -a "witch" and her "broom" is one \ No newline at end of file +a "witch" and her "broom" is one \ No newline at end of file diff --git a/10-regexp-greedy-and-lazy/witch_greedy5.svg b/10-regexp-greedy-and-lazy/witch_greedy5.svg index 05ef0f1a1..a0c5f1fb8 100644 --- a/10-regexp-greedy-and-lazy/witch_greedy5.svg +++ b/10-regexp-greedy-and-lazy/witch_greedy5.svg @@ -1 +1 @@ -a "witch" and her "broom" is one \ No newline at end of file +a "witch" and her "broom" is one \ No newline at end of file diff --git a/10-regexp-greedy-and-lazy/witch_greedy6.svg b/10-regexp-greedy-and-lazy/witch_greedy6.svg index bb2cead9e..c7cc7537c 100644 --- a/10-regexp-greedy-and-lazy/witch_greedy6.svg +++ b/10-regexp-greedy-and-lazy/witch_greedy6.svg @@ -1 +1 @@ -a "witch" and her "broom" is one \ No newline at end of file +a "witch" and her "broom" is one \ No newline at end of file diff --git a/10-regexp-greedy-and-lazy/witch_lazy3.svg b/10-regexp-greedy-and-lazy/witch_lazy3.svg index f4c040a75..77d5d1562 100644 --- a/10-regexp-greedy-and-lazy/witch_lazy3.svg +++ b/10-regexp-greedy-and-lazy/witch_lazy3.svg @@ -1 +1 @@ -a "witch" and her "broom" is one \ No newline at end of file +a "witch" and her "broom" is one \ No newline at end of file diff --git a/10-regexp-greedy-and-lazy/witch_lazy4.svg b/10-regexp-greedy-and-lazy/witch_lazy4.svg index 5cd1fcd80..6c9cc29cf 100644 --- a/10-regexp-greedy-and-lazy/witch_lazy4.svg +++ b/10-regexp-greedy-and-lazy/witch_lazy4.svg @@ -1 +1 @@ -a "witch" and her "broom" is one \ No newline at end of file +a "witch" and her "broom" is one \ No newline at end of file diff --git a/10-regexp-greedy-and-lazy/witch_lazy5.svg b/10-regexp-greedy-and-lazy/witch_lazy5.svg index c8a945bef..68c77d27d 100644 --- a/10-regexp-greedy-and-lazy/witch_lazy5.svg +++ b/10-regexp-greedy-and-lazy/witch_lazy5.svg @@ -1 +1 @@ -a "witch" and her "broom" is one \ No newline at end of file +a "witch" and her "broom" is one \ No newline at end of file diff --git a/10-regexp-greedy-and-lazy/witch_lazy6.svg b/10-regexp-greedy-and-lazy/witch_lazy6.svg index ff271001e..2ee64f5b8 100644 --- a/10-regexp-greedy-and-lazy/witch_lazy6.svg +++ b/10-regexp-greedy-and-lazy/witch_lazy6.svg @@ -1 +1 @@ -a "witch" and her "broom" is one \ No newline at end of file +a "witch" and her "broom" is one \ No newline at end of file From ff934a1344d6170e0d0b155c26c2faa25396ab42 Mon Sep 17 00:00:00 2001 From: Ilya Kantor Date: Thu, 10 Oct 2019 23:58:29 +0300 Subject: [PATCH 075/101] Update solution.md --- 08-regexp-character-sets-and-ranges/1-find-range-1/solution.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/08-regexp-character-sets-and-ranges/1-find-range-1/solution.md b/08-regexp-character-sets-and-ranges/1-find-range-1/solution.md index a6d71f661..378471611 100644 --- a/08-regexp-character-sets-and-ranges/1-find-range-1/solution.md +++ b/08-regexp-character-sets-and-ranges/1-find-range-1/solution.md @@ -5,7 +5,7 @@ Answers: **no, yes**. ```js run alert( "Java".match(/Java[^script]/) ); // null ``` -- Yes, because the regexp is case-insensitive, the `pattern:[^script]` part matches the character `"S"`. +- Yes, because the part `pattern:[^script]` part matches the character `"S"`. It's not one of `pattern:script`. As the regexp is case-sensitive (no `pattern:i` flag), it treats `"S"` as a different character from `"s"`. ```js run alert( "JavaScript".match(/Java[^script]/) ); // "JavaS" From d13bb2bbd109e00464419ef5ef363705695e4961 Mon Sep 17 00:00:00 2001 From: Ilya Kantor Date: Thu, 17 Oct 2019 16:55:19 +0300 Subject: [PATCH 076/101] Update article.md --- 05-regexp-multiline-mode/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/05-regexp-multiline-mode/article.md b/05-regexp-multiline-mode/article.md index 321218b37..539f9fa23 100644 --- a/05-regexp-multiline-mode/article.md +++ b/05-regexp-multiline-mode/article.md @@ -57,7 +57,7 @@ alert( str.match(/\d$/gm) ); // 1,2,3 Without the flag `m`, the dollar `pattern:$` would only match the end of the whole text, so only the very last digit would be found. ```smart -"End of a line" formally means "immediately before a line break": the test `pattern:^` in multiline mode matches at all positions succeeded by a newline character `\n`. +"End of a line" formally means "immediately before a line break": the test `pattern:$` in multiline mode matches at all positions succeeded by a newline character `\n`. And at the text end. ``` From df93a71f772ca473634c8d3b1e2c833b90f1a2b2 Mon Sep 17 00:00:00 2001 From: Ilya Kantor Date: Mon, 21 Oct 2019 16:21:13 +0300 Subject: [PATCH 077/101] minor --- 10-regexp-greedy-and-lazy/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/10-regexp-greedy-and-lazy/article.md b/10-regexp-greedy-and-lazy/article.md index 4298e7c80..79abc559d 100644 --- a/10-regexp-greedy-and-lazy/article.md +++ b/10-regexp-greedy-and-lazy/article.md @@ -140,7 +140,7 @@ To clearly understand the change, let's trace the search step by step. ![](witch_lazy6.svg) -In this example we saw how the lazy mode works for `pattern:+?`. Quantifiers `pattern:+?` and `pattern:??` work the similar way -- the regexp engine increases the number of repetitions only if the rest of the pattern can't match on the given position. +In this example we saw how the lazy mode works for `pattern:+?`. Quantifiers `pattern:*?` and `pattern:??` work the similar way -- the regexp engine increases the number of repetitions only if the rest of the pattern can't match on the given position. **Laziness is only enabled for the quantifier with `?`.** From 8871fd57b69431ad45787ee8ec548811c597c305 Mon Sep 17 00:00:00 2001 From: Tobi Obeck <13554426+TobiObeck@users.noreply.github.com> Date: Tue, 29 Oct 2019 14:58:38 +0100 Subject: [PATCH 078/101] removes russian text chunk from english text --- 11-regexp-groups/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/11-regexp-groups/article.md b/11-regexp-groups/article.md index acc59b7c5..ab25066d7 100644 --- a/11-regexp-groups/article.md +++ b/11-regexp-groups/article.md @@ -71,7 +71,7 @@ The method `str.match(regexp)`, if `regexp` has no flag `g`, looks for the first 1. At index `0`: the full match. 2. At index `1`: the contents of the first parentheses. -3. На позиции `2`: the contents of the second parentheses. +3. At index `2`: the contents of the second parentheses. 4. ...and so on... For instance, we'd like to find HTML tags `pattern:<.*?>`, and process them. It would be convenient to have tag content (what's inside the angles), in a separate variable. From 748e97bf1593f9ca515f60fbe16f5d7651d2ecd9 Mon Sep 17 00:00:00 2001 From: Will Golledge <35961363+wgolledge@users.noreply.github.com> Date: Tue, 5 Nov 2019 20:30:36 +0000 Subject: [PATCH 079/101] Update 01-regexp-introduction This PR just includes grammar and formatting suggestions. --- 01-regexp-introduction/article.md | 34 +++++++++++++++---------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/01-regexp-introduction/article.md b/01-regexp-introduction/article.md index 186991bac..a35d19a7b 100644 --- a/01-regexp-introduction/article.md +++ b/01-regexp-introduction/article.md @@ -1,14 +1,14 @@ # Patterns and flags -Regular expressions is a powerful way to search and replace in text. +Regular expressions are patterns that provide a powerful way to search and replace in text. -In JavaScript, they are available as [RegExp](mdn:js/RegExp) object, and also integrated in methods of strings. +In JavaScript, they are available via the [RegExp](mdn:js/RegExp) object, as well as being integrated in methods of strings. ## Regular Expressions A regular expression (also "regexp", or just "reg") consists of a *pattern* and optional *flags*. -There are two syntaxes to create a regular expression object. +There are two syntaxes that can be used to create a regular expression object. The "long" syntax: @@ -16,7 +16,7 @@ The "long" syntax: regexp = new RegExp("pattern", "flags"); ``` -...And the short one, using slashes `"/"`: +And the "short" one, using slashes `"/"`: ```js regexp = /pattern/; // no flags @@ -25,11 +25,11 @@ regexp = /pattern/gmi; // with flags g,m and i (to be covered soon) Slashes `pattern:/.../` tell JavaScript that we are creating a regular expression. They play the same role as quotes for strings. -In both cases `regexp` becomes an object of the built-in `RegExp` class. +In both cases `regexp` becomes an instance of the built-in `RegExp` class. -The main difference between these two syntaxes is that slashes `pattern:/.../` do not allow to insert expressions (like strings with `${...}`). They are fully static. +The main difference between these two syntaxes is that pattern using slashes `/.../` does not allow for expressions to be inserted (like string template literals with `${...}`). They are fully static. -Slashes are used when we know the regular expression at the code writing time -- and that's the most common situation. While `new RegExp` is used when we need to create a regexp "on the fly", from a dynamically generated string, for instance: +Slashes are used when we know the regular expression at the code writing time -- and that's the most common situation. While `new RegExp`, is more often used when we need to create a regexp "on the fly" from a dynamically generated string. For instance: ```js let tag = prompt("What tag do you want to find?", "h2"); @@ -47,7 +47,7 @@ There are only 6 of them in JavaScript: : With this flag the search is case-insensitive: no difference between `A` and `a` (see the example below). `pattern:g` -: With this flag the search looks for all matches, without it -- only the first one. +: With this flag the search looks for all matches, without it -- only the first match is returned. `pattern:m` : Multiline mode (covered in the chapter ). @@ -71,7 +71,7 @@ From here on the color scheme is: ## Searching: str.match -As it was said previously, regular expressions are integrated with string methods. +As mentioned previously, regular expressions are integrated with string methods. The method `str.match(regexp)` finds all matches of `regexp` in the string `str`. @@ -102,7 +102,7 @@ It has 3 working modes: 3. And, finally, if there are no matches, `null` is returned (doesn't matter if there's flag `pattern:g` or not). - That's a very important nuance. If there are no matches, we get not an empty array, but `null`. Forgetting about that may lead to errors, e.g.: + This a very important nuance. If there are no matches, we don't receive an empty array, but instead receive `null`. Forgetting about that may lead to errors, e.g.: ```js run let matches = "JavaScript".match(/HTML/); // = null @@ -112,7 +112,7 @@ It has 3 working modes: } ``` - If we'd like the result to be always an array, we can write it this way: + If we'd like the result to always be an array, we can write it this way: ```js run let matches = "JavaScript".match(/HTML/)*!* || []*/!*; @@ -124,7 +124,7 @@ It has 3 working modes: ## Replacing: str.replace -The method `str.replace(regexp, replacement)` replaces matches with `regexp` in string `str` with `replacement` (all matches, if there's flag `pattern:g`, otherwise only the first one). +The method `str.replace(regexp, replacement)` replaces matches found using `regexp` in string `str` with `replacement` (all matches if there's flag `pattern:g`, otherwise, only the first one). For instance: @@ -164,14 +164,14 @@ let regexp = /LOVE/i; alert( regexp.test(str) ); // true ``` -Further in this chapter we'll study more regular expressions, come across many other examples and also meet other methods. +Later in this chapter we'll study more regular expressions, walk through more examples, and also meet other methods. Full information about the methods is given in the article . ## Summary - A regular expression consists of a pattern and optional flags: `pattern:g`, `pattern:i`, `pattern:m`, `pattern:u`, `pattern:s`, `pattern:y`. -- Without flags and special symbols that we'll study later, the search by a regexp is the same as a substring search. -- The method `str.match(regexp)` looks for matches: all of them if there's `pattern:g` flag, otherwise only the first one. -- The method `str.replace(regexp, replacement)` replaces matches with `regexp` by `replacement`: all of them if there's `pattern:g` flag, otherwise only the first one. -- The method `regexp.test(str)` returns `true` if there's at least one match, otherwise `false`. +- Without flags and special symbols (that we'll study later), the search by a regexp is the same as a substring search. +- The method `str.match(regexp)` looks for matches: all of them if there's `pattern:g` flag, otherwise, only the first one. +- The method `str.replace(regexp, replacement)` replaces matches found using `regexp` with `replacement`: all of them if there's `pattern:g` flag, otherwise only the first one. +- The method `regexp.test(str)` returns `true` if there's at least one match, otherwise, it returns `false`. From 503e43f5504b68ddfc7da76fb22bade677a511f3 Mon Sep 17 00:00:00 2001 From: Alexey Pyltsyn Date: Thu, 19 Dec 2019 23:10:19 +0300 Subject: [PATCH 080/101] Update article.md --- 03-regexp-unicode/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/03-regexp-unicode/article.md b/03-regexp-unicode/article.md index 82c00d7df..a8a5f4f5b 100644 --- a/03-regexp-unicode/article.md +++ b/03-regexp-unicode/article.md @@ -34,7 +34,7 @@ Unlike strings, regular expressions have flag `pattern:u` that fixes such proble ## Unicode properties \p{...} ```warn header="Not supported in Firefox and Edge" -Despite being a part of the standard since 2018, unicode proeprties are not supported in Firefox ([bug](https://bugzilla.mozilla.org/show_bug.cgi?id=1361876)) and Edge ([bug](https://github.com/Microsoft/ChakraCore/issues/2969)). +Despite being a part of the standard since 2018, unicode properties are not supported in Firefox ([bug](https://bugzilla.mozilla.org/show_bug.cgi?id=1361876)) and Edge ([bug](https://github.com/Microsoft/ChakraCore/issues/2969)). There's [XRegExp](http://xregexp.com) library that provides "extended" regular expressions with cross-browser support for unicode properties. ``` From bc1145137abb330270964d97d5de15b9cf48d0d4 Mon Sep 17 00:00:00 2001 From: Abir Date: Tue, 14 Jan 2020 23:20:39 +0530 Subject: [PATCH 081/101] Translated tasks to English --- .../2-insert-after-head/task.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/14-regexp-lookahead-lookbehind/2-insert-after-head/task.md b/14-regexp-lookahead-lookbehind/2-insert-after-head/task.md index ac9e5e4ce..3389d5d4b 100644 --- a/14-regexp-lookahead-lookbehind/2-insert-after-head/task.md +++ b/14-regexp-lookahead-lookbehind/2-insert-after-head/task.md @@ -1,10 +1,10 @@ -# Вставьте после фрагмента +# Paste after fragment -Есть строка с HTML-документом. +There is a line with an HTML Document. -Вставьте после тега `` (у него могут быть атрибуты) строку `

Hello

`. +Insert after tag `` (it may have attributes) line `

Hello

`. -Например: +For instance: ```js let regexp = /ваше регулярное выражение/; @@ -20,7 +20,7 @@ let str = ` str = str.replace(regexp, `

Hello

`); ``` -После этого значение `str`: +After that value `str`: ```html

Hello

From e8060486faa156765e01fc8d92e4e9285901ff37 Mon Sep 17 00:00:00 2001 From: Abir Date: Tue, 14 Jan 2020 23:21:42 +0530 Subject: [PATCH 082/101] Translated line 10 content to English --- 14-regexp-lookahead-lookbehind/2-insert-after-head/task.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/14-regexp-lookahead-lookbehind/2-insert-after-head/task.md b/14-regexp-lookahead-lookbehind/2-insert-after-head/task.md index 3389d5d4b..875268e41 100644 --- a/14-regexp-lookahead-lookbehind/2-insert-after-head/task.md +++ b/14-regexp-lookahead-lookbehind/2-insert-after-head/task.md @@ -7,7 +7,7 @@ Insert after tag `` (it may have attributes) line `

Hello

`. For instance: ```js -let regexp = /ваше регулярное выражение/; +let regexp = /your regular expression/; let str = ` From a77e784b0d9d3c06fc99b0e67ee21be9bd5cde5d Mon Sep 17 00:00:00 2001 From: Abir Date: Tue, 14 Jan 2020 23:34:28 +0530 Subject: [PATCH 083/101] Translated Solution from Russian to English --- .../2-insert-after-head/solution.md | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/14-regexp-lookahead-lookbehind/2-insert-after-head/solution.md b/14-regexp-lookahead-lookbehind/2-insert-after-head/solution.md index 980a7fe66..fffada188 100644 --- a/14-regexp-lookahead-lookbehind/2-insert-after-head/solution.md +++ b/14-regexp-lookahead-lookbehind/2-insert-after-head/solution.md @@ -1,9 +1,8 @@ +In order to insert after the `` tag, you must first find it. We will use the regular expression pattern `pattern:`. -Для того, чтобы вставить после тега ``, нужно вначале его найти. Будем использовать регулярное выражение `pattern:`. +Next, we need to leave the `` tag in place and add text after it. -Далее, нам нужно оставить сам тег `` на месте и добавить текст после него. - -Это можно сделать вот так: +This can be done like this: ```js run let str = '......'; str = str.replace(//, '$&

Hello

'); @@ -11,9 +10,9 @@ str = str.replace(//, '$&

Hello

'); alert(str); // ...

Hello

... ``` -В строке замены `$&` означает само совпадение, то есть мы заменяем `pattern:` заменяется на самого себя плюс `

Hello

`. +In the replacement string `$&` means the match itself, that is, we replace `pattern:` Is replaced by itself plus `

Hello

`. -Альтернативный вариант - использовать ретроспективную проверку: +An alternative is to use retrospective validation: ```js run let str = '......'; @@ -22,8 +21,8 @@ str = str.replace(/(?<=)/, `

Hello

`); alert(str); // ...

Hello

... ``` -Такое регулярное выражение на каждой позиции будет проверять, не идёт ли прямо перед ней `pattern:`. Если да - совпадение найдено. Но сам тег `pattern:` в совпадение не входит, он только участвует в проверке. А других символов после проверки в нём нет, так что текст совпадения будет пустым. +Such a regular expression at each position will check if `pattern:`does not go directly in front of it. If yes, a match is found. But the tag `pattern:` does not coincide, it only participates in the verification. And there are no other characters after checking in it, so the match text will be empty. -Происходит замена "пустой строки", перед которой идёт `pattern:` на `

Hello

`. Что, как раз, и есть вставка этой строки после ``. +This replaces the "empty line", followed by `pattern:` With `

Hello

`. Which, exactly, is the insertion of this line after ``. -P.S. Этому регулярному выражению не помешают флаги: `pattern://si`, чтобы в "точку" входил перевод строки (тег может занимать несколько строк), а также чтобы теги в другом регистре типа `match:` тоже находились. +P.S. The flags: `pattern://si`, will not interfere with this regular expression, so that a line break appears in the "dot" (a tag can span several lines), and also that the tags are in a different register of the `match:` type, too. From 7149d1f54f87f5d55bc9972870b392151d25418d Mon Sep 17 00:00:00 2001 From: Abir Date: Tue, 14 Jan 2020 23:38:20 +0530 Subject: [PATCH 084/101] Translated to English and updated. --- 14-regexp-lookahead-lookbehind/2-insert-after-head/task.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/14-regexp-lookahead-lookbehind/2-insert-after-head/task.md b/14-regexp-lookahead-lookbehind/2-insert-after-head/task.md index 875268e41..9ecc2a7a6 100644 --- a/14-regexp-lookahead-lookbehind/2-insert-after-head/task.md +++ b/14-regexp-lookahead-lookbehind/2-insert-after-head/task.md @@ -1,4 +1,4 @@ -# Paste after fragment +# Insert After Head There is a line with an HTML Document. From c1f22a395c033d7e5ff1d50a3967fcbdf746667c Mon Sep 17 00:00:00 2001 From: Ilya Kantor Date: Sun, 19 Jan 2020 20:32:27 +0100 Subject: [PATCH 085/101] minor fixes --- .../2-insert-after-head/solution.md | 24 ++++++++++++------- .../2-insert-after-head/task.md | 6 ++--- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/14-regexp-lookahead-lookbehind/2-insert-after-head/solution.md b/14-regexp-lookahead-lookbehind/2-insert-after-head/solution.md index fffada188..b5915744a 100644 --- a/14-regexp-lookahead-lookbehind/2-insert-after-head/solution.md +++ b/14-regexp-lookahead-lookbehind/2-insert-after-head/solution.md @@ -1,8 +1,9 @@ -In order to insert after the `` tag, you must first find it. We will use the regular expression pattern `pattern:`. +In order to insert after the `` tag, we must first find it. We can use the regular expression pattern `pattern:` for that. -Next, we need to leave the `` tag in place and add text after it. +In this task we don't need to modify the `` tag. We only need to add the text after it. + +Here's how we can do it: -This can be done like this: ```js run let str = '......'; str = str.replace(//, '$&

Hello

'); @@ -10,9 +11,9 @@ str = str.replace(//, '$&

Hello

'); alert(str); // ...

Hello

... ``` -In the replacement string `$&` means the match itself, that is, we replace `pattern:` Is replaced by itself plus `

Hello

`. +In the replacement string `$&` means the match itself, that is, the part of the source text that corresponds to `pattern:`. It gets replaced by itself plus `

Hello

`. -An alternative is to use retrospective validation: +An alternative is to use lookbehind: ```js run let str = '......'; @@ -21,8 +22,15 @@ str = str.replace(/(?<=)/, `

Hello

`); alert(str); // ...

Hello

... ``` -Such a regular expression at each position will check if `pattern:`does not go directly in front of it. If yes, a match is found. But the tag `pattern:` does not coincide, it only participates in the verification. And there are no other characters after checking in it, so the match text will be empty. +As you can see, there's only lookbehind part in this regexp. + +It works like this: +- At every position in the text. +- Check if it's preceeded by `pattern:`. +- If it's so then we have the match. + +The tag `pattern:` won't be returned. The result of this regexp is literally an empty string, but it matches only at positions preceeded by `pattern:`. -This replaces the "empty line", followed by `pattern:` With `

Hello

`. Which, exactly, is the insertion of this line after ``. +So we replaces the "empty line", preceeded by `pattern:`, with `

Hello

`. That's the insertion after ``. -P.S. The flags: `pattern://si`, will not interfere with this regular expression, so that a line break appears in the "dot" (a tag can span several lines), and also that the tags are in a different register of the `match:` type, too. +P.S. Regexp flags, such as `pattern:s` and `pattern:i` can also useful: `pattern://si`. The `pattern:s` flag makes the dot `pattern:.` match a newline character, and `pattern:i` flag makes `pattern:` also match `match:` case-insensitively. diff --git a/14-regexp-lookahead-lookbehind/2-insert-after-head/task.md b/14-regexp-lookahead-lookbehind/2-insert-after-head/task.md index 9ecc2a7a6..be1a259f6 100644 --- a/14-regexp-lookahead-lookbehind/2-insert-after-head/task.md +++ b/14-regexp-lookahead-lookbehind/2-insert-after-head/task.md @@ -1,8 +1,8 @@ # Insert After Head -There is a line with an HTML Document. +We have a string with an HTML Document. -Insert after tag `` (it may have attributes) line `

Hello

`. +Write a regular expression that inserts `

Hello

` immediately after `` tag. The tag may have attributes. For instance: @@ -20,7 +20,7 @@ let str = ` str = str.replace(regexp, `

Hello

`); ``` -After that value `str`: +After that the value of `str` should be: ```html

Hello

From 055a1559e2ead81ab5c7aa30e8026a0cbf581a73 Mon Sep 17 00:00:00 2001 From: Sohail Ashraf Date: Wed, 29 Jan 2020 13:38:00 +0800 Subject: [PATCH 086/101] Fixing a typo Check if `pattern:Y` is immediately after `pattern:X` and then the regex will check if Z is immediately after Y, Like this it's more understandable. --- 14-regexp-lookahead-lookbehind/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/14-regexp-lookahead-lookbehind/article.md b/14-regexp-lookahead-lookbehind/article.md index 31393cd03..c7eb28432 100644 --- a/14-regexp-lookahead-lookbehind/article.md +++ b/14-regexp-lookahead-lookbehind/article.md @@ -26,7 +26,7 @@ More complex tests are possible, e.g. `pattern:X(?=Y)(?=Z)` means: 1. Find `pattern:X`. 2. Check if `pattern:Y` is immediately after `pattern:X` (skip if isn't). -3. Check if `pattern:Z` is immediately after `pattern:X` (skip if isn't). +3. Check if `pattern:Z` is immediately after `pattern:Y` (skip if isn't). 4. If both tests passed, then it's the match. In other words, such pattern means that we're looking for `pattern:X` followed by `pattern:Y` and `pattern:Z` at the same time. From cfb56cd018b57ee1eb02e88863729ce4eca87b32 Mon Sep 17 00:00:00 2001 From: Kin Lum Date: Wed, 29 Jan 2020 13:43:35 -0800 Subject: [PATCH 087/101] adding the `[^]` pattern --- 02-regexp-character-classes/article.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/02-regexp-character-classes/article.md b/02-regexp-character-classes/article.md index 5b4258869..b00962b3d 100644 --- a/02-regexp-character-classes/article.md +++ b/02-regexp-character-classes/article.md @@ -156,6 +156,8 @@ alert( "A\nB".match(/A[\s\S]B/) ); // A\nB (match!) The pattern `pattern:[\s\S]` literally says: "a space character OR not a space character". In other words, "anything". We could use another pair of complementary classes, such as `pattern:[\d\D]`, that doesn't matter. This trick works everywhere. Also we can use it if we don't want to set `pattern:s` flag, in cases when we want a regular "no-newline" dot too in the pattern. + +Also worth mentioning is, besides `[\s\S]`, there is another regular expression that can match any character, which is `[^]` -- it means match any character except nothing, and that means to match any character without exception. `[^]` and `[\s\S]` are the two typical regular expressions to solve the missing of `s` flag problem. ```` ````warn header="Pay attention to spaces" From f11d17bddcadbb975acb4b949898e4ee8566d313 Mon Sep 17 00:00:00 2001 From: Ilya Kantor Date: Wed, 5 Feb 2020 10:48:52 +0000 Subject: [PATCH 088/101] minor fixes --- 02-regexp-character-classes/article.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/02-regexp-character-classes/article.md b/02-regexp-character-classes/article.md index b00962b3d..9396c3e19 100644 --- a/02-regexp-character-classes/article.md +++ b/02-regexp-character-classes/article.md @@ -153,11 +153,9 @@ Luckily, there's an alternative, that works everywhere. We can use a regexp like alert( "A\nB".match(/A[\s\S]B/) ); // A\nB (match!) ``` -The pattern `pattern:[\s\S]` literally says: "a space character OR not a space character". In other words, "anything". We could use another pair of complementary classes, such as `pattern:[\d\D]`, that doesn't matter. +The pattern `pattern:[\s\S]` literally says: "a space character OR not a space character". In other words, "anything". We could use another pair of complementary classes, such as `pattern:[\d\D]`, that doesn't matter. Or even the `pattern:[^]` -- as it means match any character except nothing. -This trick works everywhere. Also we can use it if we don't want to set `pattern:s` flag, in cases when we want a regular "no-newline" dot too in the pattern. - -Also worth mentioning is, besides `[\s\S]`, there is another regular expression that can match any character, which is `[^]` -- it means match any character except nothing, and that means to match any character without exception. `[^]` and `[\s\S]` are the two typical regular expressions to solve the missing of `s` flag problem. +This trick works everywhere. Also we can use it if we don't want to set `pattern:s` flag (or it's not supported), in cases when we want a regular "no-newline" dot too in the pattern. ```` ````warn header="Pay attention to spaces" From 487899186c8981ca2d4e27516bea5d7db1d0e9d2 Mon Sep 17 00:00:00 2001 From: Ilya Kantor Date: Wed, 5 Feb 2020 10:51:47 +0000 Subject: [PATCH 089/101] minor fixes --- 02-regexp-character-classes/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/02-regexp-character-classes/article.md b/02-regexp-character-classes/article.md index 9396c3e19..dbf932875 100644 --- a/02-regexp-character-classes/article.md +++ b/02-regexp-character-classes/article.md @@ -155,7 +155,7 @@ alert( "A\nB".match(/A[\s\S]B/) ); // A\nB (match!) The pattern `pattern:[\s\S]` literally says: "a space character OR not a space character". In other words, "anything". We could use another pair of complementary classes, such as `pattern:[\d\D]`, that doesn't matter. Or even the `pattern:[^]` -- as it means match any character except nothing. -This trick works everywhere. Also we can use it if we don't want to set `pattern:s` flag (or it's not supported), in cases when we want a regular "no-newline" dot too in the pattern. +Also we can use this trick if we want both kind of "dots" in the same pattern: the actual dot `pattern:.` behaving the regular way ("not including a newline"), and also a way to match "any character" with `pattern:[\s\S]` or alike. ```` ````warn header="Pay attention to spaces" From 89e3860b8058d2caa4b158a447dd75c467b3c4c0 Mon Sep 17 00:00:00 2001 From: Quinn Flavel Date: Mon, 10 Feb 2020 11:09:15 -0500 Subject: [PATCH 090/101] Typo fix --- 03-regexp-unicode/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/03-regexp-unicode/article.md b/03-regexp-unicode/article.md index a8a5f4f5b..fb8fed470 100644 --- a/03-regexp-unicode/article.md +++ b/03-regexp-unicode/article.md @@ -1,6 +1,6 @@ # Unicode: flag "u" and class \p{...} -JavaScript uses [Unicode encoding](https://en.wikipedia.org/wiki/Unicode) for strings. Most characters are encoding with 2 bytes, but that allows to represent at most 65536 characters. +JavaScript uses [Unicode encoding](https://en.wikipedia.org/wiki/Unicode) for strings. Most characters are encoded with 2 bytes, but that allows to represent at most 65536 characters. That range is not big enough to encode all possible characters, that's why some rare characters are encoded with 4 bytes, for instance like `𝒳` (mathematical X) or `😄` (a smile), some hieroglyphs and so on. From c5d108238d75ed7a81718c7949daa4edcffee01b Mon Sep 17 00:00:00 2001 From: YRFT Date: Sat, 29 Feb 2020 12:09:02 +0800 Subject: [PATCH 091/101] Fix typo --- 09-regexp-quantifiers/2-find-html-colors-6hex/solution.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/09-regexp-quantifiers/2-find-html-colors-6hex/solution.md b/09-regexp-quantifiers/2-find-html-colors-6hex/solution.md index b9e1f85a5..afee89c50 100644 --- a/09-regexp-quantifiers/2-find-html-colors-6hex/solution.md +++ b/09-regexp-quantifiers/2-find-html-colors-6hex/solution.md @@ -17,7 +17,7 @@ alert( str.match(regexp) ); // #121212,#AA00ef The problem is that it finds the color in longer sequences: ```js run -alert( "#12345678".match( /#[a-f0-9]{6}/gi ) ) // #12345678 +alert( "#12345678".match( /#[a-f0-9]{6}/gi ) ) // #123456 ``` To fix that, we can add `pattern:\b` to the end: From 1bbd818f31ccf46160c9d40ac5b512373c2ac1e5 Mon Sep 17 00:00:00 2001 From: Kuthumi Pepple Date: Tue, 3 Mar 2020 23:20:19 +0100 Subject: [PATCH 092/101] fix typo Number should be 79031234567 not 79035419441 --- 02-regexp-character-classes/article.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/02-regexp-character-classes/article.md b/02-regexp-character-classes/article.md index dbf932875..7baa6984b 100644 --- a/02-regexp-character-classes/article.md +++ b/02-regexp-character-classes/article.md @@ -1,6 +1,6 @@ # Character classes -Consider a practical task -- we have a phone number like `"+7(903)-123-45-67"`, and we need to turn it into pure numbers: `79035419441`. +Consider a practical task -- we have a phone number like `"+7(903)-123-45-67"`, and we need to turn it into pure numbers: `79031234567`. To do so, we can find and remove anything that's not a number. Character classes can help with that. @@ -30,7 +30,7 @@ let regexp = /\d/g; alert( str.match(regexp) ); // array of matches: 7,9,0,3,1,2,3,4,5,6,7 // let's make the digits-only phone number of them: -alert( str.match(regexp).join('') ); // 79035419441 +alert( str.match(regexp).join('') ); // 79031234567 ``` That was a character class for digits. There are other character classes as well. From 1683870766df5b283e86f7135422b0104b1076bd Mon Sep 17 00:00:00 2001 From: HynekS Date: Wed, 11 Mar 2020 21:57:18 +0100 Subject: [PATCH 093/101] fix: 2 lines of russian to english and a typo (mathces -> matches) --- 11-regexp-groups/article.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/11-regexp-groups/article.md b/11-regexp-groups/article.md index ab25066d7..6fdf727a8 100644 --- a/11-regexp-groups/article.md +++ b/11-regexp-groups/article.md @@ -218,8 +218,8 @@ let results = '

'.matchAll(/<(.*?)>/gi); for(let result of results) { alert(result); - // первый вывод:

,h1 - // второй:

,h2 + // first alert:

,h1 + // second:

,h2 } ``` @@ -249,7 +249,7 @@ The call to `matchAll` does not perform the search. Instead, it returns an itera So, there will be found as many results as needed, not more. -E.g. there are potentially 100 matches in the text, but in a `for..of` loop we found 5 of them, then decided it's enough and make a `break`. Then the engine won't spend time finding other 95 mathces. +E.g. there are potentially 100 matches in the text, but in a `for..of` loop we found 5 of them, then decided it's enough and make a `break`. Then the engine won't spend time finding other 95 matches. ``` ## Named groups From b92202672d399f3d7a5a08b6ddaa0988b81d4de0 Mon Sep 17 00:00:00 2001 From: Ilya Kantor Date: Fri, 3 Apr 2020 01:54:22 +0300 Subject: [PATCH 094/101] fixes #1836 --- 14-regexp-lookahead-lookbehind/article.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/14-regexp-lookahead-lookbehind/article.md b/14-regexp-lookahead-lookbehind/article.md index c7eb28432..48c82da14 100644 --- a/14-regexp-lookahead-lookbehind/article.md +++ b/14-regexp-lookahead-lookbehind/article.md @@ -26,10 +26,10 @@ More complex tests are possible, e.g. `pattern:X(?=Y)(?=Z)` means: 1. Find `pattern:X`. 2. Check if `pattern:Y` is immediately after `pattern:X` (skip if isn't). -3. Check if `pattern:Z` is immediately after `pattern:Y` (skip if isn't). -4. If both tests passed, then it's the match. +3. Check if `pattern:Z` is also immediately after `pattern:X` (skip if isn't). +4. If both tests passed, then the `pattern:X` is a match, otherwise continue searching. -In other words, such pattern means that we're looking for `pattern:X` followed by `pattern:Y` and `pattern:Z` at the same time. +In other words, such pattern means that we're looking for `pattern:X` followed by `pattern:Y` and `pattern:Z` at the same time. That's only possible if patterns `pattern:Y` and `pattern:Z` aren't mutually exclusive. From 5b684eb4b0c0deb0392422e17c1fc480959786d5 Mon Sep 17 00:00:00 2001 From: zhangbao Date: Sun, 5 Apr 2020 22:07:27 +0800 Subject: [PATCH 095/101] fix typo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. then we don't need Array.from, разумеется, не нужен. -> then we don't need Array.from any more. 2. delete the extra table header --- 17-regexp-methods/article.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/17-regexp-methods/article.md b/17-regexp-methods/article.md index ef578020d..d4d00b695 100644 --- a/17-regexp-methods/article.md +++ b/17-regexp-methods/article.md @@ -86,7 +86,7 @@ alert( firstMatch.index ); // 0 alert( firstMatch.input ); //

Hello, world!

``` -If we use `for..of` to loop over `matchAll` matches, then we don't need `Array.from`, разумеется, не нужен. +If we use `for..of` to loop over `matchAll` matches, then we don't need `Array.from` any more. ## str.split(regexp|substr, limit) @@ -144,7 +144,6 @@ alert( '12-34-56'.replace( *!*/-/g*/!*, ":" ) ) // 12:34:56 The second argument is a replacement string. We can use special character in it: -| Symbols | Action in the replacement string | | Symbols | Action in the replacement string | |--------|--------| |`$&`|inserts the whole match| From 7c5a987ee5da4933ca160e73b4cc1b40d174ce99 Mon Sep 17 00:00:00 2001 From: zhangbao Date: Tue, 21 Apr 2020 11:24:15 +0800 Subject: [PATCH 096/101] delete extra content --- 17-regexp-methods/article.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/17-regexp-methods/article.md b/17-regexp-methods/article.md index d4d00b695..e4044361f 100644 --- a/17-regexp-methods/article.md +++ b/17-regexp-methods/article.md @@ -204,9 +204,6 @@ alert(result); // Smith, John If there are many groups, it's convenient to use rest parameters to access them: - -Если в регулярном выражении много скобочных групп, то бывает удобно использовать остаточные аргументы для обращения к ним: - ```js run let str = "John Smith"; From dce80e43673e97aaf5c6caccac0e340428ba4e5d Mon Sep 17 00:00:00 2001 From: Aakodal <48600424+Aakodal@users.noreply.github.com> Date: Fri, 8 May 2020 19:54:28 +0000 Subject: [PATCH 097/101] Adding missing 'pattern:' Adding missing 'pattern:' next to 'm' in the line "Without the flag `m`" --- 05-regexp-multiline-mode/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/05-regexp-multiline-mode/article.md b/05-regexp-multiline-mode/article.md index 539f9fa23..f8ac08ec7 100644 --- a/05-regexp-multiline-mode/article.md +++ b/05-regexp-multiline-mode/article.md @@ -54,7 +54,7 @@ Eeyore: 3`; alert( str.match(/\d$/gm) ); // 1,2,3 ``` -Without the flag `m`, the dollar `pattern:$` would only match the end of the whole text, so only the very last digit would be found. +Without the flag `pattern:m`, the dollar `pattern:$` would only match the end of the whole text, so only the very last digit would be found. ```smart "End of a line" formally means "immediately before a line break": the test `pattern:$` in multiline mode matches at all positions succeeded by a newline character `\n`. From c88d67d3a1db3a8e0ec6ce248636f03f9d8e6e52 Mon Sep 17 00:00:00 2001 From: Ilya Kantor Date: Sun, 17 May 2020 23:44:18 +0300 Subject: [PATCH 098/101] closes #1882 --- 11-regexp-groups/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/11-regexp-groups/article.md b/11-regexp-groups/article.md index 6fdf727a8..e559fd87c 100644 --- a/11-regexp-groups/article.md +++ b/11-regexp-groups/article.md @@ -18,7 +18,7 @@ Without parentheses, the pattern `pattern:go+` means `subject:g` character, foll Parentheses group characters together, so `pattern:(go)+` means `match:go`, `match:gogo`, `match:gogogo` and so on. ```js run -alert( 'Gogogo now!'.match(/(go)+/i) ); // "Gogogo" +alert( 'Gogogo now!'.match(/(go)+/ig) ); // "Gogogo" ``` ### Example: domain From 648ba600c96296c68b4cb98925208f113d80a512 Mon Sep 17 00:00:00 2001 From: Zhi Yin Date: Sun, 17 May 2020 22:08:32 -0400 Subject: [PATCH 099/101] correct minor error --- 06-regexp-boundary/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/06-regexp-boundary/article.md b/06-regexp-boundary/article.md index e4df252a4..aad65877f 100644 --- a/06-regexp-boundary/article.md +++ b/06-regexp-boundary/article.md @@ -27,7 +27,7 @@ So, it matches the pattern `pattern:\bHello\b`, because: 2. Then matches the word `pattern:Hello`. 3. Then the test `pattern:\b` matches again, as we're between `subject:o` and a space. -The pattern `pattern:\bJava\b` would also match. But not `pattern:\bHell\b` (because there's no word boundary after `l`) and not `Java!\b` (because the exclamation sign is not a wordly character `pattern:\w`, so there's no word boundary after it). +The pattern `pattern:\bHello\b` would also match. But not `pattern:\bHell\b` (because there's no word boundary after `l`) and not `Java!\b` (because the exclamation sign is not a wordly character `pattern:\w`, so there's no word boundary after it). ```js run alert( "Hello, Java!".match(/\bHello\b/) ); // Hello From 7aa3d6c8fff640132270f5ea6bd812d3ffa073c8 Mon Sep 17 00:00:00 2001 From: Zhi Yin Date: Tue, 19 May 2020 14:54:36 -0400 Subject: [PATCH 100/101] =?UTF-8?q?correct=20minor=20error:=20"=D0=B8?= =?UTF-8?q?=D0=BC=D1=8F"=20is=20used=20in=20place=20of=20"name"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 12-regexp-backreferences/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/12-regexp-backreferences/article.md b/12-regexp-backreferences/article.md index 83beb803a..b80fa85cf 100644 --- a/12-regexp-backreferences/article.md +++ b/12-regexp-backreferences/article.md @@ -57,7 +57,7 @@ In the replacement string we use a dollar sign: `pattern:$1`, while in the patte If a regexp has many parentheses, it's convenient to give them names. -To reference a named group we can use `pattern:\k<имя>`. +To reference a named group we can use `pattern:\k`. In the example below the group with quotes is named `pattern:?`, so the backreference is `pattern:\k`: From bfbf4b3f3cd9f3df60d127cda8348bcd71adf867 Mon Sep 17 00:00:00 2001 From: odsantos Date: Mon, 25 May 2020 23:57:15 +0100 Subject: [PATCH 101/101] Update folder 9-regular-expressions --- .../01-regexp-introduction}/article.md | 0 .../02-regexp-character-classes}/article.md | 0 .../02-regexp-character-classes}/love-html5-classes.svg | 0 .../03-regexp-unicode}/article.md | 0 .../04-regexp-anchors}/1-start-end/solution.md | 0 .../04-regexp-anchors}/1-start-end/task.md | 0 .../04-regexp-anchors}/article.md | 0 .../05-regexp-multiline-mode}/article.md | 0 .../06-regexp-boundary}/1-find-time-hh-mm/solution.md | 0 .../06-regexp-boundary}/1-find-time-hh-mm/task.md | 0 .../06-regexp-boundary}/article.md | 0 .../06-regexp-boundary}/hello-java-boundaries.svg | 0 .../07-regexp-escaping}/article.md | 0 .../1-find-range-1/solution.md | 0 .../08-regexp-character-sets-and-ranges}/1-find-range-1/task.md | 0 .../2-find-time-2-formats/solution.md | 0 .../2-find-time-2-formats/task.md | 0 .../08-regexp-character-sets-and-ranges}/article.md | 0 .../09-regexp-quantifiers}/1-find-text-manydots/solution.md | 0 .../09-regexp-quantifiers}/1-find-text-manydots/task.md | 0 .../09-regexp-quantifiers}/2-find-html-colors-6hex/solution.md | 0 .../09-regexp-quantifiers}/2-find-html-colors-6hex/task.md | 0 .../09-regexp-quantifiers}/article.md | 0 .../10-regexp-greedy-and-lazy}/1-lazy-greedy/solution.md | 0 .../10-regexp-greedy-and-lazy}/1-lazy-greedy/task.md | 0 .../10-regexp-greedy-and-lazy}/3-find-html-comments/solution.md | 0 .../10-regexp-greedy-and-lazy}/3-find-html-comments/task.md | 0 .../4-find-html-tags-greedy-lazy/solution.md | 0 .../4-find-html-tags-greedy-lazy/task.md | 0 .../10-regexp-greedy-and-lazy}/article.md | 0 .../10-regexp-greedy-and-lazy}/witch_greedy1.svg | 0 .../10-regexp-greedy-and-lazy}/witch_greedy2.svg | 0 .../10-regexp-greedy-and-lazy}/witch_greedy3.svg | 0 .../10-regexp-greedy-and-lazy}/witch_greedy4.svg | 0 .../10-regexp-greedy-and-lazy}/witch_greedy5.svg | 0 .../10-regexp-greedy-and-lazy}/witch_greedy6.svg | 0 .../10-regexp-greedy-and-lazy}/witch_lazy3.svg | 0 .../10-regexp-greedy-and-lazy}/witch_lazy4.svg | 0 .../10-regexp-greedy-and-lazy}/witch_lazy5.svg | 0 .../10-regexp-greedy-and-lazy}/witch_lazy6.svg | 0 .../11-regexp-groups}/01-test-mac/solution.md | 0 .../11-regexp-groups}/01-test-mac/task.md | 0 .../11-regexp-groups}/02-find-webcolor-3-or-6/solution.md | 0 .../11-regexp-groups}/02-find-webcolor-3-or-6/task.md | 0 .../11-regexp-groups}/03-find-decimal-numbers/solution.md | 0 .../11-regexp-groups}/03-find-decimal-numbers/task.md | 0 .../11-regexp-groups}/04-parse-expression/solution.md | 0 .../11-regexp-groups}/04-parse-expression/task.md | 0 .../11-regexp-groups}/article.md | 0 .../11-regexp-groups}/regexp-nested-groups-matches.svg | 0 .../11-regexp-groups}/regexp-nested-groups-pattern.svg | 0 .../12-regexp-backreferences}/article.md | 0 .../01-find-programming-language/solution.md | 0 .../13-regexp-alternation}/01-find-programming-language/task.md | 0 .../13-regexp-alternation}/02-find-matching-bbtags/solution.md | 0 .../13-regexp-alternation}/02-find-matching-bbtags/task.md | 0 .../13-regexp-alternation}/03-match-quoted-string/solution.md | 0 .../13-regexp-alternation}/03-match-quoted-string/task.md | 0 .../13-regexp-alternation}/04-match-exact-tag/solution.md | 0 .../13-regexp-alternation}/04-match-exact-tag/task.md | 0 .../13-regexp-alternation}/article.md | 0 .../1-find-non-negative-integers/solution.md | 0 .../1-find-non-negative-integers/task.md | 0 .../2-insert-after-head/solution.md | 0 .../14-regexp-lookahead-lookbehind}/2-insert-after-head/task.md | 0 .../14-regexp-lookahead-lookbehind}/article.md | 0 .../15-regexp-catastrophic-backtracking}/article.md | 0 .../16-regexp-sticky}/article.md | 0 .../17-regexp-methods}/article.md | 0 index.md => 9-regular-expressions/index.md | 0 70 files changed, 0 insertions(+), 0 deletions(-) rename {01-regexp-introduction => 9-regular-expressions/01-regexp-introduction}/article.md (100%) rename {02-regexp-character-classes => 9-regular-expressions/02-regexp-character-classes}/article.md (100%) rename {02-regexp-character-classes => 9-regular-expressions/02-regexp-character-classes}/love-html5-classes.svg (100%) rename {03-regexp-unicode => 9-regular-expressions/03-regexp-unicode}/article.md (100%) rename {04-regexp-anchors => 9-regular-expressions/04-regexp-anchors}/1-start-end/solution.md (100%) rename {04-regexp-anchors => 9-regular-expressions/04-regexp-anchors}/1-start-end/task.md (100%) rename {04-regexp-anchors => 9-regular-expressions/04-regexp-anchors}/article.md (100%) rename {05-regexp-multiline-mode => 9-regular-expressions/05-regexp-multiline-mode}/article.md (100%) rename {06-regexp-boundary => 9-regular-expressions/06-regexp-boundary}/1-find-time-hh-mm/solution.md (100%) rename {06-regexp-boundary => 9-regular-expressions/06-regexp-boundary}/1-find-time-hh-mm/task.md (100%) rename {06-regexp-boundary => 9-regular-expressions/06-regexp-boundary}/article.md (100%) rename {06-regexp-boundary => 9-regular-expressions/06-regexp-boundary}/hello-java-boundaries.svg (100%) rename {07-regexp-escaping => 9-regular-expressions/07-regexp-escaping}/article.md (100%) rename {08-regexp-character-sets-and-ranges => 9-regular-expressions/08-regexp-character-sets-and-ranges}/1-find-range-1/solution.md (100%) rename {08-regexp-character-sets-and-ranges => 9-regular-expressions/08-regexp-character-sets-and-ranges}/1-find-range-1/task.md (100%) rename {08-regexp-character-sets-and-ranges => 9-regular-expressions/08-regexp-character-sets-and-ranges}/2-find-time-2-formats/solution.md (100%) rename {08-regexp-character-sets-and-ranges => 9-regular-expressions/08-regexp-character-sets-and-ranges}/2-find-time-2-formats/task.md (100%) rename {08-regexp-character-sets-and-ranges => 9-regular-expressions/08-regexp-character-sets-and-ranges}/article.md (100%) rename {09-regexp-quantifiers => 9-regular-expressions/09-regexp-quantifiers}/1-find-text-manydots/solution.md (100%) rename {09-regexp-quantifiers => 9-regular-expressions/09-regexp-quantifiers}/1-find-text-manydots/task.md (100%) rename {09-regexp-quantifiers => 9-regular-expressions/09-regexp-quantifiers}/2-find-html-colors-6hex/solution.md (100%) rename {09-regexp-quantifiers => 9-regular-expressions/09-regexp-quantifiers}/2-find-html-colors-6hex/task.md (100%) rename {09-regexp-quantifiers => 9-regular-expressions/09-regexp-quantifiers}/article.md (100%) rename {10-regexp-greedy-and-lazy => 9-regular-expressions/10-regexp-greedy-and-lazy}/1-lazy-greedy/solution.md (100%) rename {10-regexp-greedy-and-lazy => 9-regular-expressions/10-regexp-greedy-and-lazy}/1-lazy-greedy/task.md (100%) rename {10-regexp-greedy-and-lazy => 9-regular-expressions/10-regexp-greedy-and-lazy}/3-find-html-comments/solution.md (100%) rename {10-regexp-greedy-and-lazy => 9-regular-expressions/10-regexp-greedy-and-lazy}/3-find-html-comments/task.md (100%) rename {10-regexp-greedy-and-lazy => 9-regular-expressions/10-regexp-greedy-and-lazy}/4-find-html-tags-greedy-lazy/solution.md (100%) rename {10-regexp-greedy-and-lazy => 9-regular-expressions/10-regexp-greedy-and-lazy}/4-find-html-tags-greedy-lazy/task.md (100%) rename {10-regexp-greedy-and-lazy => 9-regular-expressions/10-regexp-greedy-and-lazy}/article.md (100%) rename {10-regexp-greedy-and-lazy => 9-regular-expressions/10-regexp-greedy-and-lazy}/witch_greedy1.svg (100%) rename {10-regexp-greedy-and-lazy => 9-regular-expressions/10-regexp-greedy-and-lazy}/witch_greedy2.svg (100%) rename {10-regexp-greedy-and-lazy => 9-regular-expressions/10-regexp-greedy-and-lazy}/witch_greedy3.svg (100%) rename {10-regexp-greedy-and-lazy => 9-regular-expressions/10-regexp-greedy-and-lazy}/witch_greedy4.svg (100%) rename {10-regexp-greedy-and-lazy => 9-regular-expressions/10-regexp-greedy-and-lazy}/witch_greedy5.svg (100%) rename {10-regexp-greedy-and-lazy => 9-regular-expressions/10-regexp-greedy-and-lazy}/witch_greedy6.svg (100%) rename {10-regexp-greedy-and-lazy => 9-regular-expressions/10-regexp-greedy-and-lazy}/witch_lazy3.svg (100%) rename {10-regexp-greedy-and-lazy => 9-regular-expressions/10-regexp-greedy-and-lazy}/witch_lazy4.svg (100%) rename {10-regexp-greedy-and-lazy => 9-regular-expressions/10-regexp-greedy-and-lazy}/witch_lazy5.svg (100%) rename {10-regexp-greedy-and-lazy => 9-regular-expressions/10-regexp-greedy-and-lazy}/witch_lazy6.svg (100%) rename {11-regexp-groups => 9-regular-expressions/11-regexp-groups}/01-test-mac/solution.md (100%) rename {11-regexp-groups => 9-regular-expressions/11-regexp-groups}/01-test-mac/task.md (100%) rename {11-regexp-groups => 9-regular-expressions/11-regexp-groups}/02-find-webcolor-3-or-6/solution.md (100%) rename {11-regexp-groups => 9-regular-expressions/11-regexp-groups}/02-find-webcolor-3-or-6/task.md (100%) rename {11-regexp-groups => 9-regular-expressions/11-regexp-groups}/03-find-decimal-numbers/solution.md (100%) rename {11-regexp-groups => 9-regular-expressions/11-regexp-groups}/03-find-decimal-numbers/task.md (100%) rename {11-regexp-groups => 9-regular-expressions/11-regexp-groups}/04-parse-expression/solution.md (100%) rename {11-regexp-groups => 9-regular-expressions/11-regexp-groups}/04-parse-expression/task.md (100%) rename {11-regexp-groups => 9-regular-expressions/11-regexp-groups}/article.md (100%) rename {11-regexp-groups => 9-regular-expressions/11-regexp-groups}/regexp-nested-groups-matches.svg (100%) rename {11-regexp-groups => 9-regular-expressions/11-regexp-groups}/regexp-nested-groups-pattern.svg (100%) rename {12-regexp-backreferences => 9-regular-expressions/12-regexp-backreferences}/article.md (100%) rename {13-regexp-alternation => 9-regular-expressions/13-regexp-alternation}/01-find-programming-language/solution.md (100%) rename {13-regexp-alternation => 9-regular-expressions/13-regexp-alternation}/01-find-programming-language/task.md (100%) rename {13-regexp-alternation => 9-regular-expressions/13-regexp-alternation}/02-find-matching-bbtags/solution.md (100%) rename {13-regexp-alternation => 9-regular-expressions/13-regexp-alternation}/02-find-matching-bbtags/task.md (100%) rename {13-regexp-alternation => 9-regular-expressions/13-regexp-alternation}/03-match-quoted-string/solution.md (100%) rename {13-regexp-alternation => 9-regular-expressions/13-regexp-alternation}/03-match-quoted-string/task.md (100%) rename {13-regexp-alternation => 9-regular-expressions/13-regexp-alternation}/04-match-exact-tag/solution.md (100%) rename {13-regexp-alternation => 9-regular-expressions/13-regexp-alternation}/04-match-exact-tag/task.md (100%) rename {13-regexp-alternation => 9-regular-expressions/13-regexp-alternation}/article.md (100%) rename {14-regexp-lookahead-lookbehind => 9-regular-expressions/14-regexp-lookahead-lookbehind}/1-find-non-negative-integers/solution.md (100%) rename {14-regexp-lookahead-lookbehind => 9-regular-expressions/14-regexp-lookahead-lookbehind}/1-find-non-negative-integers/task.md (100%) rename {14-regexp-lookahead-lookbehind => 9-regular-expressions/14-regexp-lookahead-lookbehind}/2-insert-after-head/solution.md (100%) rename {14-regexp-lookahead-lookbehind => 9-regular-expressions/14-regexp-lookahead-lookbehind}/2-insert-after-head/task.md (100%) rename {14-regexp-lookahead-lookbehind => 9-regular-expressions/14-regexp-lookahead-lookbehind}/article.md (100%) rename {15-regexp-catastrophic-backtracking => 9-regular-expressions/15-regexp-catastrophic-backtracking}/article.md (100%) rename {16-regexp-sticky => 9-regular-expressions/16-regexp-sticky}/article.md (100%) rename {17-regexp-methods => 9-regular-expressions/17-regexp-methods}/article.md (100%) rename index.md => 9-regular-expressions/index.md (100%) diff --git a/01-regexp-introduction/article.md b/9-regular-expressions/01-regexp-introduction/article.md similarity index 100% rename from 01-regexp-introduction/article.md rename to 9-regular-expressions/01-regexp-introduction/article.md diff --git a/02-regexp-character-classes/article.md b/9-regular-expressions/02-regexp-character-classes/article.md similarity index 100% rename from 02-regexp-character-classes/article.md rename to 9-regular-expressions/02-regexp-character-classes/article.md diff --git a/02-regexp-character-classes/love-html5-classes.svg b/9-regular-expressions/02-regexp-character-classes/love-html5-classes.svg similarity index 100% rename from 02-regexp-character-classes/love-html5-classes.svg rename to 9-regular-expressions/02-regexp-character-classes/love-html5-classes.svg diff --git a/03-regexp-unicode/article.md b/9-regular-expressions/03-regexp-unicode/article.md similarity index 100% rename from 03-regexp-unicode/article.md rename to 9-regular-expressions/03-regexp-unicode/article.md diff --git a/04-regexp-anchors/1-start-end/solution.md b/9-regular-expressions/04-regexp-anchors/1-start-end/solution.md similarity index 100% rename from 04-regexp-anchors/1-start-end/solution.md rename to 9-regular-expressions/04-regexp-anchors/1-start-end/solution.md diff --git a/04-regexp-anchors/1-start-end/task.md b/9-regular-expressions/04-regexp-anchors/1-start-end/task.md similarity index 100% rename from 04-regexp-anchors/1-start-end/task.md rename to 9-regular-expressions/04-regexp-anchors/1-start-end/task.md diff --git a/04-regexp-anchors/article.md b/9-regular-expressions/04-regexp-anchors/article.md similarity index 100% rename from 04-regexp-anchors/article.md rename to 9-regular-expressions/04-regexp-anchors/article.md diff --git a/05-regexp-multiline-mode/article.md b/9-regular-expressions/05-regexp-multiline-mode/article.md similarity index 100% rename from 05-regexp-multiline-mode/article.md rename to 9-regular-expressions/05-regexp-multiline-mode/article.md diff --git a/06-regexp-boundary/1-find-time-hh-mm/solution.md b/9-regular-expressions/06-regexp-boundary/1-find-time-hh-mm/solution.md similarity index 100% rename from 06-regexp-boundary/1-find-time-hh-mm/solution.md rename to 9-regular-expressions/06-regexp-boundary/1-find-time-hh-mm/solution.md diff --git a/06-regexp-boundary/1-find-time-hh-mm/task.md b/9-regular-expressions/06-regexp-boundary/1-find-time-hh-mm/task.md similarity index 100% rename from 06-regexp-boundary/1-find-time-hh-mm/task.md rename to 9-regular-expressions/06-regexp-boundary/1-find-time-hh-mm/task.md diff --git a/06-regexp-boundary/article.md b/9-regular-expressions/06-regexp-boundary/article.md similarity index 100% rename from 06-regexp-boundary/article.md rename to 9-regular-expressions/06-regexp-boundary/article.md diff --git a/06-regexp-boundary/hello-java-boundaries.svg b/9-regular-expressions/06-regexp-boundary/hello-java-boundaries.svg similarity index 100% rename from 06-regexp-boundary/hello-java-boundaries.svg rename to 9-regular-expressions/06-regexp-boundary/hello-java-boundaries.svg diff --git a/07-regexp-escaping/article.md b/9-regular-expressions/07-regexp-escaping/article.md similarity index 100% rename from 07-regexp-escaping/article.md rename to 9-regular-expressions/07-regexp-escaping/article.md diff --git a/08-regexp-character-sets-and-ranges/1-find-range-1/solution.md b/9-regular-expressions/08-regexp-character-sets-and-ranges/1-find-range-1/solution.md similarity index 100% rename from 08-regexp-character-sets-and-ranges/1-find-range-1/solution.md rename to 9-regular-expressions/08-regexp-character-sets-and-ranges/1-find-range-1/solution.md diff --git a/08-regexp-character-sets-and-ranges/1-find-range-1/task.md b/9-regular-expressions/08-regexp-character-sets-and-ranges/1-find-range-1/task.md similarity index 100% rename from 08-regexp-character-sets-and-ranges/1-find-range-1/task.md rename to 9-regular-expressions/08-regexp-character-sets-and-ranges/1-find-range-1/task.md diff --git a/08-regexp-character-sets-and-ranges/2-find-time-2-formats/solution.md b/9-regular-expressions/08-regexp-character-sets-and-ranges/2-find-time-2-formats/solution.md similarity index 100% rename from 08-regexp-character-sets-and-ranges/2-find-time-2-formats/solution.md rename to 9-regular-expressions/08-regexp-character-sets-and-ranges/2-find-time-2-formats/solution.md diff --git a/08-regexp-character-sets-and-ranges/2-find-time-2-formats/task.md b/9-regular-expressions/08-regexp-character-sets-and-ranges/2-find-time-2-formats/task.md similarity index 100% rename from 08-regexp-character-sets-and-ranges/2-find-time-2-formats/task.md rename to 9-regular-expressions/08-regexp-character-sets-and-ranges/2-find-time-2-formats/task.md diff --git a/08-regexp-character-sets-and-ranges/article.md b/9-regular-expressions/08-regexp-character-sets-and-ranges/article.md similarity index 100% rename from 08-regexp-character-sets-and-ranges/article.md rename to 9-regular-expressions/08-regexp-character-sets-and-ranges/article.md diff --git a/09-regexp-quantifiers/1-find-text-manydots/solution.md b/9-regular-expressions/09-regexp-quantifiers/1-find-text-manydots/solution.md similarity index 100% rename from 09-regexp-quantifiers/1-find-text-manydots/solution.md rename to 9-regular-expressions/09-regexp-quantifiers/1-find-text-manydots/solution.md diff --git a/09-regexp-quantifiers/1-find-text-manydots/task.md b/9-regular-expressions/09-regexp-quantifiers/1-find-text-manydots/task.md similarity index 100% rename from 09-regexp-quantifiers/1-find-text-manydots/task.md rename to 9-regular-expressions/09-regexp-quantifiers/1-find-text-manydots/task.md diff --git a/09-regexp-quantifiers/2-find-html-colors-6hex/solution.md b/9-regular-expressions/09-regexp-quantifiers/2-find-html-colors-6hex/solution.md similarity index 100% rename from 09-regexp-quantifiers/2-find-html-colors-6hex/solution.md rename to 9-regular-expressions/09-regexp-quantifiers/2-find-html-colors-6hex/solution.md diff --git a/09-regexp-quantifiers/2-find-html-colors-6hex/task.md b/9-regular-expressions/09-regexp-quantifiers/2-find-html-colors-6hex/task.md similarity index 100% rename from 09-regexp-quantifiers/2-find-html-colors-6hex/task.md rename to 9-regular-expressions/09-regexp-quantifiers/2-find-html-colors-6hex/task.md diff --git a/09-regexp-quantifiers/article.md b/9-regular-expressions/09-regexp-quantifiers/article.md similarity index 100% rename from 09-regexp-quantifiers/article.md rename to 9-regular-expressions/09-regexp-quantifiers/article.md diff --git a/10-regexp-greedy-and-lazy/1-lazy-greedy/solution.md b/9-regular-expressions/10-regexp-greedy-and-lazy/1-lazy-greedy/solution.md similarity index 100% rename from 10-regexp-greedy-and-lazy/1-lazy-greedy/solution.md rename to 9-regular-expressions/10-regexp-greedy-and-lazy/1-lazy-greedy/solution.md diff --git a/10-regexp-greedy-and-lazy/1-lazy-greedy/task.md b/9-regular-expressions/10-regexp-greedy-and-lazy/1-lazy-greedy/task.md similarity index 100% rename from 10-regexp-greedy-and-lazy/1-lazy-greedy/task.md rename to 9-regular-expressions/10-regexp-greedy-and-lazy/1-lazy-greedy/task.md diff --git a/10-regexp-greedy-and-lazy/3-find-html-comments/solution.md b/9-regular-expressions/10-regexp-greedy-and-lazy/3-find-html-comments/solution.md similarity index 100% rename from 10-regexp-greedy-and-lazy/3-find-html-comments/solution.md rename to 9-regular-expressions/10-regexp-greedy-and-lazy/3-find-html-comments/solution.md diff --git a/10-regexp-greedy-and-lazy/3-find-html-comments/task.md b/9-regular-expressions/10-regexp-greedy-and-lazy/3-find-html-comments/task.md similarity index 100% rename from 10-regexp-greedy-and-lazy/3-find-html-comments/task.md rename to 9-regular-expressions/10-regexp-greedy-and-lazy/3-find-html-comments/task.md diff --git a/10-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/solution.md b/9-regular-expressions/10-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/solution.md similarity index 100% rename from 10-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/solution.md rename to 9-regular-expressions/10-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/solution.md diff --git a/10-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/task.md b/9-regular-expressions/10-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/task.md similarity index 100% rename from 10-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/task.md rename to 9-regular-expressions/10-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/task.md diff --git a/10-regexp-greedy-and-lazy/article.md b/9-regular-expressions/10-regexp-greedy-and-lazy/article.md similarity index 100% rename from 10-regexp-greedy-and-lazy/article.md rename to 9-regular-expressions/10-regexp-greedy-and-lazy/article.md diff --git a/10-regexp-greedy-and-lazy/witch_greedy1.svg b/9-regular-expressions/10-regexp-greedy-and-lazy/witch_greedy1.svg similarity index 100% rename from 10-regexp-greedy-and-lazy/witch_greedy1.svg rename to 9-regular-expressions/10-regexp-greedy-and-lazy/witch_greedy1.svg diff --git a/10-regexp-greedy-and-lazy/witch_greedy2.svg b/9-regular-expressions/10-regexp-greedy-and-lazy/witch_greedy2.svg similarity index 100% rename from 10-regexp-greedy-and-lazy/witch_greedy2.svg rename to 9-regular-expressions/10-regexp-greedy-and-lazy/witch_greedy2.svg diff --git a/10-regexp-greedy-and-lazy/witch_greedy3.svg b/9-regular-expressions/10-regexp-greedy-and-lazy/witch_greedy3.svg similarity index 100% rename from 10-regexp-greedy-and-lazy/witch_greedy3.svg rename to 9-regular-expressions/10-regexp-greedy-and-lazy/witch_greedy3.svg diff --git a/10-regexp-greedy-and-lazy/witch_greedy4.svg b/9-regular-expressions/10-regexp-greedy-and-lazy/witch_greedy4.svg similarity index 100% rename from 10-regexp-greedy-and-lazy/witch_greedy4.svg rename to 9-regular-expressions/10-regexp-greedy-and-lazy/witch_greedy4.svg diff --git a/10-regexp-greedy-and-lazy/witch_greedy5.svg b/9-regular-expressions/10-regexp-greedy-and-lazy/witch_greedy5.svg similarity index 100% rename from 10-regexp-greedy-and-lazy/witch_greedy5.svg rename to 9-regular-expressions/10-regexp-greedy-and-lazy/witch_greedy5.svg diff --git a/10-regexp-greedy-and-lazy/witch_greedy6.svg b/9-regular-expressions/10-regexp-greedy-and-lazy/witch_greedy6.svg similarity index 100% rename from 10-regexp-greedy-and-lazy/witch_greedy6.svg rename to 9-regular-expressions/10-regexp-greedy-and-lazy/witch_greedy6.svg diff --git a/10-regexp-greedy-and-lazy/witch_lazy3.svg b/9-regular-expressions/10-regexp-greedy-and-lazy/witch_lazy3.svg similarity index 100% rename from 10-regexp-greedy-and-lazy/witch_lazy3.svg rename to 9-regular-expressions/10-regexp-greedy-and-lazy/witch_lazy3.svg diff --git a/10-regexp-greedy-and-lazy/witch_lazy4.svg b/9-regular-expressions/10-regexp-greedy-and-lazy/witch_lazy4.svg similarity index 100% rename from 10-regexp-greedy-and-lazy/witch_lazy4.svg rename to 9-regular-expressions/10-regexp-greedy-and-lazy/witch_lazy4.svg diff --git a/10-regexp-greedy-and-lazy/witch_lazy5.svg b/9-regular-expressions/10-regexp-greedy-and-lazy/witch_lazy5.svg similarity index 100% rename from 10-regexp-greedy-and-lazy/witch_lazy5.svg rename to 9-regular-expressions/10-regexp-greedy-and-lazy/witch_lazy5.svg diff --git a/10-regexp-greedy-and-lazy/witch_lazy6.svg b/9-regular-expressions/10-regexp-greedy-and-lazy/witch_lazy6.svg similarity index 100% rename from 10-regexp-greedy-and-lazy/witch_lazy6.svg rename to 9-regular-expressions/10-regexp-greedy-and-lazy/witch_lazy6.svg diff --git a/11-regexp-groups/01-test-mac/solution.md b/9-regular-expressions/11-regexp-groups/01-test-mac/solution.md similarity index 100% rename from 11-regexp-groups/01-test-mac/solution.md rename to 9-regular-expressions/11-regexp-groups/01-test-mac/solution.md diff --git a/11-regexp-groups/01-test-mac/task.md b/9-regular-expressions/11-regexp-groups/01-test-mac/task.md similarity index 100% rename from 11-regexp-groups/01-test-mac/task.md rename to 9-regular-expressions/11-regexp-groups/01-test-mac/task.md diff --git a/11-regexp-groups/02-find-webcolor-3-or-6/solution.md b/9-regular-expressions/11-regexp-groups/02-find-webcolor-3-or-6/solution.md similarity index 100% rename from 11-regexp-groups/02-find-webcolor-3-or-6/solution.md rename to 9-regular-expressions/11-regexp-groups/02-find-webcolor-3-or-6/solution.md diff --git a/11-regexp-groups/02-find-webcolor-3-or-6/task.md b/9-regular-expressions/11-regexp-groups/02-find-webcolor-3-or-6/task.md similarity index 100% rename from 11-regexp-groups/02-find-webcolor-3-or-6/task.md rename to 9-regular-expressions/11-regexp-groups/02-find-webcolor-3-or-6/task.md diff --git a/11-regexp-groups/03-find-decimal-numbers/solution.md b/9-regular-expressions/11-regexp-groups/03-find-decimal-numbers/solution.md similarity index 100% rename from 11-regexp-groups/03-find-decimal-numbers/solution.md rename to 9-regular-expressions/11-regexp-groups/03-find-decimal-numbers/solution.md diff --git a/11-regexp-groups/03-find-decimal-numbers/task.md b/9-regular-expressions/11-regexp-groups/03-find-decimal-numbers/task.md similarity index 100% rename from 11-regexp-groups/03-find-decimal-numbers/task.md rename to 9-regular-expressions/11-regexp-groups/03-find-decimal-numbers/task.md diff --git a/11-regexp-groups/04-parse-expression/solution.md b/9-regular-expressions/11-regexp-groups/04-parse-expression/solution.md similarity index 100% rename from 11-regexp-groups/04-parse-expression/solution.md rename to 9-regular-expressions/11-regexp-groups/04-parse-expression/solution.md diff --git a/11-regexp-groups/04-parse-expression/task.md b/9-regular-expressions/11-regexp-groups/04-parse-expression/task.md similarity index 100% rename from 11-regexp-groups/04-parse-expression/task.md rename to 9-regular-expressions/11-regexp-groups/04-parse-expression/task.md diff --git a/11-regexp-groups/article.md b/9-regular-expressions/11-regexp-groups/article.md similarity index 100% rename from 11-regexp-groups/article.md rename to 9-regular-expressions/11-regexp-groups/article.md diff --git a/11-regexp-groups/regexp-nested-groups-matches.svg b/9-regular-expressions/11-regexp-groups/regexp-nested-groups-matches.svg similarity index 100% rename from 11-regexp-groups/regexp-nested-groups-matches.svg rename to 9-regular-expressions/11-regexp-groups/regexp-nested-groups-matches.svg diff --git a/11-regexp-groups/regexp-nested-groups-pattern.svg b/9-regular-expressions/11-regexp-groups/regexp-nested-groups-pattern.svg similarity index 100% rename from 11-regexp-groups/regexp-nested-groups-pattern.svg rename to 9-regular-expressions/11-regexp-groups/regexp-nested-groups-pattern.svg diff --git a/12-regexp-backreferences/article.md b/9-regular-expressions/12-regexp-backreferences/article.md similarity index 100% rename from 12-regexp-backreferences/article.md rename to 9-regular-expressions/12-regexp-backreferences/article.md diff --git a/13-regexp-alternation/01-find-programming-language/solution.md b/9-regular-expressions/13-regexp-alternation/01-find-programming-language/solution.md similarity index 100% rename from 13-regexp-alternation/01-find-programming-language/solution.md rename to 9-regular-expressions/13-regexp-alternation/01-find-programming-language/solution.md diff --git a/13-regexp-alternation/01-find-programming-language/task.md b/9-regular-expressions/13-regexp-alternation/01-find-programming-language/task.md similarity index 100% rename from 13-regexp-alternation/01-find-programming-language/task.md rename to 9-regular-expressions/13-regexp-alternation/01-find-programming-language/task.md diff --git a/13-regexp-alternation/02-find-matching-bbtags/solution.md b/9-regular-expressions/13-regexp-alternation/02-find-matching-bbtags/solution.md similarity index 100% rename from 13-regexp-alternation/02-find-matching-bbtags/solution.md rename to 9-regular-expressions/13-regexp-alternation/02-find-matching-bbtags/solution.md diff --git a/13-regexp-alternation/02-find-matching-bbtags/task.md b/9-regular-expressions/13-regexp-alternation/02-find-matching-bbtags/task.md similarity index 100% rename from 13-regexp-alternation/02-find-matching-bbtags/task.md rename to 9-regular-expressions/13-regexp-alternation/02-find-matching-bbtags/task.md diff --git a/13-regexp-alternation/03-match-quoted-string/solution.md b/9-regular-expressions/13-regexp-alternation/03-match-quoted-string/solution.md similarity index 100% rename from 13-regexp-alternation/03-match-quoted-string/solution.md rename to 9-regular-expressions/13-regexp-alternation/03-match-quoted-string/solution.md diff --git a/13-regexp-alternation/03-match-quoted-string/task.md b/9-regular-expressions/13-regexp-alternation/03-match-quoted-string/task.md similarity index 100% rename from 13-regexp-alternation/03-match-quoted-string/task.md rename to 9-regular-expressions/13-regexp-alternation/03-match-quoted-string/task.md diff --git a/13-regexp-alternation/04-match-exact-tag/solution.md b/9-regular-expressions/13-regexp-alternation/04-match-exact-tag/solution.md similarity index 100% rename from 13-regexp-alternation/04-match-exact-tag/solution.md rename to 9-regular-expressions/13-regexp-alternation/04-match-exact-tag/solution.md diff --git a/13-regexp-alternation/04-match-exact-tag/task.md b/9-regular-expressions/13-regexp-alternation/04-match-exact-tag/task.md similarity index 100% rename from 13-regexp-alternation/04-match-exact-tag/task.md rename to 9-regular-expressions/13-regexp-alternation/04-match-exact-tag/task.md diff --git a/13-regexp-alternation/article.md b/9-regular-expressions/13-regexp-alternation/article.md similarity index 100% rename from 13-regexp-alternation/article.md rename to 9-regular-expressions/13-regexp-alternation/article.md diff --git a/14-regexp-lookahead-lookbehind/1-find-non-negative-integers/solution.md b/9-regular-expressions/14-regexp-lookahead-lookbehind/1-find-non-negative-integers/solution.md similarity index 100% rename from 14-regexp-lookahead-lookbehind/1-find-non-negative-integers/solution.md rename to 9-regular-expressions/14-regexp-lookahead-lookbehind/1-find-non-negative-integers/solution.md diff --git a/14-regexp-lookahead-lookbehind/1-find-non-negative-integers/task.md b/9-regular-expressions/14-regexp-lookahead-lookbehind/1-find-non-negative-integers/task.md similarity index 100% rename from 14-regexp-lookahead-lookbehind/1-find-non-negative-integers/task.md rename to 9-regular-expressions/14-regexp-lookahead-lookbehind/1-find-non-negative-integers/task.md diff --git a/14-regexp-lookahead-lookbehind/2-insert-after-head/solution.md b/9-regular-expressions/14-regexp-lookahead-lookbehind/2-insert-after-head/solution.md similarity index 100% rename from 14-regexp-lookahead-lookbehind/2-insert-after-head/solution.md rename to 9-regular-expressions/14-regexp-lookahead-lookbehind/2-insert-after-head/solution.md diff --git a/14-regexp-lookahead-lookbehind/2-insert-after-head/task.md b/9-regular-expressions/14-regexp-lookahead-lookbehind/2-insert-after-head/task.md similarity index 100% rename from 14-regexp-lookahead-lookbehind/2-insert-after-head/task.md rename to 9-regular-expressions/14-regexp-lookahead-lookbehind/2-insert-after-head/task.md diff --git a/14-regexp-lookahead-lookbehind/article.md b/9-regular-expressions/14-regexp-lookahead-lookbehind/article.md similarity index 100% rename from 14-regexp-lookahead-lookbehind/article.md rename to 9-regular-expressions/14-regexp-lookahead-lookbehind/article.md diff --git a/15-regexp-catastrophic-backtracking/article.md b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md similarity index 100% rename from 15-regexp-catastrophic-backtracking/article.md rename to 9-regular-expressions/15-regexp-catastrophic-backtracking/article.md diff --git a/16-regexp-sticky/article.md b/9-regular-expressions/16-regexp-sticky/article.md similarity index 100% rename from 16-regexp-sticky/article.md rename to 9-regular-expressions/16-regexp-sticky/article.md diff --git a/17-regexp-methods/article.md b/9-regular-expressions/17-regexp-methods/article.md similarity index 100% rename from 17-regexp-methods/article.md rename to 9-regular-expressions/17-regexp-methods/article.md diff --git a/index.md b/9-regular-expressions/index.md similarity index 100% rename from index.md rename to 9-regular-expressions/index.md