При разработке вспомогательных инструментов — анализаторов кода, генераторов документации или построителей деревьев вызовов — часто требуется программно разобрать заголовок процедуры или функции 1С. Типичная задача состоит в том, чтобы получить имя метода, список его параметров и определить, является ли метод экспортным (содержит ли ключевое слово Экспорт).
Для решения этой задачи в среде Windows традиционно используется COM-объект VBScript.RegExp. Разберем типичные ошибки при составлении регулярных выражений, посмотрим на рабочие шаблоны и проанализируем ограничения данного подхода.
Рассмотрим шаблон, с которого началась проблема:
ШаблонФункции.Pattern = "\s*?(\S+?)\([\S\s]*?\)\s*?(экспорт)*?.*";
При таком шаблоне имя функции попадает в первую группу захвата (SubMatches), а группа с ключевым словом экспорт возвращает пустое значение. Выясним причину такого поведения:
\s*? перед группой и ленивый квантификатор *? на самой группе (экспорт)*? заставляют движок сопоставить ноль вхождений слова «экспорт». Так как после группы стоит жадный хвост .*, движок просто «проглатывает» слово «экспорт» в остаток строки, не захватывая его в группу.\S+?\( ожидает открывающую скобку сразу после идентификатора. Если в коде перед скобкой стоит один или несколько пробелов (например, МояФункция (Параметр)), ленивый шаблон \S+? может отработать некорректно.Экспорт, ЭКСПОРТ, экспорт). Без включенного свойства IgnoreCase поиск может завершиться неудачей.Скорректируем квантификаторы, сделаем группу экспорта опциональной с помощью знака ? (одно или ноль вхождений) и добавим обработку возможных пробелов перед скобками параметров.
РегЭксп = Новый COMОбъект("VBScript.RegExp");
РегЭксп.IgnoreCase = Истина;
РегЭксп.Global = Истина;
РегЭксп.Multiline = Истина;
// Шаблон для строки вида "ИмяМетода(Параметры) Экспорт"
РегЭксп.Pattern = "^\s*([a-zA-Zа-яА-ЯёЁ_][a-zA-Zа-яА-ЯёЁ0-9_]*)\s*\(([\s\S]*?)\)\s*(экспорт)?";
Разберем структуру этого выражения по шагам:
^\s* — начало строки с возможными ведущими пробелами или отступами.([a-zA-Zа-яА-ЯёЁ_][a-zA-Zа-яА-ЯёЁ0-9_]*) — первая подмаска (SubMatches[0]), строго захватывающая корректный идентификатор 1С (начинается с буквы или подчеркивания, не содержит спецсимволов).\s*\( — возможные пробелы перед открывающей скобкой и сама скобка.([\s\S]*?) — вторая подмаска (SubMatches[1]), извлекающая тело параметров метода (включая многострочные переносы).\)\s* — закрывающая скобка и пробелы после нее.(экспорт)? — третья подмаска (SubMatches[2]), опционально захватывающая ключевое слово. Если метод экспортный, эта группа будет содержать строку «Экспорт» (или «экспорт»), иначе останется пустой.Посмотрим на практический пример обработки строки в 1С:
РегЭксп = Новый COMОбъект("VBScript.RegExp");
РегЭксп.IgnoreCase = Истина;
РегЭксп.Global = Ложь;
РегЭксп.Pattern = "^\s*([a-zA-Zа-яА-ЯёЁ_][a-zA-Zа-яА-ЯёЁ0-9_]*)\s*\(([\s\S]*?)\)\s*(экспорт)?";
СтрокаТеста = "РассчитатьСумму(Знач Сумма, СтавкаНДС = 20) Экспорт // комментарий";
Совпадения = РегЭксп.Execute(СтрокаТеста);
Если Совпадения.Count > 0 Тогда
Результат = Совпадения.Item(0);
ИмяМетода = Результат.SubMatches.Item(0);
Параметры = Результат.SubMatches.Item(1);
ПризнакЭкспорт = (Результат.SubMatches.Count > 2 И ЗначениеЗаполнено(Результат.SubMatches.Item(2)));
Сообщить("Имя: " + ИмяМетода);
Сообщить("Параметры: " + Параметры);
Сообщить("Экспортный: " + ?(ПризнакЭкспорт, "Да", "Нет"));
КонецЕсли;
Если стоит задача не просто обработать отдельную строку, а выделить все процедуры и функции из текста модуля, потребуется предварительно изолировать комментарии и строковые литералы, чтобы регулярное выражение не срабатывало на ложные совпадения внутри строк.
Рассмотрим пример шаблонов для лексического разбиения текста модуля:
idТекст = ".*";
idПР = "[ \t]*";
idСтрокаКомментария = "(?://" + idТекст + "$)";
idФункция = "функция([\S\s]*?)конецфункции";
idПроцедура = "процедура([\S\s]*?)конецпроцедуры";
idЛитералСтрока = "(""[^""]*""(?:""[^""]*"")*(?:""[^""]*\s*?|[^""]*(?:""[^""]*"")*"")*?)";
idКомментарий = "(" + idСтрокаКомментария + "(?:\n" + idПР + idСтрокаКомментария + ")*)";
// Объединенный шаблон разбора
idРазборМодуля = idКомментарий + "|" + idЛитералСтрока + "|" + idПроцедура + "|" + idФункция;
Перед анализом структуры процедур рекомендуется выполнить первичную очистку текста:
//).Строка = "Текст (в скобках)" не ломали подсчет параметров.Процедура ... КонецПроцедуры и Функция ... КонецФункции.Проанализируем важные технические нюансы, с которыми можно столкнуться при использовании регулярных выражений для синтаксического анализа кода 1С:
VBScript.RegExp доступен исключительно в среде Windows. При выполнении кода на сервере под управлением Linux вызов Новый COMОбъект("VBScript.RegExp") вызовет исключение.VBScript.RegExp реализует спецификацию ECMA-262 3rd Edition. В нем отсутствуют ретроспективные проверки (lookbehind), именованные группы захвата и поддержка классов Unicode.&НаСервере, &НаКлиенте) и модификаторы асинхронности (Асинх Процедура / Async Procedure). Шаблон должен учитывать возможность наличия таких ключевых слов перед именем метода.[\s\S]*? на больших текстах модулей (более нескольких мегабайт) может приводить к зависанию процесса или переполнению стека. Для полноценного анализа сложных графов вызовов предпочтительнее использовать специализированные парсеры (построение AST) или анализировать выгрузку конфигурации в файлы.