Липкий флаг "y", поиск на конкретной позиции

Чтобы разобрать флаг y и понять, чем же он так хорош, рассмотрим практический пример.

Одна из часто встречающихся задач регулярных выражений – «парсинг»: мы имеем текст и с помощью регулярных выражений разделяем его на логические компоненты, получаем структуру.

Например, в браузерах существует HTML-парсер, который превращает текст в структурированный документ. Также существуют парсеры для языков программирования, как JavaScript и любых других.

Мы не будем погружаться глубоко в тему написания парсеров (это специализированная область со своим набором инструментов и алгоритмов). Но в процессе их работы, вообще, в процессе анализа текста, очень часто возникает вопрос: «Что за сущность находится в тексте на заданной позиции?»

Например, для языка программирования варианты могут быть следующие:

  • Это название переменной или функции \w+?
  • Или число \d+?
  • Или оператор [+-/*]?
  • (Или же это синтаксическая ошибка, если не попадает ни под один из ожидаемых вариантов)

Обычно поиск ищет в строке с начала, а не с нужной позиции. Можно, конечно, сделать подстроку и искать в ней, но это приведёт к очень существенному замедлению работы, так как таких поисков много.

Ещё один вариант – использовать regexp.exec с определённым свойством regexp.lastIndex, но это тоже не совсем то, так как он ищет везде, начиная с lastIndex и далее.

Мы хотим проверять текст именно в той позиции, которую мы указали, а не «начиная» с неё.

В примере показано (ошибочное) использование lastIndex:

let str = "(text before) function ...";

// попробуем найти слово function на позиции 5:
let regexp = /function/g; // необходимо использовать флаг "g", в противном случае свойство lastIndex будет проигнорировано
regexp.lastIndex = 5

alert (regexp.exec(str)); // function

Регулярное выражение находит совпадение, потому что метод regexp.exec начинает искать с указанной позиции и продвигается дальше по тексту и находит совпадение в слове «function».

Чтобы узнать, находится ли совпадение в нужном месте можно использовать свойство regexp.exec(str).index и проверить равно ли оно 5, если нет, тогда игнорировать результат. Такое решение – рабочее, но оно имеет проблему производительности.

Алгоритм проверки регулярного выражение сделает ненужную работу по проверки текста после указанного значения. Нам просто-напросто не нужно искать после нужной позиции и тратить на это время, причём существенное, если текст большой.

Флаг «y»

Итак, мы пришли к проблеме: как проверить на совпадение с регулярным выражением ровно на конкретной позиции.

Ответ: флаг y, который создан именно для этого. С ним поиск производиться только в позиции, указанной в свойстве lastIndex.

Пример:

let str = "(text before) function ...";

let regexp = /function/y;
regexp.lastIndex = 5;

alert (regexp.exec(str)); // null (совпадение не найдено, в отличие от флага "g"!)

regexp.lastIndex = 14;

alert (regexp.exec(str)); // function (совпадение!)

Как мы видим, теперь регулярное выражение проверяет на совпадение только в указанной позиции.

Именно это делает флаг y уникальным, и очень важным при написании парсера.

Флаг y позволяет проверять регулярное выражение именно на конкретной позиции и двигаться дальше, после того как парсер определит, что именно находится в этой позиции – шаг за шагом исследуя текст.

Без этого флага регулярное выражение будет выполнять поиск до конца текста, что будет занимать время, особенно, если текст большой. Таким образом парсер будет очень медленным. Флаг y для подобных задач – именно то, что нужно.

Карта учебника

Комментарии

перед тем как писать…
  • Если вам кажется, что в статье что-то не так - вместо комментария напишите на GitHub.
  • Для одной строки кода используйте тег <code>, для нескольких строк кода — тег <pre>, если больше 10 строк — ссылку на песочницу (plnkr, JSBin, codepen…)
  • Если что-то непонятно в статье — пишите, что именно и с какого места.