alexkmbk / RegEx1CAddin

Native API component for executing regular expressions on 1C: Enterprise platform / Внешняя Native API компонента для выполнения регулярных выражений на платформе 1С:Предприятие 8
The Unlicense
173 stars 32 forks source link

Совместимость с VBScript #8

Open tormozit opened 3 years ago

tormozit commented 3 years ago
  1. Есть ли понимание, насколько эта реализация вычисления регулярных выражений совместима с VBScript ?
  2. Можно ли дополнить объектную модель компоненты избыточными элементами для быстрого подключения компоненты к большому коду, опирающемуся на объектную модель VBScript? Приложил пример Clip_208849.txt
alexkmbk commented 3 years ago

@tormozit

Можно ли дополнить объектную модель компоненты избыточными элементами для быстрого подключения компоненты к большому коду, опирающемуся на объектную модель VBScript?

Полной совместимости не получиться, в силу ограничений протокола Native API, который, например, не позволяет возвращать коллекции. Прилагаю файл с кодом, приближенным к вашему, который работает на внешней компоненте: АналогичныйКодНаRegEx1CAddin.txt

В принципе, можно синтаксис еще глубже приблизить к VBScript если это в самом деле целесообразно с учетом того, что он в любом случе не будет на 100% совместим.

alexkmbk commented 3 years ago

@tormozit

Есть ли понимание, насколько эта реализация вычисления регулярных выражений совместима с VBScript ?

В компоненте используется движок от библиотеки Boost.RegEx. Он точно отличается от VBScript. На текущий момент выявлено расхождение, описанное в этом issue: https://github.com/alexkmbk/RegEx1CAddin/issues/4

Полный список расхождений к сожалению мне не известен в виду известной сложности проведения подобного анализа.

alexkmbk commented 3 years ago

@tormozit После перехода на движок PCRE2 вероятно будет полная совместимость с синтаксисом regex101.com, поскольку там судя по всему используется по дефолту та же библиотека. На текущий момент собрана тестовая версия на движке PCRE2 под все поддеживаемые плафтормы - https://github.com/alexkmbk/RegEx1CAddin/releases/tag/13.0

alexkmbk commented 3 years ago

@tormozit Добавил функцию MatchesJSON\НайтиСовпаденияJSON она возвращает результаты поиска в виде JSON текста. Если третий параметр функции равен Ложь, тогда результат представляет из себя простой массив, иначе, результат представляет из себя массив массивов, где вложенный массив представляет из себя коллекцию результатов, с первым элементом- найденная строка, последующие элементы - подгруппы. Пример обхода твоего шаблона:

ResJSON = RegExp.MatchesJSON(ТекстГдеИскать, ,Истина);

jsonreader = new JSONReader;
jsonreader.SetString(ResJSON);
res = ReadJSON(jsonreader, True); 

For Each Item In res Do
    //Начало = Вхождение.FirstIndex; // нет аналога
    //Длина = СтрДлина(RegExp.CurrentValue);
    Значение = Item[0];
    ОписаниеФункцииПроцедурыЗначение = Item[1];
    ОписаниеФункцииПроцедуры1Значение = Item[2];
    ОписаниеФункцииПроцедуры2Значение = Item[3];
    ОписаниеФункцииПроцедуры3Значение = Item[4];
    ОписаниеФункцииПроцедуры4Значение = Item[5];
    ОписаниеФункцииПроцедуры5Значение = Item[6];
    ОписаниеФункцииПроцедуры6Значение = Item[7];

    КонецЦикла;

КонецЦикла;

Компоненту прикладываю к этому сообщению в виде внешних файлов (это не архив компонент). RegExWin32.zip RegExWin64.zip

По результатам тестов, через JSON работает в несколько раз быстрее. Текущая версия тестовая, не для прода.

tormozit commented 3 years ago

Хорошо. Но FirstIndex мне необходим. Он используются у меня во многих местах. Есть шансы на его появление?

alexkmbk commented 3 years ago

Хорошо. Но FirstIndex мне необходим. Он используются у меня во многих местах. Есть шансы на его появление?

В результатах функции НайтиСовпадения (объектная модель обхода) да, а вот в виде Json есть ли предложения как это можно сделать? Сам Json позволяет, а вот при десериализации в объекты 1С, как это будет выглядеть? в 1С есть требования к ключам стукртур и соответствий, у стрктуры ключ не может начинаться с цифр и т.д., у соответствий ключ уникален. Как вариант можно вообще в виде Json текст не возвращать, а возвращать только интервалы с координатами найденных подстрок. тогда FirstIndex можно будет получить просто из значения первого интервала.

tormozit commented 3 years ago

при десериализации в объекты 1С, как это будет выглядеть?

