Прапорець y
дозволяє виконувати пошук на вказаній позиції у вихідному рядку.
Щоб дізнатись як використовувати прапорець y
, і краще зрозуміти шляхи використання регулярних виразів, розгляньмо приклад з практики.
Одним із поширених завдань для регулярних виразів є “лексичний аналіз”: для прикладу ми розглядаємо тест написаний певною мовою програмування і хочемо виділити структурні елементи. Наприклад, HTML містить теги та атрибути, код JavaScript – функції, змінні тощо.
Написання лексичних аналізаторів – це особлива сфера, зі своїми інструментами та алгоритмами, тому ми не будемо заглиблюватись в неї, а зосередимось на звичайному завданні: прочитати щось на заданій позиції.
Наприклад, у нас є рядок коду let varName = "значення"
, і нам потрібно прочитати з нього назву змінної, яка починається з позиції 4
.
Ми шукатимемо назву змінної за допомогою регулярного виразу \w+
. Насправді імена змінних JavaScript потребують трохи складніших регулярних виразів для точної відповідності, але тут це не важливо.
- Виклик
str.match(/\w+/)
знайде лише перше слово в рядку (let
). А це не те що нам потрібно. - Ми можемо додати прапорець
g
. Але тоді викликstr.match(/\w+/g)
шукатиме всі слова в тексті, тоді як нам потрібно лише одне слово на позиції4
.
Отже, як змусити регулярний вираз шукати саме на заданій позиції?
Спробуймо використати метод regexp.exec(str)
.
Для regexp
без прапорців g
і y
, цей метод шукає лише перший збіг, він працює, так само як str.match(regexp)
.
…Але якщо є прапорець g
, тоді він виконує пошук у str
, починаючи з позиції, збереженої у властивості regexp.lastIndex
. І, якщо він знаходить збіг, то змінює regexp.lastIndex
на індекс одразу після збігу.
Іншими словами, regexp.lastIndex
служить відправною точкою для пошуку, і кожен виклик regexp.exec(str)
встановлює нове значення (“після останнього збігу”). Звичайно, якщо є прапорець g
.
Отже, послідовні виклики regexp.exec(str)
повертають збіги один за одним.
Ось приклад таких викликів:
let str = 'let varName'; // Знайдімо всі слова в цьому рядку
let regexp = /\w+/g;
alert(regexp.lastIndex); // 0 (спочатку lastIndex=0)
let word1 = regexp.exec(str);
alert(word1[0]); // let (перше слово)
alert(regexp.lastIndex); // 3 (позиція після збігу)
let word2 = regexp.exec(str);
alert(word2[0]); // varName (друге слово)
alert(regexp.lastIndex); // 11 (позиція після збігу)
let word3 = regexp.exec(str);
alert(word3); // null (більше немає збігів)
alert(regexp.lastIndex); // 0 (скидається в кінці пошуку)
Ми можемо отримати всі збіги в циклі:
let str = 'let varName';
let regexp = /\w+/g;
let result;
while (result = regexp.exec(str)) {
alert( `Found ${result[0]} at position ${result.index}` );
// Знайдено let на позиції 0, після
// Знайдено varName на позиції 4
}
Таке використання regexp.exec
є альтернативою методу str.matchAll
, з трохи більшим контролем над процесом.
Повернемося до нашого завдання.
Ми можемо вручну встановити lastIndex
на 4
, щоб почати пошук із заданої позиції!
Ось так:
let str = 'let varName = "значення"';
let regexp = /\w+/g; // без прапорця "g", властивість lastIndex ігнорується
regexp.lastIndex = 4;
let word = regexp.exec(str);
alert(word); // varName
Ура! Проблема вирішена!
Ми здійснили пошук \w+
, починаючи з позиції regexp.lastIndex = 4
.
І результат нашого пошуку правильний.
…Але заждіть, не так швидко.
Зауважте: виклик regexp.exec
починає пошук із позиції lastIndex
, а потім продовжує пошук. Якщо на позиції lastIndex
немає слова, але воно знаходиться десь після неї, тоді воно буде знайдено:
let str = 'let varName = "значення"';
let regexp = /\w+/g;
// почати пошук з позиції 3
regexp.lastIndex = 3;
let word = regexp.exec(str);
// знайдено збіг на позиції 4
alert(word[0]); // varName
alert(word.index); // 4
Для деяких завдань, зокрема лексичного аналізу, це неправильно. Нам потрібно знайти збіг в заданій позиції в тексті, а не десь після неї. Це і є головне призначення прапорця y
.
Прапорець y
змушує regexp.exec
шукати саме на позиції lastIndex
, а не “починаючи з” неї.
Ось той самий пошук із прапорцем y
:
let str = 'let varName = "значення"';
let regexp = /\w+/y;
regexp.lastIndex = 3;
alert( regexp.exec(str) ); // null (на позиції 3 пробіл, а не слово)
regexp.lastIndex = 4;
alert( regexp.exec(str) ); // varName (слово на позиції 4)
Як ми бачимо, регулярний вираз /\w+/y
не знаходить збігів на позиції 3
(на відміну від регулярного виразу з прапорцем g
), але знаходить збіг на позиції 4
.
Але це не всі переваги використання прапорця y
, він також збільшує продуктивність пошуку.
Уявіть, у нас довгий текст, а в ньому зовсім немає збігів. Тоді пошук із прапорцем g
буде йти до кінця тексту й нічого не знайде, і це займе значно більше часу, ніж пошук із прапорцем y
, який перевіряє лише на вказаній позиції.
Лексичний аналіз часто вимагає пошук на конкретній позиції. Використання прапорця y
є ключем до правильної реалізації та хорошої продуктивності при виконанні таких завдань.