SekraSoft (sekrasoft) wrote,
SekraSoft
sekrasoft

Category:

Многострочные регулярные выражения в JavaScript

Не так давно мы выяснили, что с помощью редко используемых конструкций, Function.prototype.toString, обработки строк и eval или Function можно создать своё подмножество JS, которое при обработке средствами JS добавляет невиданные доселе возможности. Скажем, возможность запуска функций в синхронном стиле или возможность добавления препроцессора почти как в C.

Сегодня мы поговорим о гораздо более простых вещах. О том, что знает каждый из нас.

Многострочная строка


function line(f){ return f.toString().match(/\/\*([\s\S]*)\*\//)[1]; }

Самый простой способ сделать многострочную строку - записать строку внутри комментария, а затем вынуть её оттуда регулярным выражением. Конечно же, не без помощи Function.prototype.toString.
Кто-то использует для этого содержимое скрытых HTML-узлов, но подобный подход смотрится крайне странно при использовании Node.JS.
Кто-то пользуется стандартной возможностью, но ему приходится долго и упорно расставлять "\" и "\n".

А вот как используется функция line:
var test = line(function(){/*
  My
      multiline
                  string!
*/});
 
console.log(test); 
 
// выведет:
//
//  My
//      multiline
//                  string!
//
 

Заметим, что в строка начинается и кончается символами переноса строки. Избежать этого можно, немного усложнив регулярное выражение.

Многострочное регулярное выражение


Строка - это хорошо. Имея многострочную строку и парсер, можно "вклеивать" в код исходники на других языках (если заранее написан интерпретатор), небольшие HTML-страницы, простые таблицы и многое другое.

Полезно иметь возможность писать многострочные регулярные выражения и комментировать их. Сегодня я увидел такую возможность в каком-то языке и порадовался.
function createRegExp(func){
  if(typeof func !== 'function') throw new TypeError(func + ' is not a function');
 
  var m = func.toString(). // Да, мы наркоманы
    replace(/\s+|--.*?((?=\*\/)|$)/gm, '').
    match(/^.*?\/\*\/((?:\\\/|.)*?)\/(?:([img]+?)\/?)?\*\/\}$/);
 
  if(!m) throw new TypeError('Invalid RegExp format');
  return new RegExp(m[1], m[2] || undefined);
}

Функция createRegExp в качестве аргумента принимает функцию с закомментированным регулярным выражением. Оно может быть многострочным, иметь флаги и однострочные комментарии. Усложнив createRegExp, можно добавить многострочные комментарии, поддержку констант и своего микроязыка (например, for_all="[\s\S]+"; until_e="?e"; return /^ for_all until_e/;). Если хорошенько подумать над кодом и усовершенствовать возвращаемые значения (возвращать уже свой доморощенный объект), можно реализовать хоть рекурсивные условные переменные, в итоге позволив пользователю парсить XML получившимися "как-бы регулярками", если он осилит предложенный программистом способ записи грамматик...

А теперь вернёмся на Землю и продемонстрируем работу createRegExp на её же регулярных выражениях:
console.log(createRegExp(function(){/*
  /
      \s+               -- непустая последовательность пробельных символов (пробелы, переносы строки)
    |                   -- или
      - -               -- строки, начинающиеся на "--"
      .*?               -- содержащие некоторое количество символов
      (
          (?= \*\/ )    -- и заканчивающиеся либо символом закрытия комментария JS...
                        --   важно не удалить закрытие комментария JS вместе с комментарием регулярного выражения
                        --   иначе второе регулярное выражение из createRegExp не будет соответствовать строке
                        --   (поэтому и используется ?=, чтобы комментарий не захватывался)
        |               -- ...или
          $             -- концом строки
      )
  /
  g                     -- причём, замена по этому регулярному выражению будет выполнена несколько раз,
  m                     -- а $ символизирует конец подстроки
*/}), createRegExp(function(){/*
  /
    ^.*?\/\*            -- какие-то символы и открытие комментария в начале строки
    \/                  -- символ "/" - начало регулярного выражения
    ( (?: \\\/ | . )*? )-- сохраняем наименьшую последовательность строки "\/" или любых символов,
    \/                  -- идущих до символа "/"
    (?:([img]+?)\/?)?   -- если есть последовательность из букв i, m, g, вероятно, заканчивающаяся на "/",
                        -- сохраняем её
    \*\/\}$             -- конец регулярного выражения сопровождается концом комментария и
                        -- исходный код функции завершается
  /
*/}), createRegExp(function(){/*
  /^.*?\/\*\/((\\\/|.)*?)\/((.+?)\/?)?\*\/\}$/ -- тут всё очевидно, можно не расписывать
*/})
);
 
// выведет:
// /\s+|--.*?((?=\*\/)|$)/gm /^.*?\/\*\/((?:\\\/|.)*?)\/(?:([img]+?)\/?)?\*\/\}$/ /^.*?\/\*\/((\\\/|.)*?)\/((.+?)\/?)?\*\/\}$/
 

В более ранней версии createRegExp была возможность использовать символ # для начала комментария. Но при создании примеров выше, она утратила эту возможность. Действительно, как бы автор использовал "#" внутри создаваемого регулярного выражения?

И конечно же, ультратонкая версия createRegExp:
function crRegExp(f){var m;return RegExp((m=/^.*?\/\*\/((\\\/|.)*?)\/((.+?)\/?)?\*\/\}$/.exec((f+'').replace(/\s+|--.*?((?=\*\/)|$)/gm,'')))[1],m[4]||'');}
Subscribe

  • Post a new comment

    Error

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

    When you submit the form an invisible reCAPTCHA check will be performed.
    You must follow the Privacy Policy and Google Terms of use.
  • 2 comments