Это

      {
        "FirstIndex": 86534,
        "Match": {...}
      }

Десериализуется в структуру. Т.е. нужен массив структур, внутри которых одним из свойств является массив подгрупп

alexkmbk commented 3 years ago

при десериализации в объекты 1С, как это будет выглядеть?

Это

      {
        "FirstIndex": 86534,
        "Match": {...}
      }

Десериализуется в структуру. Т.е. нужен массив структур, внутри которых одним из свойств является массив подгрупп

Ну да, как вариант. А что думаешь про вариант с возвратом не самого текста, а значений интервалов? А затем получение уже значений с помощью функции Сред? Решается вопрос с экранированием символов (сейчас экранирование сделано, но некоторые накладные расходы на это присутствуют). И мы также исключаем лишнее копирование найденного текста. Это должен быть самый быстрый способ получения результатов.

tormozit commented 3 years ago

Возврат результата в виде диапазонов будет самым оптимальным с точки зрения скорости и потребления ресурсов. Но нужно тщательно проверить корректность расчета этих позиций - символы концов строк CRLF, LF, CR теоритически могут по- разному считаться в 1С и твоем движке. Поэтому я FirstIndex стараюсь использовать минимально.

alexkmbk commented 3 years ago

Переделал: RegExWin32.zip RegExWin64.zip Теперь вариант обхода будет выглядеть так:

ResJSON = RegExp.MatchesJSON(ТекстГдеИскать);

jsonreader = new JSONReader;
jsonreader.SetString(ResJSON);
res = ReadJSON(jsonreader, False); 

For Each Item In res Do
    Начало = Item.FirstIndex; 
    Длина = Item.Length;
    Значение = Item.Value;
    ОписаниеФункцииПроцедурыЗначение = Item.SubMatches[0];
    ОписаниеФункцииПроцедуры1Значение = Item.SubMatches[1];
    ОписаниеФункцииПроцедуры2Значение = Item.SubMatches[2];
    ОписаниеФункцииПроцедуры3Значение = Item.SubMatches[3];
    ОписаниеФункцииПроцедуры4Значение = Item.SubMatches[4];
    ОписаниеФункцииПроцедуры5Значение = Item.SubMatches[5];
    ОписаниеФункцииПроцедуры6Значение = Item.SubMatches[6];         
КонецЦикла;

Также свойство FirstIndex добавлено в объектную модель обхода результатов. Результат несколько хуже по скорости чем предыдущий, но все равно намного быстрее чем через объектную модель.

alexkmbk commented 3 years ago

@tormozit Еще до кучи добавил вариант с возвратом Json с интервалами, этот режим включается, если третьим параметром функции MatchesJSON передать Истина. Тесты показали что в этом режиме выигрыш совершенно не значительный (5-20%), поэтому не уверен что оставлю это в финальной версии. Обход результатов в этом случае, будет выглядеть так:

ResJSON = RegExp.MatchesJSON(ТекстГдеИскать,,True);

jsonreader = new JSONReader;
jsonreader.SetString(ResJSON);
res = ReadJSON(jsonreader, False); 

For Each Item In res Do
    Начало = Item[0]; 
    Значение = Mid(ТекстГдеИскать, Item[0] + 1, Item[1]-Item[0]);
    Длина = Item[1]-Item[0];

    ОписаниеФункцииПроцедурыЗначение = Mid(ТекстГдеИскать, Item[2] + 1, Item[3] - Item[2]);
    ОписаниеФункцииПроцедуры1Значение = Mid(ТекстГдеИскать, Item[4] + 1, Item[5]- Item[4]);
    ОписаниеФункцииПроцедуры2Значение = Mid(ТекстГдеИскать, Item[6] + 1, Item[7]-Item[6]);
    ОписаниеФункцииПроцедуры3Значение = Mid(ТекстГдеИскать, Item[8] + 1, Item[9]- Item[8]);
    ОписаниеФункцииПроцедуры4Значение = Mid(ТекстГдеИскать, Item[10] + 1, Item[11]-Item[10]);
    ОписаниеФункцииПроцедуры5Значение = Mid(ТекстГдеИскать, Item[12] + 1, Item[13]-Item[12]);   
    ОписаниеФункцииПроцедуры6Значение = Mid(ТекстГдеИскать, Item[14] + 1, Item[15]- Item[14]);  

КонецЦикла;

Еще в этой сборке добавил синоним Test для функции IsMatch, для совместимости с VBScript. Можно еще добавить синоним SubMatches, для функции GetSubMatch если это поможет в миграции.

RegExWin32.zip RegExWin64.zip

alexkmbk commented 3 years ago

@tormozit ИМХО, для такого крупного проекта как у тебя, наверное было бы оптимальнее не осуществлять сразу переход на новый движок, а добавить дополнительный слой абстракции, который бы позволял подключать разные механизмы для регулярок. Например, для Windows использовать VBScript, а для Линукса и Мака использовать компоненту, это позволит постепенно адаптировать существующие регулярные выражения под новый движок, с учетом различий от VBScript и при этом переход не затронет основную массу пользователей. В последствии, можно будет полностью перейти на внешнюю компоненту.

tormozit commented 3 years ago

добавить дополнительный слой абстракции

Да. Только так я и внедряю подобные альтернативные механизмы. Без этого я бы утонул в ошибках особенно с учетом отсутствия разветвленной версионности в конфигураторе. Одним из ярких примеров была попытка перехода на RexV8 https://infostart.ru/public/183084/ , которая после долгих мучений была признана провальной и слой абстракции позволил мне быстро вернуться без потерь. Но там была тонкая прослойка. А тут придется делать большую и сложную.

tormozit commented 3 years ago

Вспомнил что JSON в платформе появился с 8.3.6. А у меня поддержка с 8.2.13. Если это не сложно, то было бы неплохо еще добавить функцию MatchesXML. Но если сложно, то оно того точно не стОит, т.к. процент пользователей на 8.2 очень небольшой.

alexkmbk commented 3 years ago

Вспомнил что JSON в платформе появился с 8.3.6. А у меня поддержка с 8.2.13. Если это не сложно, то было бы неплохо еще добавить функцию MatchesXML. Но если сложно, то оно того точно не стОит, т.к. процент пользователей на 8.2 очень небольшой.

Кстати для JSON под 8.2 можно воспользоваться этим проектом - https://github.com/legionWFZ/1C-JSON

tormozit commented 3 years ago

В реализации VBScript коллекция SubMatches (подгруппы) всегда содержит столько элементов, сколько подгрупп в выражении. Для не найденных подгрупп - Неопределено.Твой же результат содержит пустую коллекцию

Шаблон ="(?:[^_ЁА-ЯA-Z\d\.]|^)([_ЁА-ЯA-Z][_ЁА-ЯA-Z\d]*(\([^\(\)]*(?:\([^\(\)]*\)[^\(\)]*)*\))?((\.([_ЁА-ЯA-Z][_ЁА-ЯA-Z\d]*)(\([^\(\)]*(?:\([^\(\)]*\)[^\(\)]*)*\))?)|(\[[^\]\[]+?(?:(?:\[[^\]]+?\][^\]\[]*?)*)*\]))*\.?)?$";
Строка = "                ";
РезультатJSON = MatchesJSON(Строка , Шаблон );

изображение

Попробовал несколько разных шаблонов. Похоже коллекция SubMatches всегда возвращается пустой.

alexkmbk commented 3 years ago

@tormozit Можно сравнить с https://regex101.com/, там тоже группы не находит по этому шаблону для всех flavors кроме golang. Для этого шаблона, группы возвращает - https://github.com/alexkmbk/RegEx1CAddin/issues/8#issue-808630660

Если группы в шаблоне найдены, то они будут добавлены, даже если пустые.

tormozit commented 3 years ago

Тогда другой пример шаблон - (1)(2)?(3) текст - 13 Результат должен выдать одну группу (вхождение), внутри которой есть подгруппа (Submatch) №1 и подгруппа (Submatch) №3. Как ты вернешь такой массив Submatches? 3-й элемент в массиве может быть только при наличии всех предшествующих. Так что добавлять значение "Неопределено" все равно придется. Так сделай это сразу всегда. изображение

alexkmbk commented 3 years ago

(1)(2)?(3)

Вторая пустая будет, так и работает сейчас. То есть будет 3 группы. Сергей, есть предолжение issues по тестовой версии на движке pcre2 или вводить в одном issue или в отдельных, но как-то помечать что это pcre2, потому что большинство из найденных проблем не будут касаться текущей стабильной версии, ну или я сам потом добавлю примечание.

tormozit commented 3 years ago

Вторая пустая будет, так и работает сейчас. То есть будет 3 группы.

Это неправильно. Подгруппа может иметь значение <пустая строка> или отсутствовать, что для случая массива может быть обозначено только заменой на значение "Неопределено". Ты сейчас вместо отсутствия возвращаешь <пустая строка>, т.е. искажаешь результат. Подобные грабли мы уже проходили с RexV8.

alexkmbk commented 3 years ago

Вторая пустая будет, так и работает сейчас. То есть будет 3 группы.

Это неправильно. Подгруппа может иметь значение <пустая строка> или отсутствовать, что для случая массива может быть обозначено только заменой на значение "Неопределено". Ты сейчас вместо отсутствия возвращаешь <пустая строка>, т.е. искажаешь результат. Подобные грабли мы уже проходили с RexV8.

а понял, согласен